Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/modules/databases/jdbc.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,17 @@ Examples/Tests:

* [MySQL](https://github.com/testcontainers/testcontainers-java/blob/main/modules/mysql/src/test/java/org/testcontainers/junit/mysql/SimpleMySQLTest.java)
* [PostgreSQL](https://github.com/testcontainers/testcontainers-java/blob/main/modules/postgresql/src/test/java/org/testcontainers/junit/postgresql/SimplePostgreSQLTest.java)

## Init script encoding for container instances

Scripts configured with `withInitScript` or `withInitScripts` are read as UTF-8 by default.
For scripts saved with another encoding, set the charset on the container before starting it:

```java
container.withInitScript("init.sql")
.withInitScriptCharset(java.nio.charset.Charset.forName("windows-1252"));
```

The charset applies to all scripts configured on that container. It controls how the script files
are read, not the database's character set. Scripts loaded through the JDBC URL parameter
`TC_INITSCRIPT` continue to use UTF-8.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.LinkedList;
import java.util.List;
Expand Down Expand Up @@ -192,12 +193,24 @@ public static boolean containsSqlScriptDelimiters(
}

/**
* Load script from classpath and apply it to the given database
* Load a UTF-8 script from the classpath and apply it to the given database.
*
* @param databaseDelegate database delegate for script execution
* @param initScriptPath the resource to load the init script from
*/
public static void runInitScript(DatabaseDelegate databaseDelegate, String initScriptPath) {
runInitScript(databaseDelegate, initScriptPath, StandardCharsets.UTF_8);
}

/**
* Load a script from the classpath using the given charset and apply it to the given database.
*
* @param databaseDelegate database delegate for script execution
* @param initScriptPath the resource to load the init script from
* @param charset the charset used to read the script; never {@code null}
*/
public static void runInitScript(DatabaseDelegate databaseDelegate, String initScriptPath, Charset charset) {
checkArgument(charset != null, "charset must not be null");
try {
URL resource = Thread.currentThread().getContextClassLoader().getResource(initScriptPath);
if (resource == null) {
Expand All @@ -209,7 +222,7 @@ public static void runInitScript(DatabaseDelegate databaseDelegate, String initS
);
}
}
String scripts = IOUtils.toString(resource, StandardCharsets.UTF_8);
String scripts = IOUtils.toString(resource, charset);
executeDatabaseScript(databaseDelegate, initScriptPath, scripts);
} catch (IOException e) {
LOGGER.warn("Could not load classpath init script: {}", initScriptPath);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.testcontainers.ext;

import org.junit.jupiter.api.Test;
import org.testcontainers.delegate.AbstractDatabaseDelegate;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class ScriptUtilsTest {

private final RecordingDatabaseDelegate delegate = new RecordingDatabaseDelegate();

@Test
void usesUtf8ByDefault() {
ScriptUtils.runInitScript(delegate, "init-utf-8.sql");

assertThat(delegate.statements).containsExactly("SELECT 'café'");
}

@Test
void readsWindows1252Script() {
ScriptUtils.runInitScript(delegate, "init-windows-1252.sql", Charset.forName("windows-1252"));

assertThat(delegate.statements).containsExactly("SELECT 'café'");
}

@Test
void readsExplicitUtf8Script() {
ScriptUtils.runInitScript(delegate, "init-utf-8.sql", StandardCharsets.UTF_8);

assertThat(delegate.statements).containsExactly("SELECT 'café'");
}

@Test
void rejectsNullCharset() {
assertThatThrownBy(() -> ScriptUtils.runInitScript(delegate, "init-utf-8.sql", null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("charset must not be null");
}

private static class RecordingDatabaseDelegate extends AbstractDatabaseDelegate<Void> {

private final List<String> statements = new ArrayList<>();

@Override
public void execute(
String statement,
String scriptPath,
int lineNumber,
boolean continueOnError,
boolean ignoreFailedDrops
) {
statements.add(statement);
}

@Override
protected Void createNewConnection() {
return null;
}

@Override
protected void closeConnectionQuietly(Void connection) {}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SELECT 'café';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SELECT 'caf�';
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import org.testcontainers.utility.DockerImageName;
import org.testcontainers.utility.MountableFile;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
Expand Down Expand Up @@ -41,6 +43,8 @@ public abstract class JdbcDatabaseContainer<SELF extends JdbcDatabaseContainer<S

private List<String> initScriptPaths = new ArrayList<>();

private Charset initScriptCharset = StandardCharsets.UTF_8;

protected Map<String, String> parameters = new HashMap<>();

protected Map<String, String> urlParameters = new HashMap<>();
Expand Down Expand Up @@ -171,6 +175,18 @@ public SELF withInitScripts(Iterable<String> initScriptPaths) {
return self();
}

/**
* Sets the charset used to read all scripts configured with {@link #withInitScript(String)}
* or {@link #withInitScripts(String...)}. Defaults to UTF-8.
*
* @param charset the charset used to read the scripts
* @return self
*/
public SELF withInitScriptCharset(@NonNull Charset charset) {
this.initScriptCharset = charset;
return self();
}

@SneakyThrows(InterruptedException.class)
@Override
protected void waitUntilContainerStarted() {
Expand Down Expand Up @@ -365,7 +381,7 @@ protected void runInitScriptIfRequired() {
initScriptPaths
.stream()
.filter(Objects::nonNull)
.forEach(path -> ScriptUtils.runInitScript(getDatabaseDelegate(), path));
.forEach(path -> ScriptUtils.runInitScript(getDatabaseDelegate(), path, initScriptCharset));
}

public void setParameters(Map<String, String> parameters) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@
import lombok.NonNull;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.testcontainers.delegate.DatabaseDelegate;

import java.nio.charset.Charset;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collections;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

class JdbcDatabaseContainerTest {

Expand All @@ -20,7 +26,56 @@ void anExceptionIsThrownIfJdbcIsNotAvailable() {
assertThatExceptionOfType(IllegalStateException.class).isThrownBy(jdbcContainer::waitUntilContainerStarted);
}

static class JdbcDatabaseContainerStub extends JdbcDatabaseContainer {
@Test
void readsInitScriptAsUtf8ByDefault() {
JdbcDatabaseContainerStub container = new JdbcDatabaseContainerStub("mysql:latest");
container.withInitScript("init-utf-8.sql");

container.runInitScriptIfRequired();

verify(container.delegate).execute(Collections.singletonList("SELECT 'café'"), "init-utf-8.sql", false, false);
}

@Test
void readsInitScriptWithConfiguredCharset() {
JdbcDatabaseContainerStub container = new JdbcDatabaseContainerStub("mysql:latest");
assertThat(container.withInitScriptCharset(Charset.forName("windows-1252"))).isSameAs(container);
container.withInitScript("init-windows-1252.sql");

container.runInitScriptIfRequired();

verify(container.delegate)
.execute(Collections.singletonList("SELECT 'café'"), "init-windows-1252.sql", false, false);
}

@Test
void readsAllInitScriptsWithConfiguredCharset() {
JdbcDatabaseContainerStub container = new JdbcDatabaseContainerStub("mysql:latest");
container
.withInitScripts("init-windows-1252.sql", null, "init-windows-1252.sql")
.withInitScriptCharset(Charset.forName("windows-1252"));

container.runInitScriptIfRequired();

verify(container.delegate, times(2))
.execute(Collections.singletonList("SELECT 'café'"), "init-windows-1252.sql", false, false);
}

@Test
void rejectsNullInitScriptCharset() {
JdbcDatabaseContainerStub container = new JdbcDatabaseContainerStub("mysql:latest");

assertThatExceptionOfType(NullPointerException.class).isThrownBy(() -> container.withInitScriptCharset(null));
}

static class JdbcDatabaseContainerStub extends JdbcDatabaseContainer<JdbcDatabaseContainerStub> {

private final DatabaseDelegate delegate = mock(DatabaseDelegate.class);

@Override
protected DatabaseDelegate getDatabaseDelegate() {
return delegate;
}

public JdbcDatabaseContainerStub(@NonNull String dockerImageName) {
super(dockerImageName);
Expand Down
1 change: 1 addition & 0 deletions modules/jdbc/src/test/resources/init-utf-8.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SELECT 'café';
1 change: 1 addition & 0 deletions modules/jdbc/src/test/resources/init-windows-1252.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SELECT 'caf�';