Skip to content

Commit 6c45a20

Browse files
authored
Add R2DBC support (#2545)
This PR adds support for [R2DBC](https://r2dbc.io). ## URL support The URL support is similar to the JDBC's, but: 1. Does not reuse containers by URL - it is unclear yet how to "hash" `ConnectionFactoryOptions` 1. The image tag must be explicitly provided via `TC_IMAGE_TAG` r2dbc option 1. TODO: daemon mode 1. TODO: alias support (see #4) ## Example usage The user **must add the `org.testcontainers:r2dbc` module** - unlike JDBC, we use `compileOnly` dependency on R2DBC, since it is a 3rd party dependency. Once both the `r2dbc` and database's module are added, one can use: ```java String url = "r2dbc:tc:postgresql:///db?TC_IMAGE_TAG=10-alpine"; ConnectionFactory connectionFactory = ConnectionFactories.get(url); ``` ## Programmatic access For those who start containers manually and want to obtain `ConnectionFactoryOptions` for already started containers, we provide a helper static method: ```java try (PostgreSQLContainer<?> container = new PostgreSQLContainer<>()) { container.start(); ConnectionFactory connectionFactory = ConnectionFactories.get( PostgreSQLR2DBCDatabaseContainer.getOptions(container) ); } ```
1 parent 84c8d1e commit 6c45a20

File tree

37 files changed

+1212
-113
lines changed

37 files changed

+1212
-113
lines changed

docs/modules/databases/index.md

Lines changed: 1 addition & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -10,115 +10,4 @@ You might want to use Testcontainers' database support:
1010
!!! note
1111
Of course, it's still important to have as few tests that hit the database as possible, and make good use of mocks for components higher up the stack.
1212

13-
You can obtain a temporary database in one of two ways:
14-
15-
* **JUnit @Rule/@ClassRule**: this mode starts a database inside a container before your tests and tears it down afterwards.
16-
* **Using a specially modified JDBC URL**: after making a very simple modification to your system's JDBC URL string, Testcontainers will provide a disposable stand-in database that can be used without requiring modification to your application code.
17-
18-
## Database container objects
19-
20-
Add a @Rule or @ClassRule to your test class, e.g.:
21-
22-
```java
23-
public class SimpleMySQLTest {
24-
@Rule
25-
public MySQLContainer mysql = new MySQLContainer();
26-
```
27-
28-
Now, in your test code (or a suitable setup method), you can obtain details necessary to connect to this database:
29-
30-
* `mysql.getJdbcUrl()` provides a JDBC URL your code can connect to
31-
* `mysql.getUsername()` provides the username your code should pass to the driver
32-
* `mysql.getPassword()` provides the password your code should pass to the driver
33-
34-
Note that if you use `@Rule`, you will be given an isolated container for each test method. If you use `@ClassRule`, you will get on isolated container for all the methods in the test class.
35-
36-
Examples/Tests:
37-
38-
* [MySQL](https://github.com/testcontainers/testcontainers-java/blob/master/modules/jdbc-test/src/test/java/org/testcontainers/junit/SimpleMySQLTest.java)
39-
* [PostgreSQL](https://github.com/testcontainers/testcontainers-java/blob/master/modules/jdbc-test/src/test/java/org/testcontainers/junit/SimplePostgreSQLTest.java)
40-
41-
## Database containers launched via JDBC URL scheme
42-
43-
As long as you have Testcontainers and the appropriate JDBC driver on your classpath, you can simply modify regular JDBC connection URLs to get a fresh containerized instance of the database each time your application starts up.
44-
45-
_N.B:_
46-
47-
* _TC needs to be on your application's classpath at runtime for this to work_
48-
* _For Spring Boot (Before version `2.3.0`) you need to specify the driver manually `spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver`_
49-
50-
**Original URL**: `jdbc:mysql:5.7.22://localhost:3306/databasename`
51-
52-
Insert `tc:` after `jdbc:` as follows. Note that the hostname, port and database name will be ignored; you can leave these as-is or set them to any value.
53-
54-
!!! note
55-
We will use `///` (host-less URIs) from now on to emphasis the unimportance of the `host:port` pair.
56-
From Testcontainers' perspective, `jdbc:mysql:5.7.22://localhost:3306/databasename` and `jdbc:mysql:5.7.22:///databasename` is the same URI.
57-
58-
### JDBC URL examples
59-
60-
#### Using Testcontainers with a fixed version
61-
62-
`jdbc:tc:mysql:5.6.23:///databasename`
63-
64-
#### Using PostgreSQL
65-
66-
`jdbc:tc:postgresql:9.6.8:///databasename`
67-
68-
### Using PostGIS
69-
70-
`jdbc:tc:postgis:9.6:///databasename`
71-
72-
### Using Presto
73-
74-
`jdbc:tc:presto:329://localhost/memory/default`
75-
76-
## Using a classpath init script
77-
78-
Testcontainers can run an init script after the database container is started, but before your code is given a connection to it. The script must be on the classpath, and is referenced as follows:
79-
80-
`jdbc:tc:mysql:5.7.22:///databasename?TC_INITSCRIPT=somepath/init_mysql.sql`
81-
82-
This is useful if you have a fixed script for setting up database schema, etc.
83-
84-
## Using an init script from a file
85-
86-
If the init script path is prefixed `file:`, it will be loaded from a file (relative to the working directory, which will usually be the project root).
87-
88-
`jdbc:tc:mysql:5.7.22:///databasename?TC_INITSCRIPT=file:src/main/resources/init_mysql.sql`
89-
90-
#### Using an init function
91-
92-
Instead of running a fixed script for DB setup, it may be useful to call a Java function that you define. This is intended to allow you to trigger database schema migration tools. To do this, add TC_INITFUNCTION to the URL as follows, passing a full path to the class name and method:
93-
94-
`jdbc:tc:mysql:5.7.22:///databasename?TC_INITFUNCTION=org.testcontainers.jdbc.JDBCDriverTest::sampleInitFunction`
95-
96-
The init function must be a public static method which takes a `java.sql.Connection` as its only parameter, e.g.
97-
```java
98-
public class JDBCDriverTest {
99-
public static void sampleInitFunction(Connection connection) throws SQLException {
100-
// e.g. run schema setup or Flyway/liquibase/etc DB migrations here...
101-
}
102-
...
103-
```
104-
105-
#### Running container in daemon mode
106-
107-
By default database container is being stopped as soon as last connection is closed. There are cases when you might need to start container and keep it running till you stop it explicitly or JVM is shutdown. To do this, add `TC_DAEMON` parameter to the URL as follows:
108-
109-
`jdbc:tc:mysql:5.7.22:///databasename?TC_DAEMON=true`
110-
111-
With this parameter database container will keep running even when there're no open connections.
112-
113-
114-
#### Running container with tmpfs options
115-
116-
Container can have `tmpfs` mounts for storing data in host memory. This is useful if you want to speed up your database tests. Be aware that the data will be lost when the container stops.
117-
118-
To pass this option to the container, add `TC_TMPFS` parameter to the URL as follows:
119-
120-
`jdbc:tc:postgresql:9.6.8:///databasename?TC_TMPFS=/testtmpfs:rw`
121-
122-
If you need more than one option, separate them by comma (e.g. `TC_TMPFS=key:value,key1:value1&other_parameters=foo`).
123-
124-
For more information about `tmpfs` mount, see [the official Docker documentation](https://docs.docker.com/storage/tmpfs/).
13+
See [JDBC](./jdbc.md) and [R2DBC](./r2dbc.md) for information on how to use Testcontainers with SQL-like databases.

docs/modules/databases/jdbc.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# JDBC support
2+
3+
You can obtain a temporary database in one of two ways:
4+
5+
* **Using a specially modified JDBC URL**: after making a very simple modification to your system's JDBC URL string, Testcontainers will provide a disposable stand-in database that can be used without requiring modification to your application code.
6+
* **JUnit @Rule/@ClassRule**: this mode starts a database inside a container before your tests and tears it down afterwards.
7+
8+
## Database containers launched via JDBC URL scheme
9+
10+
As long as you have Testcontainers and the appropriate JDBC driver on your classpath, you can simply modify regular JDBC connection URLs to get a fresh containerized instance of the database each time your application starts up.
11+
12+
_N.B:_
13+
14+
* _TC needs to be on your application's classpath at runtime for this to work_
15+
* _For Spring Boot (Before version `2.3.0`) you need to specify the driver manually `spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver`_
16+
17+
**Original URL**: `jdbc:mysql://localhost:3306/databasename`
18+
19+
Insert `tc:` after `jdbc:` as follows. Note that the hostname, port and database name will be ignored; you can leave these as-is or set them to any value.
20+
21+
!!! note
22+
We will use `///` (host-less URIs) from now on to emphasis the unimportance of the `host:port` pair.
23+
From Testcontainers' perspective, `jdbc:mysql:5.7.22://localhost:3306/databasename` and `jdbc:mysql:5.7.22:///databasename` is the same URI.
24+
25+
!!! warning
26+
If you're using the JDBC URL support, there is no need to instantiate an instance of the container - Testcontainers will do it automagically.
27+
28+
### JDBC URL examples
29+
30+
#### Using Testcontainers with a fixed version
31+
32+
`jdbc:tc:mysql:5.6.23:///databasename`
33+
34+
#### Using PostgreSQL
35+
36+
`jdbc:tc:postgresql:9.6.8:///databasename`
37+
38+
#### Using PostGIS
39+
40+
`jdbc:tc:postgis:9.6:///databasename`
41+
42+
#### Using Presto
43+
44+
`jdbc:tc:presto:329://localhost/memory/default`
45+
46+
### Using a classpath init script
47+
48+
Testcontainers can run an init script after the database container is started, but before your code is given a connection to it. The script must be on the classpath, and is referenced as follows:
49+
50+
`jdbc:tc:mysql:5.7.22:///databasename?TC_INITSCRIPT=somepath/init_mysql.sql`
51+
52+
This is useful if you have a fixed script for setting up database schema, etc.
53+
54+
### Using an init script from a file
55+
56+
If the init script path is prefixed `file:`, it will be loaded from a file (relative to the working directory, which will usually be the project root).
57+
58+
`jdbc:tc:mysql:5.7.22:///databasename?TC_INITSCRIPT=file:src/main/resources/init_mysql.sql`
59+
60+
### Using an init function
61+
62+
Instead of running a fixed script for DB setup, it may be useful to call a Java function that you define. This is intended to allow you to trigger database schema migration tools. To do this, add TC_INITFUNCTION to the URL as follows, passing a full path to the class name and method:
63+
64+
`jdbc:tc:mysql:5.7.22:///databasename?TC_INITFUNCTION=org.testcontainers.jdbc.JDBCDriverTest::sampleInitFunction`
65+
66+
The init function must be a public static method which takes a `java.sql.Connection` as its only parameter, e.g.
67+
```java
68+
public class JDBCDriverTest {
69+
public static void sampleInitFunction(Connection connection) throws SQLException {
70+
// e.g. run schema setup or Flyway/liquibase/etc DB migrations here...
71+
}
72+
...
73+
```
74+
75+
### Running container in daemon mode
76+
77+
By default database container is being stopped as soon as last connection is closed. There are cases when you might need to start container and keep it running till you stop it explicitly or JVM is shutdown. To do this, add `TC_DAEMON` parameter to the URL as follows:
78+
79+
`jdbc:tc:mysql:5.7.22:///databasename?TC_DAEMON=true`
80+
81+
With this parameter database container will keep running even when there're no open connections.
82+
83+
84+
### Running container with tmpfs options
85+
86+
Container can have `tmpfs` mounts for storing data in host memory. This is useful if you want to speed up your database tests. Be aware that the data will be lost when the container stops.
87+
88+
To pass this option to the container, add `TC_TMPFS` parameter to the URL as follows:
89+
90+
`jdbc:tc:postgresql:9.6.8:///databasename?TC_TMPFS=/testtmpfs:rw`
91+
92+
If you need more than one option, separate them by comma (e.g. `TC_TMPFS=key:value,key1:value1&other_parameters=foo`).
93+
94+
For more information about `tmpfs` mount, see [the official Docker documentation](https://docs.docker.com/storage/tmpfs/).
95+
96+
## Database container objects
97+
98+
In case you can't use the URL support, or need to fine-tune the container, you can instantiate it yourself.
99+
100+
Add a @Rule or @ClassRule to your test class, e.g.:
101+
102+
```java
103+
public class SimpleMySQLTest {
104+
@Rule
105+
public MySQLContainer mysql = new MySQLContainer();
106+
```
107+
108+
Now, in your test code (or a suitable setup method), you can obtain details necessary to connect to this database:
109+
110+
* `mysql.getJdbcUrl()` provides a JDBC URL your code can connect to
111+
* `mysql.getUsername()` provides the username your code should pass to the driver
112+
* `mysql.getPassword()` provides the password your code should pass to the driver
113+
114+
Note that if you use `@Rule`, you will be given an isolated container for each test method. If you use `@ClassRule`, you will get on isolated container for all the methods in the test class.
115+
116+
Examples/Tests:
117+
118+
* [MySQL](https://github.com/testcontainers/testcontainers-java/blob/master/modules/jdbc-test/src/test/java/org/testcontainers/junit/SimpleMySQLTest.java)
119+
* [PostgreSQL](https://github.com/testcontainers/testcontainers-java/blob/master/modules/jdbc-test/src/test/java/org/testcontainers/junit/SimplePostgreSQLTest.java)

docs/modules/databases/r2dbc.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# R2DBC support
2+
3+
You can obtain a temporary database in one of two ways:
4+
5+
* **Using a specially modified R2DBC URL**: after making a very simple modification to your system's R2DBC URL string, Testcontainers will provide a disposable stand-in database that can be used without requiring modification to your application code.
6+
* **JUnit @Rule/@ClassRule**: this mode starts a database inside a container before your tests and tears it down afterwards.
7+
8+
## Database containers launched via R2DBC URL scheme
9+
10+
As long as you have Testcontainers and the appropriate R2DBC driver on your classpath, you can simply modify regular R2DBC connection URLs to get a fresh containerized instance of the database each time your application starts up.
11+
12+
The started container will be terminated when the `ConnectionFactory` is closed.
13+
14+
!!! warning
15+
Both the database module (e.g. `org.testcontainers:mysql`) **and** `org.testcontainers:r2dbc` need to be on your application's classpath at runtime.
16+
17+
**Original URL**: `r2dbc:mysql://localhost:3306/databasename`
18+
19+
1. Insert `tc:` after `r2dbc:` as follows. Note that the hostname, port and database name will be ignored; you can leave these as-is or set them to any value.
20+
1. Specify the mandatory Docker tag of the database's official image that you want using a `TC_IMAGE_TAG` query parameter.
21+
22+
**Note that, unlike Testcontainers' JDBC URL support, it is not possible to specify an image tag in the 'scheme' part of the URL, and it is always necessary to specify a tag using `TC_IMAGE_TAG`.**
23+
24+
So that the URL becomes:
25+
`r2dbc:tc:mysql:///databasename?TC_IMAGE_TAG=5.7.22`
26+
27+
!!! note
28+
We will use `///` (host-less URIs) from now on to emphasis the unimportance of the `host:port` pair.
29+
From Testcontainers' perspective, `r2dbc:mysql://localhost:3306/databasename` and `r2dbc:mysql:///databasename` is the same URI.
30+
31+
!!! warning
32+
If you're using the R2DBC URL support, there is no need to instantiate an instance of the container - Testcontainers will do it automagically.
33+
34+
### R2DBC URL examples
35+
36+
#### Using MySQL
37+
38+
`r2dbc:tc:mysql:///databasename?TC_IMAGE_TAG=5.6.23`
39+
40+
#### Using MariaDB
41+
42+
`r2dbc:tc:mariadb:///databasename?TC_IMAGE_TAG=10.3.6`
43+
44+
#### Using PostgreSQL
45+
46+
`r2dbc:tc:postgresql:///databasename?TC_IMAGE_TAG=9.6.8`
47+
48+
#### Using MSSQL:
49+
50+
`r2dbc:tc:sqlserver:///?TC_IMAGE_TAG=2017-CU12`
51+
52+
## Obtaining `ConnectionFactoryOptions` from database container objects
53+
54+
If you already have an instance of the database container, you can get an instance of `ConnectionFactoryOptions` from it:
55+
<!--codeinclude-->
56+
[Creating `ConnectionFactoryOptions` from an instance)](../../../modules/postgresql/src/test/java/org/testcontainers/containers/PostgreSQLR2DBCDatabaseContainerTest.java) inside_block:get_options
57+
<!--/codeinclude-->

mkdocs.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ nav:
4040
- Modules:
4141
- Databases:
4242
- modules/databases/index.md
43+
- modules/databases/jdbc.md
44+
- modules/databases/r2dbc.md
4345
- modules/databases/cassandra.md
4446
- modules/databases/cockroachdb.md
4547
- modules/databases/couchbase.md

modules/mariadb/build.gradle

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
description = "Testcontainers :: JDBC :: MariaDB"
22

33
dependencies {
4+
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
5+
compileOnly 'com.google.auto.service:auto-service:1.0-rc6'
6+
47
compile project(':jdbc')
58

9+
compileOnly project(':r2dbc')
10+
compileOnly 'org.mariadb:r2dbc-mariadb:0.8.1-alpha1'
11+
612
testCompile 'org.mariadb.jdbc:mariadb-java-client:2.6.0'
713
testCompile 'com.zaxxer:HikariCP-java6:2.3.13'
814
testCompile 'commons-dbutils:commons-dbutils:1.7'
915
testCompile 'org.apache.tomcat:tomcat-jdbc:9.0.33'
1016
testCompile 'org.vibur:vibur-dbcp:25.0'
17+
18+
testCompile testFixtures(project(':r2dbc'))
19+
testCompile 'org.mariadb:r2dbc-mariadb:0.8.1-alpha1'
1120
}

modules/mariadb/src/main/java/org/testcontainers/containers/MariaDBContainer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public class MariaDBContainer<SELF extends MariaDBContainer<SELF>> extends JdbcD
1111
public static final String IMAGE = "mariadb";
1212
public static final String DEFAULT_TAG = "10.3.6";
1313

14-
private static final Integer MARIADB_PORT = 3306;
14+
static final Integer MARIADB_PORT = 3306;
1515
private String databaseName = "test";
1616
private String username = "test";
1717
private String password = "test";
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package org.testcontainers.containers;
2+
3+
import io.r2dbc.spi.ConnectionFactoryOptions;
4+
import lombok.RequiredArgsConstructor;
5+
import lombok.experimental.Delegate;
6+
import org.testcontainers.lifecycle.Startable;
7+
import org.testcontainers.r2dbc.R2DBCDatabaseContainer;
8+
9+
@RequiredArgsConstructor
10+
public class MariaDBR2DBCDatabaseContainer implements R2DBCDatabaseContainer {
11+
12+
@Delegate(types = Startable.class)
13+
private final MariaDBContainer<?> container;
14+
15+
public static ConnectionFactoryOptions getOptions(MariaDBContainer<?> container) {
16+
ConnectionFactoryOptions options = ConnectionFactoryOptions.builder()
17+
.option(ConnectionFactoryOptions.DRIVER, MariaDBR2DBCDatabaseContainerProvider.DRIVER)
18+
.build();
19+
20+
return new MariaDBR2DBCDatabaseContainer(container).configure(options);
21+
}
22+
23+
@Override
24+
public ConnectionFactoryOptions configure(ConnectionFactoryOptions options) {
25+
return options.mutate()
26+
.option(ConnectionFactoryOptions.HOST, container.getContainerIpAddress())
27+
.option(ConnectionFactoryOptions.PORT, container.getMappedPort(MariaDBContainer.MARIADB_PORT))
28+
.option(ConnectionFactoryOptions.DATABASE, container.getDatabaseName())
29+
.option(ConnectionFactoryOptions.USER, container.getUsername())
30+
.option(ConnectionFactoryOptions.PASSWORD, container.getPassword())
31+
.build();
32+
}
33+
}

0 commit comments

Comments
 (0)