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

Add R2DBC support #2545

Merged
merged 18 commits into from
Apr 12, 2020
Merged
Show file tree
Hide file tree
Changes from 7 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
9 changes: 9 additions & 0 deletions modules/mariadb/build.gradle
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
description = "Testcontainers :: JDBC :: MariaDB"

dependencies {
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
compileOnly 'com.google.auto.service:auto-service:1.0-rc6'

compile project(':jdbc')

compileOnly project(':r2dbc')
compileOnly 'org.mariadb:r2dbc-mariadb:0.8.1-alpha1'

testCompile 'org.mariadb.jdbc:mariadb-java-client:2.5.4'
testCompile 'com.zaxxer:HikariCP-java6:2.3.13'
testCompile 'commons-dbutils:commons-dbutils:1.7'
testCompile 'org.apache.tomcat:tomcat-jdbc:9.0.31'
testCompile 'org.vibur:vibur-dbcp:25.0'

testCompile project(':r2dbc')
testCompile 'org.mariadb:r2dbc-mariadb:0.8.1-alpha1'
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class MariaDBContainer<SELF extends MariaDBContainer<SELF>> extends JdbcD
public static final String IMAGE = "mariadb";
public static final String DEFAULT_TAG = "10.3.6";

private static final Integer MARIADB_PORT = 3306;
static final Integer MARIADB_PORT = 3306;
private String databaseName = "test";
private String username = "test";
private String password = "test";
Expand Down Expand Up @@ -83,7 +83,7 @@ public SELF withConfigurationOverride(String s) {
parameters.put(MY_CNF_CONFIG_OVERRIDE_PARAM_NAME, s);
return self();
}

@Override
public SELF withDatabaseName(final String databaseName) {
this.databaseName = databaseName;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.testcontainers.containers;

import io.r2dbc.spi.ConnectionFactoryOptions;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Delegate;
import org.testcontainers.lifecycle.Startable;
import org.testcontainers.r2dbc.R2DBCDatabaseContainer;

@RequiredArgsConstructor
public class MariaDBR2DBCDatabaseContainer implements R2DBCDatabaseContainer {

@Delegate(types = Startable.class)
private final MariaDBContainer<?> container;

public static ConnectionFactoryOptions getOptions(MariaDBContainer<?> container) {
ConnectionFactoryOptions options = ConnectionFactoryOptions.builder()
.option(ConnectionFactoryOptions.DRIVER, MariaDBR2DBCDatabaseContainerProvider.DRIVER)
.build();

return new MariaDBR2DBCDatabaseContainer(container).configure(options);
}

@Override
public ConnectionFactoryOptions configure(ConnectionFactoryOptions options) {
return options.mutate()
.option(ConnectionFactoryOptions.HOST, container.getContainerIpAddress())
.option(ConnectionFactoryOptions.PORT, container.getMappedPort(MariaDBContainer.MARIADB_PORT))
.option(ConnectionFactoryOptions.DATABASE, container.getDatabaseName())
.option(ConnectionFactoryOptions.USER, container.getUsername())
.option(ConnectionFactoryOptions.PASSWORD, container.getPassword())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.testcontainers.containers;

import com.google.auto.service.AutoService;
import io.r2dbc.spi.ConnectionFactoryOptions;
import org.mariadb.r2dbc.MariadbConnectionFactoryProvider;
import org.testcontainers.r2dbc.R2DBCDatabaseContainer;
import org.testcontainers.r2dbc.R2DBCDatabaseContainerProvider;

@AutoService(R2DBCDatabaseContainerProvider.class)
Copy link
Member

Choose a reason for hiding this comment

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

Woah, TIL about @AutoService. I like it.

For the sake of consistency, we should probably propagate this out to everywhere else where we've currently hand-written a service descriptor file. This could be a subsequent PR, though.

public class MariaDBR2DBCDatabaseContainerProvider implements R2DBCDatabaseContainerProvider {

static final String DRIVER = MariadbConnectionFactoryProvider.MARIADB_DRIVER;

@Override
public boolean supports(ConnectionFactoryOptions options) {
return DRIVER.equals(options.getRequiredValue(ConnectionFactoryOptions.DRIVER));
}

@Override
public R2DBCDatabaseContainer createContainer(ConnectionFactoryOptions options) {
MariaDBContainer<?> container = new MariaDBContainer<>(options.getRequiredValue(IMAGE_OPTION))
.withDatabaseName(options.getRequiredValue(ConnectionFactoryOptions.DATABASE));

if (Boolean.TRUE.equals(options.getValue(REUSABLE_OPTION))) {
container.withReuse(true);
}
return new MariaDBR2DBCDatabaseContainer(container);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.testcontainers.containers;

import io.r2dbc.spi.Closeable;
import io.r2dbc.spi.Connection;
import io.r2dbc.spi.ConnectionFactories;
import io.r2dbc.spi.ConnectionFactory;
import io.r2dbc.spi.Result;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import static org.junit.Assert.*;

public class MariaDBR2DBCDatabaseContainerTest {
Copy link
Member

Choose a reason for hiding this comment

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

Just wondering if we should prefer an approach similar to our jdbc-test module, that runs the same test in a parameterized way?

Copy link
Member Author

Choose a reason for hiding this comment

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

@rnorth wanted to change it to "per module" for better parallelization, see #2520

Copy link
Member

Choose a reason for hiding this comment

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

Ah yes, I was assuming something like this actually 😬
Still, we could maybe extract an abstract base test class or something similar in a later change, to reduced code duplication.

Copy link
Member

Choose a reason for hiding this comment

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

Yep, we can split it per-module and have some common base test classes to keep things neat. That's how I'd started with my draft splitting of jdbc-test, and works nicely.


@Test
public void testGetOptions() {
try (MariaDBContainer<?> container = new MariaDBContainer<>()) {
container.start();

int result = Flux
.usingWhen(
Mono.just(
ConnectionFactories.get(
MariaDBR2DBCDatabaseContainer.getOptions(container)
)
),
connectionFactory -> {
return Flux
.usingWhen(
connectionFactory.create(),
connection -> connection.createStatement("SELECT 42").execute(),
Connection::close
)
.flatMap(it -> it.map((row, meta) -> (Integer) row.get(0)));
},
it -> ((Closeable) it).close()
)
.blockFirst();

assertEquals(42, result);
}
}

@Test
public void testUrlSupport() {
String url = "r2dbc:tc:mariadb:///db?TC_IMAGE=mysql:5.7.22";
ConnectionFactory connectionFactory = ConnectionFactories.get(url);
try {
int updated = Flux
.usingWhen(
connectionFactory.create(),
connection -> {
return Mono
.from(connection.createStatement("CREATE TABLE test(id integer PRIMARY KEY)").execute())
.thenMany(connection.createStatement("INSERT INTO test(id) VALUES(123)").execute())
.flatMap(Result::getRowsUpdated);
},
Connection::close
)
.blockFirst();

assertEquals(updated, 1);
} finally {
Mono.from(((Closeable) connectionFactory).close()).block();
}
}

}
16 changes: 16 additions & 0 deletions modules/mariadb/src/test/resources/logback-test.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<configuration>

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>
</encoder>
</appender>

<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>

<logger name="org.testcontainers" level="DEBUG"/>
</configuration>
12 changes: 12 additions & 0 deletions modules/mssqlserver/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
description = "Testcontainers :: MS SQL Server"

dependencies {
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
compileOnly 'com.google.auto.service:auto-service:1.0-rc6'

compile project(':jdbc')

compileOnly project(':r2dbc')
compileOnly 'io.r2dbc:r2dbc-mssql:0.8.1.RELEASE'

testCompile project(':r2dbc')
testCompile 'io.r2dbc:r2dbc-mssql:0.8.1.RELEASE'

// MSSQL's wait strategy requires the JDBC driver
testCompile 'com.microsoft.sqlserver:mssql-jdbc:6.1.0.jre8'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.testcontainers.containers;

import io.r2dbc.spi.ConnectionFactoryOptions;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Delegate;
import org.testcontainers.lifecycle.Startable;
import org.testcontainers.r2dbc.R2DBCDatabaseContainer;

@RequiredArgsConstructor
public class MSSQLR2DBCDatabaseContainer implements R2DBCDatabaseContainer {

@Delegate(types = Startable.class)
private final MSSQLServerContainer<?> container;

public static ConnectionFactoryOptions getOptions(MSSQLServerContainer<?> container) {
ConnectionFactoryOptions options = ConnectionFactoryOptions.builder()
.option(ConnectionFactoryOptions.DRIVER, MSSQLR2DBCDatabaseContainerProvider.DRIVER)
.build();

return new MSSQLR2DBCDatabaseContainer(container).configure(options);
}

@Override
public ConnectionFactoryOptions configure(ConnectionFactoryOptions options) {
return options.mutate()
.option(ConnectionFactoryOptions.HOST, container.getContainerIpAddress())
.option(ConnectionFactoryOptions.PORT, container.getMappedPort(MSSQLServerContainer.MS_SQL_SERVER_PORT))
// TODO enable if/when MSSQLServerContainer adds support for customizing the DB name
// .option(ConnectionFactoryOptions.DATABASE, container.getDatabasseName())
.option(ConnectionFactoryOptions.USER, container.getUsername())
.option(ConnectionFactoryOptions.PASSWORD, container.getPassword())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.testcontainers.containers;

import com.google.auto.service.AutoService;
import io.r2dbc.mssql.MssqlConnectionFactoryProvider;
import io.r2dbc.spi.ConnectionFactoryOptions;
import org.testcontainers.r2dbc.R2DBCDatabaseContainer;
import org.testcontainers.r2dbc.R2DBCDatabaseContainerProvider;

@AutoService(R2DBCDatabaseContainerProvider.class)
public class MSSQLR2DBCDatabaseContainerProvider implements R2DBCDatabaseContainerProvider {

static final String DRIVER = MssqlConnectionFactoryProvider.MSSQL_DRIVER;

@Override
public boolean supports(ConnectionFactoryOptions options) {
return DRIVER.equals(options.getRequiredValue(ConnectionFactoryOptions.DRIVER));
}

@Override
public R2DBCDatabaseContainer createContainer(ConnectionFactoryOptions options) {
MSSQLServerContainer<?> container = new MSSQLServerContainer<>(options.getRequiredValue(IMAGE_OPTION));

if (Boolean.TRUE.equals(options.getValue(REUSABLE_OPTION))) {
container.withReuse(true);
}
return new MSSQLR2DBCDatabaseContainer(container);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package org.testcontainers.containers;

import io.r2dbc.spi.Closeable;
import io.r2dbc.spi.Connection;
import io.r2dbc.spi.ConnectionFactories;
import io.r2dbc.spi.ConnectionFactory;
import io.r2dbc.spi.Result;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import static org.junit.Assert.*;

public class MSSQLR2DBCDatabaseContainerTest {

@Test
public void testGetOptions() {
try (MSSQLServerContainer<?> container = new MSSQLServerContainer<>()) {
container.start();

ConnectionFactory connectionFactory = ConnectionFactories.get(
MSSQLR2DBCDatabaseContainer.getOptions(container)
);

int result = Flux
.usingWhen(
connectionFactory.create(),
connection -> connection.createStatement("SELECT 42").execute(),
Connection::close
)
.flatMap(it -> it.map((row, meta) -> (Integer) row.get(0)))
.blockFirst();

assertEquals(42, result);
}
}

@Test
public void testUrlSupport() {
String url = "r2dbc:tc:sqlserver:///?TC_IMAGE=mcr.microsoft.com%2Fmssql%2Fserver%3A2017-CU12";
ConnectionFactory connectionFactory = ConnectionFactories.get(url);
try {
int updated = Flux
.usingWhen(
connectionFactory.create(),
connection -> {
return Mono
.from(connection.createStatement("CREATE DATABASE [test];").execute())
.thenMany(connection.createStatement("CREATE TABLE test(id integer PRIMARY KEY)").execute())
.thenMany(connection.createStatement("INSERT INTO test(id) VALUES(123)").execute())
.flatMap(Result::getRowsUpdated);
},
Connection::close
)
.blockFirst();

assertEquals(updated, 1);
} finally {
Mono.from(((Closeable) connectionFactory).close()).block();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mcr.microsoft.com/mssql/server:2017-CU12
16 changes: 16 additions & 0 deletions modules/mssqlserver/src/test/resources/logback-test.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<configuration>

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>
</encoder>
</appender>

<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>

<logger name="org.testcontainers" level="DEBUG"/>
</configuration>
9 changes: 9 additions & 0 deletions modules/mysql/build.gradle
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
description = "Testcontainers :: JDBC :: MySQL"

dependencies {
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
compileOnly 'com.google.auto.service:auto-service:1.0-rc6'

compile project(':jdbc')

compileOnly project(':r2dbc')
compileOnly 'dev.miku:r2dbc-mysql:0.8.1.RELEASE'

testCompile 'mysql:mysql-connector-java:8.0.19'
testCompile 'com.zaxxer:HikariCP-java6:2.3.13'
testCompile 'org.apache.tomcat:tomcat-jdbc:9.0.31'
testCompile 'commons-dbutils:commons-dbutils:1.7'
testCompile 'org.vibur:vibur-dbcp:25.0'

testCompile project(':r2dbc')
testCompile 'dev.miku:r2dbc-mysql:0.8.1.RELEASE'
}
Loading