diff --git a/docs/modules/databases/jdbc.md b/docs/modules/databases/jdbc.md index 72539aef44e..c2473539c8a 100644 --- a/docs/modules/databases/jdbc.md +++ b/docs/modules/databases/jdbc.md @@ -174,3 +174,13 @@ 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 + +Init scripts configured with `withInitScript` or `withInitScripts` are read as UTF-8 by default. If your scripts use another encoding, set it on the container: + + +[Init script charset](../../../modules/postgresql/src/test/java/org/testcontainers/postgresql/PostgreSQLContainerTest.java) inside_block:initScriptCharset + + +The charset applies to all init scripts of the container. Scripts loaded via `TC_INITSCRIPT` are always read as UTF-8. diff --git a/modules/database-commons/src/main/java/org/testcontainers/ext/ScriptUtils.java b/modules/database-commons/src/main/java/org/testcontainers/ext/ScriptUtils.java index b78ac4d63c4..c56a869a930 100644 --- a/modules/database-commons/src/main/java/org/testcontainers/ext/ScriptUtils.java +++ b/modules/database-commons/src/main/java/org/testcontainers/ext/ScriptUtils.java @@ -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; @@ -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) { @@ -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); diff --git a/modules/database-commons/src/test/java/org/testcontainers/ext/ScriptUtilsTest.java b/modules/database-commons/src/test/java/org/testcontainers/ext/ScriptUtilsTest.java new file mode 100644 index 00000000000..917606c102d --- /dev/null +++ b/modules/database-commons/src/test/java/org/testcontainers/ext/ScriptUtilsTest.java @@ -0,0 +1,61 @@ +package org.testcontainers.ext; + +import org.junit.jupiter.api.Test; +import org.testcontainers.delegate.AbstractDatabaseDelegate; + +import java.nio.charset.Charset; +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 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 { + + private final List 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) {} + } +} diff --git a/modules/database-commons/src/test/resources/init-utf-8.sql b/modules/database-commons/src/test/resources/init-utf-8.sql new file mode 100644 index 00000000000..a90ec0ec243 --- /dev/null +++ b/modules/database-commons/src/test/resources/init-utf-8.sql @@ -0,0 +1 @@ +SELECT 'café'; diff --git a/modules/database-commons/src/test/resources/init-windows-1252.sql b/modules/database-commons/src/test/resources/init-windows-1252.sql new file mode 100644 index 00000000000..b6dcf5effe7 --- /dev/null +++ b/modules/database-commons/src/test/resources/init-windows-1252.sql @@ -0,0 +1 @@ +SELECT 'café'; diff --git a/modules/jdbc/src/main/java/org/testcontainers/containers/JdbcDatabaseContainer.java b/modules/jdbc/src/main/java/org/testcontainers/containers/JdbcDatabaseContainer.java index cf6c995528f..f8d9c26b74f 100644 --- a/modules/jdbc/src/main/java/org/testcontainers/containers/JdbcDatabaseContainer.java +++ b/modules/jdbc/src/main/java/org/testcontainers/containers/JdbcDatabaseContainer.java @@ -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; @@ -41,6 +43,8 @@ public abstract class JdbcDatabaseContainer initScriptPaths = new ArrayList<>(); + private Charset initScriptCharset = StandardCharsets.UTF_8; + protected Map parameters = new HashMap<>(); protected Map urlParameters = new HashMap<>(); @@ -171,6 +175,18 @@ public SELF withInitScripts(Iterable 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() { @@ -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 parameters) { diff --git a/modules/postgresql/src/test/java/org/testcontainers/postgresql/PostgreSQLContainerTest.java b/modules/postgresql/src/test/java/org/testcontainers/postgresql/PostgreSQLContainerTest.java index beefa3776b1..3a96f44b212 100644 --- a/modules/postgresql/src/test/java/org/testcontainers/postgresql/PostgreSQLContainerTest.java +++ b/modules/postgresql/src/test/java/org/testcontainers/postgresql/PostgreSQLContainerTest.java @@ -4,6 +4,7 @@ import org.testcontainers.PostgreSQLTestImages; import org.testcontainers.db.AbstractContainerDatabaseTest; +import java.nio.charset.Charset; import java.sql.ResultSet; import java.sql.SQLException; import java.util.logging.Level; @@ -87,6 +88,24 @@ void testExplicitInitScript() throws SQLException { } } + @Test + void testExplicitInitScriptWithCharset() throws SQLException { + try ( + // initScriptCharset { + PostgreSQLContainer postgres = new PostgreSQLContainer(PostgreSQLTestImages.POSTGRES_TEST_IMAGE) + .withInitScript("somepath/init_postgresql_windows-1252.sql") + .withInitScriptCharset(Charset.forName("windows-1252")) + // } + ) { + postgres.start(); + + ResultSet resultSet = performQuery(postgres, "SELECT foo FROM bar"); + + String firstColumnValue = resultSet.getString(1); + assertThat(firstColumnValue).as("Value from init script should equal real value").isEqualTo("café"); + } + } + @Test void testExplicitInitScripts() throws SQLException { try ( diff --git a/modules/postgresql/src/test/resources/somepath/init_postgresql_windows-1252.sql b/modules/postgresql/src/test/resources/somepath/init_postgresql_windows-1252.sql new file mode 100644 index 00000000000..9dbc89eeba6 --- /dev/null +++ b/modules/postgresql/src/test/resources/somepath/init_postgresql_windows-1252.sql @@ -0,0 +1,5 @@ +CREATE TABLE bar ( + foo VARCHAR(255) +); + +INSERT INTO bar (foo) VALUES ('café');