From 206c5f65482475e14e9fc1eba5edd7d20ae25aea Mon Sep 17 00:00:00 2001 From: "Roman S.A" Date: Thu, 21 May 2020 18:36:24 +0300 Subject: [PATCH] Added additional url params in JdbcDatabaseContainer (#1802) (#1874) --- .../containers/ClickHouseContainer.java | 4 + .../containers/CockroachContainer.java | 4 +- .../cockroachdb/SimpleCockroachDBTest.java | 20 +++++ .../containers/Db2Container.java | 4 +- .../junit/db2/SimpleDb2Test.java | 15 ++++ .../containers/JdbcDatabaseContainer.java | 23 +++++ .../containers/MariaDBContainer.java | 4 +- .../junit/mariadb/SimpleMariaDBTest.java | 20 +++++ .../containers/MSSQLServerContainer.java | 3 +- .../mssqlserver/SimpleMSSQLServerTest.java | 16 ++++ .../containers/MySQLContainer.java | 6 +- .../junit/mysql/SimpleMySQLTest.java | 83 ++++++++++++++++++- .../containers/OracleContainer.java | 5 ++ .../containers/PostgreSQLContainer.java | 24 ++---- .../PostgreSQLConnectionURLTest.java | 2 +- .../postgresql/SimplePostgreSQLTest.java | 15 ++++ 16 files changed, 222 insertions(+), 26 deletions(-) diff --git a/modules/clickhouse/src/main/java/org/testcontainers/containers/ClickHouseContainer.java b/modules/clickhouse/src/main/java/org/testcontainers/containers/ClickHouseContainer.java index 4d4ea2d11a9..10c0e1835c2 100644 --- a/modules/clickhouse/src/main/java/org/testcontainers/containers/ClickHouseContainer.java +++ b/modules/clickhouse/src/main/java/org/testcontainers/containers/ClickHouseContainer.java @@ -66,4 +66,8 @@ public String getTestQueryString() { return TEST_QUERY; } + @Override + public ClickHouseContainer withUrlParam(String paramName, String paramValue) { + throw new UnsupportedOperationException("The ClickHouse does not support this"); + } } diff --git a/modules/cockroachdb/src/main/java/org/testcontainers/containers/CockroachContainer.java b/modules/cockroachdb/src/main/java/org/testcontainers/containers/CockroachContainer.java index 3768ca7a008..de64902c88a 100644 --- a/modules/cockroachdb/src/main/java/org/testcontainers/containers/CockroachContainer.java +++ b/modules/cockroachdb/src/main/java/org/testcontainers/containers/CockroachContainer.java @@ -43,7 +43,9 @@ public String getDriverClassName() { @Override public String getJdbcUrl() { - return JDBC_URL_PREFIX + "://" + getHost() + ":" + getMappedPort(DB_PORT) + "/" + databaseName; + String additionalUrlParams = constructUrlParameters("?", "&"); + return JDBC_URL_PREFIX + "://" + getHost() + ":" + getMappedPort(DB_PORT) + + "/" + databaseName + additionalUrlParams; } @Override diff --git a/modules/cockroachdb/src/test/java/org/testcontainers/junit/cockroachdb/SimpleCockroachDBTest.java b/modules/cockroachdb/src/test/java/org/testcontainers/junit/cockroachdb/SimpleCockroachDBTest.java index 7c62b2f6bcf..14fe20222a3 100644 --- a/modules/cockroachdb/src/test/java/org/testcontainers/junit/cockroachdb/SimpleCockroachDBTest.java +++ b/modules/cockroachdb/src/test/java/org/testcontainers/junit/cockroachdb/SimpleCockroachDBTest.java @@ -9,6 +9,8 @@ import java.util.logging.Level; import java.util.logging.LogManager; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertThat; import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals; public class SimpleCockroachDBTest extends AbstractContainerDatabaseTest { @@ -42,4 +44,22 @@ public void testExplicitInitScript() throws SQLException { assertEquals("Value from init script should equal real value", "hello world", firstColumnValue); } } + + @Test + public void testWithAdditionalUrlParamInJdbcUrl() { + CockroachContainer cockroach = new CockroachContainer() + .withUrlParam("sslmode", "disable") + .withUrlParam("application_name", "cockroach"); + + try { + cockroach.start(); + String jdbcUrl = cockroach.getJdbcUrl(); + assertThat(jdbcUrl, containsString("?")); + assertThat(jdbcUrl, containsString("&")); + assertThat(jdbcUrl, containsString("sslmode=disable")); + assertThat(jdbcUrl, containsString("application_name=cockroach")); + } finally { + cockroach.stop(); + } + } } diff --git a/modules/db2/src/main/java/org/testcontainers/containers/Db2Container.java b/modules/db2/src/main/java/org/testcontainers/containers/Db2Container.java index b1472b21c6c..05edcb2c3ed 100644 --- a/modules/db2/src/main/java/org/testcontainers/containers/Db2Container.java +++ b/modules/db2/src/main/java/org/testcontainers/containers/Db2Container.java @@ -73,7 +73,9 @@ public String getDriverClassName() { @Override public String getJdbcUrl() { - return "jdbc:db2://" + getHost() + ":" + getMappedPort(DB2_PORT) + "/" + databaseName; + String additionalUrlParams = constructUrlParameters(":", ";", ";"); + return "jdbc:db2://" + getHost() + ":" + getMappedPort(DB2_PORT) + + "/" + databaseName + additionalUrlParams; } @Override diff --git a/modules/db2/src/test/java/org/testcontainers/junit/db2/SimpleDb2Test.java b/modules/db2/src/test/java/org/testcontainers/junit/db2/SimpleDb2Test.java index 08f427cf12b..6cc7535f785 100644 --- a/modules/db2/src/test/java/org/testcontainers/junit/db2/SimpleDb2Test.java +++ b/modules/db2/src/test/java/org/testcontainers/junit/db2/SimpleDb2Test.java @@ -7,6 +7,8 @@ import java.sql.ResultSet; import java.sql.SQLException; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertThat; import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals; public class SimpleDb2Test extends AbstractContainerDatabaseTest { @@ -24,4 +26,17 @@ public void testSimple() throws SQLException { assertEquals("A basic SELECT query succeeds", 1, resultSetInt); } } + + @Test + public void testWithAdditionalUrlParamInJdbcUrl() { + try (Db2Container db2 = new Db2Container() + .withUrlParam("sslConnection", "false") + .acceptLicense()) { + + db2.start(); + + String jdbcUrl = db2.getJdbcUrl(); + assertThat(jdbcUrl, containsString(":sslConnection=false;")); + } + } } 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 36a6461aa74..6c265bc6943 100644 --- a/modules/jdbc/src/main/java/org/testcontainers/containers/JdbcDatabaseContainer.java +++ b/modules/jdbc/src/main/java/org/testcontainers/containers/JdbcDatabaseContainer.java @@ -2,6 +2,7 @@ import com.github.dockerjava.api.command.InspectContainerResponse; import lombok.NonNull; +import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.NotNull; import org.testcontainers.containers.traits.LinkableContainer; import org.testcontainers.delegate.DatabaseDelegate; @@ -17,6 +18,8 @@ import java.util.Properties; import java.util.concurrent.Future; +import static java.util.stream.Collectors.joining; + /** * Base class for containers that expose a JDBC connection * @@ -28,6 +31,7 @@ public abstract class JdbcDatabaseContainer parameters = new HashMap<>(); + protected Map urlParameters = new HashMap<>(); private int startupTimeoutSeconds = 120; private int connectTimeoutSeconds = 120; @@ -82,7 +86,11 @@ public SELF withPassword(String password) { public SELF withDatabaseName(String dbName) { throw new UnsupportedOperationException(); + } + public SELF withUrlParam(String paramName, String paramValue) { + urlParameters.put(paramName, paramValue); + return self(); } /** @@ -223,6 +231,21 @@ protected String constructUrlForConnection(String queryString) { return getJdbcUrl() + queryString; } + protected String constructUrlParameters(String startCharacter, String delimiter) { + return constructUrlParameters(startCharacter, delimiter, StringUtils.EMPTY); + } + + protected String constructUrlParameters(String startCharacter, String delimiter, String endCharacter) { + String urlParameters = ""; + if (!this.urlParameters.isEmpty()) { + String additionalParameters = this.urlParameters.entrySet().stream() + .map(Object::toString) + .collect(joining(delimiter)); + urlParameters = startCharacter + additionalParameters + endCharacter; + } + return urlParameters; + } + protected void optionallyMapResourceParameterAsVolume(@NotNull String paramName, @NotNull String pathNameInContainer, @NotNull String defaultResource) { String resourceName = parameters.getOrDefault(paramName, defaultResource); diff --git a/modules/mariadb/src/main/java/org/testcontainers/containers/MariaDBContainer.java b/modules/mariadb/src/main/java/org/testcontainers/containers/MariaDBContainer.java index 1b4cb3344e4..04d745a9efe 100644 --- a/modules/mariadb/src/main/java/org/testcontainers/containers/MariaDBContainer.java +++ b/modules/mariadb/src/main/java/org/testcontainers/containers/MariaDBContainer.java @@ -60,7 +60,9 @@ public String getDriverClassName() { @Override public String getJdbcUrl() { - return "jdbc:mariadb://" + getHost() + ":" + getMappedPort(MARIADB_PORT) + "/" + databaseName; + String additionalUrlParams = constructUrlParameters("?", "&"); + return "jdbc:mariadb://" + getHost() + ":" + getMappedPort(MARIADB_PORT) + + "/" + databaseName + additionalUrlParams; } @Override diff --git a/modules/mariadb/src/test/java/org/testcontainers/junit/mariadb/SimpleMariaDBTest.java b/modules/mariadb/src/test/java/org/testcontainers/junit/mariadb/SimpleMariaDBTest.java index 646fbb40947..af503ce424b 100644 --- a/modules/mariadb/src/test/java/org/testcontainers/junit/mariadb/SimpleMariaDBTest.java +++ b/modules/mariadb/src/test/java/org/testcontainers/junit/mariadb/SimpleMariaDBTest.java @@ -8,6 +8,8 @@ import java.sql.ResultSet; import java.sql.SQLException; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertThat; import static org.junit.Assume.assumeFalse; import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals; import static org.rnorth.visibleassertions.VisibleAssertions.assertTrue; @@ -68,4 +70,22 @@ public void testMariaDBWithCommandOverride() throws SQLException { assertEquals("Auto increment increment should be overriden by command line", "10", result); } } + + @Test + public void testWithAdditionalUrlParamInJdbcUrl() { + MariaDBContainer mariaDBContainer = (MariaDBContainer) new MariaDBContainer() + .withUrlParam("connectTimeout", "40000") + .withUrlParam("rewriteBatchedStatements", "true"); + + try { + mariaDBContainer.start(); + String jdbcUrl = mariaDBContainer.getJdbcUrl(); + assertThat(jdbcUrl, containsString("?")); + assertThat(jdbcUrl, containsString("&")); + assertThat(jdbcUrl, containsString("rewriteBatchedStatements=true")); + assertThat(jdbcUrl, containsString("connectTimeout=40000")); + } finally { + mariaDBContainer.stop(); + } + } } diff --git a/modules/mssqlserver/src/main/java/org/testcontainers/containers/MSSQLServerContainer.java b/modules/mssqlserver/src/main/java/org/testcontainers/containers/MSSQLServerContainer.java index 631228aeef2..b500e6fddf0 100644 --- a/modules/mssqlserver/src/main/java/org/testcontainers/containers/MSSQLServerContainer.java +++ b/modules/mssqlserver/src/main/java/org/testcontainers/containers/MSSQLServerContainer.java @@ -62,7 +62,8 @@ public String getDriverClassName() { @Override public String getJdbcUrl() { - return "jdbc:sqlserver://" + getHost() + ":" + getMappedPort(MS_SQL_SERVER_PORT); + String additionalUrlParams = constructUrlParameters(";", ";"); + return "jdbc:sqlserver://" + getHost() + ":" + getMappedPort(MS_SQL_SERVER_PORT) + additionalUrlParams; } @Override diff --git a/modules/mssqlserver/src/test/java/org/testcontainers/junit/mssqlserver/SimpleMSSQLServerTest.java b/modules/mssqlserver/src/test/java/org/testcontainers/junit/mssqlserver/SimpleMSSQLServerTest.java index c1699d2473a..71e12a6d864 100644 --- a/modules/mssqlserver/src/test/java/org/testcontainers/junit/mssqlserver/SimpleMSSQLServerTest.java +++ b/modules/mssqlserver/src/test/java/org/testcontainers/junit/mssqlserver/SimpleMSSQLServerTest.java @@ -9,6 +9,8 @@ import java.sql.SQLException; import java.sql.Statement; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertThat; import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals; public class SimpleMSSQLServerTest extends AbstractContainerDatabaseTest { @@ -24,6 +26,20 @@ public void testSimple() throws SQLException { } } + @Test + public void testWithAdditionalUrlParamInJdbcUrl() { + try (MSSQLServerContainer mssqlServer = new MSSQLServerContainer<>() + .withUrlParam("integratedSecurity", "false") + .withUrlParam("applicationName", "MyApp")) { + + mssqlServer.start(); + + String jdbcUrl = mssqlServer.getJdbcUrl(); + assertThat(jdbcUrl, containsString(";integratedSecurity=false;applicationName=MyApp")); + } + } + + @Test public void testSetupDatabase() throws SQLException { try (MSSQLServerContainer mssqlServer = new MSSQLServerContainer<>()) { diff --git a/modules/mysql/src/main/java/org/testcontainers/containers/MySQLContainer.java b/modules/mysql/src/main/java/org/testcontainers/containers/MySQLContainer.java index 79d29234b99..1ef574cbd5e 100644 --- a/modules/mysql/src/main/java/org/testcontainers/containers/MySQLContainer.java +++ b/modules/mysql/src/main/java/org/testcontainers/containers/MySQLContainer.java @@ -5,6 +5,8 @@ import java.util.HashSet; import java.util.Set; +import static java.util.stream.Collectors.joining; + /** * @author richardnorth */ @@ -70,7 +72,9 @@ public String getDriverClassName() { @Override public String getJdbcUrl() { - return "jdbc:mysql://" + getHost() + ":" + getMappedPort(MYSQL_PORT) + "/" + databaseName; + String additionalUrlParams = constructUrlParameters("?", "&"); + return "jdbc:mysql://" + getHost() + ":" + getMappedPort(MYSQL_PORT) + + "/" + databaseName + additionalUrlParams; } @Override diff --git a/modules/mysql/src/test/java/org/testcontainers/junit/mysql/SimpleMySQLTest.java b/modules/mysql/src/test/java/org/testcontainers/junit/mysql/SimpleMySQLTest.java index ee6c3ab6993..6d6f8d26453 100644 --- a/modules/mysql/src/test/java/org/testcontainers/junit/mysql/SimpleMySQLTest.java +++ b/modules/mysql/src/test/java/org/testcontainers/junit/mysql/SimpleMySQLTest.java @@ -9,9 +9,17 @@ import org.testcontainers.containers.output.Slf4jLogConsumer; import org.testcontainers.db.AbstractContainerDatabaseTest; +import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; - +import java.sql.Statement; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals; @@ -142,4 +150,77 @@ public void testEmptyPasswordWithRootUser() throws SQLException { assertEquals("A basic SELECT query succeeds", 1, resultSetInt); } } + + @Test + public void testWithAdditionalUrlParamTimeZone() throws SQLException { + MySQLContainer mysql = (MySQLContainer) new MySQLContainer() + .withUrlParam("serverTimezone", "Europe/Zurich") + .withEnv("TZ", "Europe/Zurich") + .withLogConsumer(new Slf4jLogConsumer(logger)); + mysql.start(); + + try(Connection connection = mysql.createConnection("")) { + Statement statement = connection.createStatement(); + statement.execute("SELECT NOW();"); + try (ResultSet resultSet = statement.getResultSet()) { + resultSet.next(); + + // checking that the time_zone MySQL is Europe/Zurich + LocalDateTime localDateTime = resultSet.getObject(1, LocalDateTime.class); + ZonedDateTime actualDateTime = localDateTime.atZone(ZoneId.of("Europe/Zurich")) + .truncatedTo(ChronoUnit.MINUTES); + ZonedDateTime expectedDateTime = ZonedDateTime.now(ZoneId.of("Europe/Zurich")) + .truncatedTo(ChronoUnit.MINUTES); + + String message = String.format("MySQL time zone is not Europe/Zurich. MySQL date:%s, current date:%s", + actualDateTime, expectedDateTime); + assertTrue(message, actualDateTime.equals(expectedDateTime)); + } + } finally { + mysql.stop(); + } + } + + @Test + public void testWithAdditionalUrlParamMultiQueries() throws SQLException { + MySQLContainer mysql = (MySQLContainer) new MySQLContainer() + .withUrlParam("allowMultiQueries", "true") + .withLogConsumer(new Slf4jLogConsumer(logger)); + mysql.start(); + + try(Connection connection = mysql.createConnection("")) { + Statement statement = connection.createStatement(); + String multiQuery = "DROP TABLE IF EXISTS bar; " + + "CREATE TABLE bar (foo VARCHAR(20)); " + + "INSERT INTO bar (foo) VALUES ('hello world');"; + statement.execute(multiQuery); + statement.execute("SELECT foo FROM bar;"); + try(ResultSet resultSet = statement.getResultSet()) { + resultSet.next(); + String firstColumnValue = resultSet.getString(1); + assertEquals("Value from bar should equal real value", "hello world", firstColumnValue); + } + } finally { + mysql.stop(); + } + } + + @Test + public void testWithAdditionalUrlParamInJdbcUrl() { + MySQLContainer mysql = (MySQLContainer) new MySQLContainer() + .withUrlParam("allowMultiQueries", "true") + .withUrlParam("rewriteBatchedStatements", "true") + .withLogConsumer(new Slf4jLogConsumer(logger)); + + try { + mysql.start(); + String jdbcUrl = mysql.getJdbcUrl(); + assertThat(jdbcUrl, containsString("?")); + assertThat(jdbcUrl, containsString("&")); + assertThat(jdbcUrl, containsString("rewriteBatchedStatements=true")); + assertThat(jdbcUrl, containsString("allowMultiQueries=true")); + } finally { + mysql.stop(); + } + } } diff --git a/modules/oracle-xe/src/main/java/org/testcontainers/containers/OracleContainer.java b/modules/oracle-xe/src/main/java/org/testcontainers/containers/OracleContainer.java index 60603a7e222..d263a141d4f 100644 --- a/modules/oracle-xe/src/main/java/org/testcontainers/containers/OracleContainer.java +++ b/modules/oracle-xe/src/main/java/org/testcontainers/containers/OracleContainer.java @@ -89,6 +89,11 @@ public OracleContainer withPassword(String password) { return self(); } + @Override + public OracleContainer withUrlParam(String paramName, String paramValue) { + throw new UnsupportedOperationException("The OracleDb does not support this"); + } + @SuppressWarnings("SameReturnValue") public String getSid() { return "xe"; diff --git a/modules/postgresql/src/main/java/org/testcontainers/containers/PostgreSQLContainer.java b/modules/postgresql/src/main/java/org/testcontainers/containers/PostgreSQLContainer.java index 8634f9d70fa..6ba7390878e 100644 --- a/modules/postgresql/src/main/java/org/testcontainers/containers/PostgreSQLContainer.java +++ b/modules/postgresql/src/main/java/org/testcontainers/containers/PostgreSQLContainer.java @@ -55,6 +55,8 @@ protected Set getLivenessCheckPorts() { @Override protected void configure() { + // Disable Postgres driver use of java.util.logging to reduce noise at startup time + withUrlParam("loggerLevel", "OFF"); addEnv("POSTGRES_DB", databaseName); addEnv("POSTGRES_USER", username); addEnv("POSTGRES_PASSWORD", password); @@ -67,25 +69,9 @@ public String getDriverClassName() { @Override public String getJdbcUrl() { - // Disable Postgres driver use of java.util.logging to reduce noise at startup time - return format("jdbc:postgresql://%s:%d/%s?loggerLevel=OFF", getHost(), getMappedPort(POSTGRESQL_PORT), databaseName); - } - - @Override - protected String constructUrlForConnection(String queryString) { - String baseUrl = getJdbcUrl(); - - if ("".equals(queryString)) { - return baseUrl; - } - - if (!queryString.startsWith("?")) { - throw new IllegalArgumentException("The '?' character must be included"); - } - - return baseUrl.contains("?") - ? baseUrl + QUERY_PARAM_SEPARATOR + queryString.substring(1) - : baseUrl + queryString; + String additionalUrlParams = constructUrlParameters("?", "&"); + return "jdbc:postgresql://" + getContainerIpAddress() + ":" + getMappedPort(POSTGRESQL_PORT) + + "/" + databaseName + additionalUrlParams; } @Override diff --git a/modules/postgresql/src/test/java/org/testcontainers/containers/PostgreSQLConnectionURLTest.java b/modules/postgresql/src/test/java/org/testcontainers/containers/PostgreSQLConnectionURLTest.java index 05f6ea204a8..c0ba7e40d44 100644 --- a/modules/postgresql/src/test/java/org/testcontainers/containers/PostgreSQLConnectionURLTest.java +++ b/modules/postgresql/src/test/java/org/testcontainers/containers/PostgreSQLConnectionURLTest.java @@ -15,7 +15,7 @@ public void shouldCorrectlyAppendQueryString() { String connectionUrl = postgres.constructUrlForConnection("?stringtype=unspecified&stringtype=unspecified"); String queryString = connectionUrl.substring(connectionUrl.indexOf('?')); - assertTrue("Query String contains expected params", queryString.contains("&stringtype=unspecified&stringtype=unspecified")); + assertTrue("Query String contains expected params", queryString.contains("?stringtype=unspecified&stringtype=unspecified")); assertEquals("Query String starts with '?'", 0, queryString.indexOf('?')); assertFalse("Query String does not contain extra '?'", queryString.substring(1).contains("?")); } diff --git a/modules/postgresql/src/test/java/org/testcontainers/junit/postgresql/SimplePostgreSQLTest.java b/modules/postgresql/src/test/java/org/testcontainers/junit/postgresql/SimplePostgreSQLTest.java index 9c97f51e64e..36ece03b604 100644 --- a/modules/postgresql/src/test/java/org/testcontainers/junit/postgresql/SimplePostgreSQLTest.java +++ b/modules/postgresql/src/test/java/org/testcontainers/junit/postgresql/SimplePostgreSQLTest.java @@ -9,6 +9,8 @@ import java.util.logging.Level; import java.util.logging.LogManager; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertThat; import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals; import static org.rnorth.visibleassertions.VisibleAssertions.assertNotEquals; @@ -63,4 +65,17 @@ public void testExplicitInitScript() throws SQLException { assertEquals("Value from init script should equal real value", "hello world", firstColumnValue); } } + + @Test + public void testWithAdditionalUrlParamInJdbcUrl() { + try (PostgreSQLContainer postgres = new PostgreSQLContainer<>() + .withUrlParam("charSet", "UNICODE")) { + + postgres.start(); + String jdbcUrl = postgres.getJdbcUrl(); + assertThat(jdbcUrl, containsString("?")); + assertThat(jdbcUrl, containsString("&")); + assertThat(jdbcUrl, containsString("charSet=UNICODE")); + } + } }