Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow setting database name and user when using JDBC URL #594

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,9 @@ node_modules/

.gradle/
build/

# Eclipse IDE files
**/.project
**/.classpath
**/.settings
**/bin/
8 changes: 6 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ sudo: required
services:
- docker

before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
cache:
directories:
- '$HOME/.gradle'
- $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/

install:
- ./gradlew build -x check
Expand Down Expand Up @@ -40,7 +44,7 @@ jobs:
-v "$(pwd)":"$(pwd)" \
-w "$(pwd)" \
openjdk:8-jdk-alpine \
./gradlew testcontainers:test --tests '*GenericContainerRuleTest'
./gradlew --no-daemon testcontainers:test --tests '*GenericContainerRuleTest'

- stage: deploy
sudo: false
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ All notable changes to this project will be documented in this file.
### Changed
- Abstracted and changed database init script functionality to support use of SQL-like scripts with non-JDBC connections. ([\#551](https://github.com/testcontainers/testcontainers-java/pull/551))
- Added `JdbcDatabaseContainer(Future)` constructor. ([\#543](https://github.com/testcontainers/testcontainers-java/issues/543))
- Mark DockerMachineClientProviderStrategy as not persistable ([\#593](https://github.com/testcontainers/testcontainers-java/pull/593))
- Enhancements and Fixes for JDBC URL usage to create Containers ([\#594](https://github.com/testcontainers/testcontainers-java/pull/594))
- Extracted JDBC URL manipulations to a separate class - `ConnectionUrl`.
- Added an overloaded method `JdbcDatabaseContainerProvider.newInstance(ConnectionUrl)`, with default implementation delegating to the existing `newInstance(tag)` method. (Relates to [\#566](https://github.com/testcontainers/testcontainers-java/issues/566))
- Added an implementation of `MySQLContainerProvider.newInstance(ConnectionUrl)` that uses Database Name, User, and Password from JDBC URL while creating new MySQL Container. (Fixes [\#566](https://github.com/testcontainers/testcontainers-java/issues/566) for MySQL Container)
- Fixed JDBC URL Regex Pattern to ensure all supported Database URL's are accepted (Fixes [\#596](https://github.com/testcontainers/testcontainers-java/issues/596))
- Filtered out TestContainer parameters (TC_*) from query string before passing to database (Fixes [\#345](https://github.com/testcontainers/testcontainers-java/issues/345))

## [1.6.0] - 2018-01-28

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ protected boolean isApplicable() {
return true;
}

protected boolean isPersistable() {
return true;
}

/**
* @return highest to lowest priority value
*/
Expand Down Expand Up @@ -93,7 +97,10 @@ public static DockerClientProviderStrategy getFirstValidStrategy(List<DockerClie
LOGGER.warn("Can't instantiate a strategy from {}", it, e);
return Stream.empty();
}
}),
})

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the formatting in this Stream.concat... block just 'idea' being weird? doesnt seem to follow the 4 space indents everywhere else.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class is not modified and related for this PR, probably upstream has changed and so you see the difference. I would leave it as-is for now.

Copy link
Member

@rnorth rnorth Mar 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please let's see if we can tackle this without regular expressions separately. I'd be very happy if we could do that and make this code easier to read.
Response to wrong comment

// Ignore persisted strategy if it's not persistable anymore
.filter(DockerClientProviderStrategy::isPersistable)
.peek(strategy -> LOGGER.info("Loaded {} from ~/.testcontainers.properties, will try it first", strategy.getClass().getName())),
strategies
.stream()
.filter(DockerClientProviderStrategy::isApplicable)
Expand All @@ -104,7 +111,9 @@ public static DockerClientProviderStrategy getFirstValidStrategy(List<DockerClie
strategy.test();
LOGGER.info("Found Docker environment with {}", strategy.getDescription());

TestcontainersConfiguration.getInstance().updateGlobalConfig("docker.client.strategy", strategy.getClass().getName());
if (strategy.isPersistable()) {
TestcontainersConfiguration.getInstance().updateGlobalConfig("docker.client.strategy", strategy.getClass().getName());
}

return Stream.of(strategy);
} catch (Exception | ExceptionInInitializerError | NoClassDefFoundError e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ protected boolean isApplicable() {
return DockerMachineClient.instance().isInstalled();
}

@Override
protected boolean isPersistable() {
return false;
}

@Override
protected int getPriority() {
return ProxiedUnixSocketClientProviderStrategy.PRIORITY - 10;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,26 +35,26 @@ public class JDBCDriverTest {
@Parameterized.Parameters(name = "{index} - {0}")
public static Iterable<Object[]> data() {
return asList(
new Object[][]{
{"jdbc:tc:mysql:5.5.43://hostname/databasename", false, false, false},
{"jdbc:tc:mysql://hostname/databasename?TC_INITSCRIPT=somepath/init_mysql.sql", true, false, false},
{"jdbc:tc:mysql://hostname/databasename?TC_INITFUNCTION=org.testcontainers.jdbc.JDBCDriverTest::sampleInitFunction", true, false, false},
{"jdbc:tc:mysql://hostname/databasename?useUnicode=yes&characterEncoding=utf8", false, true, false},
{"jdbc:tc:mysql://hostname/databasename", false, false, false},
{"jdbc:tc:mysql://hostname/databasename?useSSL=false", false, false, false},
{"jdbc:tc:postgresql://hostname/databasename", false, false, false},
{"jdbc:tc:mysql:5.6://hostname/databasename?TC_MY_CNF=somepath/mysql_conf_override", false, false, true},
});
new Object[][]{
{"jdbc:tc:mysql:5.5.43://hostname/databasename", false, false, false},
{"jdbc:tc:mysql://hostname/databasename?user=someuser&password=somepwd&TC_INITSCRIPT=somepath/init_mysql.sql", true, false, false},
{"jdbc:tc:mysql://hostname/databasename?user=someuser&password=somepwd&TC_INITFUNCTION=org.testcontainers.jdbc.JDBCDriverTest::sampleInitFunction", true, false, false},
{"jdbc:tc:mysql://hostname/databasename?useUnicode=yes&characterEncoding=utf8", false, true, false},
{"jdbc:tc:mysql://hostname/databasename", false, false, false},
{"jdbc:tc:mysql://hostname/databasename?useSSL=false", false, false, false},
{"jdbc:tc:postgresql://hostname/databasename", false, false, false},
{"jdbc:tc:mysql:5.6://hostname/databasename?TC_MY_CNF=somepath/mysql_conf_override", false, false, true},
});
}

public static void sampleInitFunction(Connection connection) throws SQLException {
connection.createStatement().execute("CREATE TABLE bar (\n" +
" foo VARCHAR(255)\n" +
");");
" foo VARCHAR(255)\n" +
");");
connection.createStatement().execute("INSERT INTO bar (foo) VALUES ('hello world');");
connection.createStatement().execute("CREATE TABLE my_counter (\n" +
" n INT\n" +
");");
" n INT\n" +
");");
}

@AfterClass
Expand All @@ -71,6 +71,9 @@ public void test() throws SQLException {
}

if (performTestForCharacterSet) {
//Called twice to ensure that the query string parameters are used when
//connections are created from cached containers.
performSimpleTestWithCharacterSet(jdbcUrl);
performSimpleTestWithCharacterSet(jdbcUrl);
}

Expand Down Expand Up @@ -101,24 +104,52 @@ private void performTestForScriptedSchema(String jdbcUrl) throws SQLException {
return true;
});

assertTrue("The database returned a record as expected", result);

}
}
result = new QueryRunner(dataSource).query("select CURRENT_USER()", rs -> {
rs.next();
String resultUser = rs.getString(1);
assertEquals("User from query param is created.", "someuser@%", resultUser);
return true;
});

private void performSimpleTestWithCharacterSet(String jdbcUrl) throws SQLException {
try (HikariDataSource dataSource = getDataSource(jdbcUrl, 1)) {
boolean result = new QueryRunner(dataSource).query("SHOW VARIABLES LIKE 'character\\_set\\_connection'", rs -> {
result = new QueryRunner(dataSource).query("SELECT DATABASE()", rs -> {
rs.next();
String resultSetInt = rs.getString(2);
assertEquals("Passing query parameters to set DB connection encoding is successful", "utf8", resultSetInt);
String resultDB = rs.getString(1);
assertEquals("Database name from URL String is used.", "databasename", resultDB);
return true;
});

assertTrue("The database returned a record as expected", result);

}
}

/**
* This method intentionally verifies encoding twice to ensure that the query string parameters are used when
* Connections are created from cached containers.
*
* @param jdbcUrl
* @throws SQLException
*/
private void performSimpleTestWithCharacterSet(String jdbcUrl) throws SQLException {
HikariDataSource datasource1 = verifyCharacterSet(jdbcUrl);
HikariDataSource datasource2 = verifyCharacterSet(jdbcUrl);
datasource1.close();
datasource2.close();
}

private HikariDataSource verifyCharacterSet(String jdbcUrl) throws SQLException {
HikariDataSource dataSource = getDataSource(jdbcUrl, 1);
boolean result = new QueryRunner(dataSource).query("SHOW VARIABLES LIKE 'character\\_set\\_connection'", rs -> {
rs.next();
String resultSetInt = rs.getString(2);
assertEquals("Passing query parameters to set DB connection encoding is successful", "utf8", resultSetInt);
return true;
});

assertTrue("The database returned a record as expected", result);
return dataSource;
}

private void performTestForCustomIniFile(final String jdbcUrl) throws SQLException {
assumeFalse(SystemUtils.IS_OS_WINDOWS);
try (HikariDataSource ds = getDataSource(jdbcUrl, 1)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.testcontainers.containers;

import java.util.concurrent.Future;

import lombok.NonNull;
import org.jetbrains.annotations.NotNull;
import org.rnorth.ducttape.ratelimits.RateLimiter;
Expand Down Expand Up @@ -29,9 +30,9 @@ public abstract class JdbcDatabaseContainer<SELF extends JdbcDatabaseContainer<S
protected Map<String, String> parameters = new HashMap<>();

private static final RateLimiter DB_CONNECT_RATE_LIMIT = RateLimiterBuilder.newBuilder()
.withRate(10, TimeUnit.SECONDS)
.withConstantThroughput()
.build();
.withRate(10, TimeUnit.SECONDS)
.withConstantThroughput()
.build();

public JdbcDatabaseContainer(@NonNull final String dockerImageName) {
super(dockerImageName);
Expand Down Expand Up @@ -133,9 +134,8 @@ public Driver getJdbcDriverInstance() {
/**
* Creates a connection to the underlying containerized database instance.
*
* @param queryString
* query string parameters that should be appended to the JDBC connection URL.
* The '?' character must be included
* @param queryString query string parameters that should be appended to the JDBC connection URL.
* The '?' character must be included
* @return a Connection
* @throws SQLException if there is a repeated failure to create the connection
*/
Expand All @@ -159,9 +159,8 @@ public Connection createConnection(String queryString) throws SQLException {
* This should be overridden if the JDBC URL and query string concatenation or URL string
* construction needs to be different to normal.
*
* @param queryString
* query string parameters that should be appended to the JDBC connection URL.
* The '?' character must be included
* @param queryString query string parameters that should be appended to the JDBC connection URL.
* The '?' character must be included
* @return a full JDBC URL including queryString
*/
protected String constructUrlForConnection(String queryString) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,32 @@
package org.testcontainers.containers;

import org.testcontainers.jdbc.ConnectionUrl;

/**
* Base class for classes that can provide a JDBC container.
*/
public abstract class JdbcDatabaseContainerProvider {

/**
* Tests if the specified database type is supported by this Container Provider. It should match to the base image name.
* @param databaseType {@link String}
* @return <code>true</code> when provider can handle this database type, else <code>false</code>.
*/
public abstract boolean supports(String databaseType);

/**
* Instantiate a new {@link JdbcDatabaseContainer} with specified image tag.
* @param tag
* @return Instance of {@link JdbcDatabaseContainer}
*/
public abstract JdbcDatabaseContainer newInstance(String tag);

/**
* Instantiate a new {@link JdbcDatabaseContainer} using information provided with {@link ConnectionUrl}.
* @param url {@link ConnectionUrl}
* @return Instance of {@link JdbcDatabaseContainer}
*/
public JdbcDatabaseContainer newInstance(ConnectionUrl url) {
return newInstance(url.getImageTag());
}
}