Skip to content

Commit 5522641

Browse files
authored
Add Oracle R2DBC support (#6810)
* Update r2dbc-spi version to 0.9.0.RELEASE * Add `OracleR2DBCDatabaseContainer` * Testcontainers R2DBC URL support Fixes #4475
1 parent 2d57f61 commit 5522641

File tree

12 files changed

+147
-9
lines changed

12 files changed

+147
-9
lines changed

docs/modules/databases/r2dbc.md

+4
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ So that the URL becomes:
4949

5050
`r2dbc:tc:sqlserver:///?TC_IMAGE_TAG=2017-CU12`
5151

52+
#### Using Oracle:
53+
54+
`r2dbc:tc:oracle:///?TC_IMAGE_TAG=21-slim-faststart`
55+
5256
## Obtaining `ConnectionFactoryOptions` from database container objects
5357

5458
If you already have an instance of the database container, you can get an instance of `ConnectionFactoryOptions` from it:

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public boolean supports(ConnectionFactoryOptions options) {
2323
public R2DBCDatabaseContainer createContainer(ConnectionFactoryOptions options) {
2424
String image = MariaDBContainer.IMAGE + ":" + options.getRequiredValue(IMAGE_TAG_OPTION);
2525
MariaDBContainer<?> container = new MariaDBContainer<>(image)
26-
.withDatabaseName(options.getRequiredValue(ConnectionFactoryOptions.DATABASE));
26+
.withDatabaseName((String) options.getRequiredValue(ConnectionFactoryOptions.DATABASE));
2727

2828
if (Boolean.TRUE.equals(options.getValue(REUSABLE_OPTION))) {
2929
container.withReuse(true);

modules/mysql/src/main/java/org/testcontainers/containers/MySQLR2DBCDatabaseContainerProvider.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public boolean supports(ConnectionFactoryOptions options) {
2323
public R2DBCDatabaseContainer createContainer(ConnectionFactoryOptions options) {
2424
String image = MySQLContainer.IMAGE + ":" + options.getRequiredValue(IMAGE_TAG_OPTION);
2525
MySQLContainer<?> container = new MySQLContainer<>(image)
26-
.withDatabaseName(options.getRequiredValue(ConnectionFactoryOptions.DATABASE));
26+
.withDatabaseName((String) options.getRequiredValue(ConnectionFactoryOptions.DATABASE));
2727

2828
if (Boolean.TRUE.equals(options.getValue(REUSABLE_OPTION))) {
2929
container.withReuse(true);

modules/oracle-xe/build.gradle

+23-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,32 @@
11
description = "Testcontainers :: JDBC :: Oracle XE"
22

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

9+
compileOnly project(':r2dbc')
10+
compileOnly 'com.oracle.database.r2dbc:oracle-r2dbc:1.0.0'
11+
612
testImplementation project(':jdbc-test')
7-
testImplementation 'com.oracle.ojdbc:ojdbc8:19.3.0.0'
13+
testImplementation 'com.oracle.database.jdbc:ojdbc11:21.5.0.0'
814

915
compileOnly 'org.jetbrains:annotations:24.0.0'
16+
17+
testImplementation testFixtures(project(':r2dbc'))
18+
testImplementation 'com.oracle.database.r2dbc:oracle-r2dbc:1.0.0'
19+
}
20+
21+
test {
22+
javaLauncher = javaToolchains.launcherFor {
23+
languageVersion = JavaLanguageVersion.of(11)
24+
}
25+
}
26+
27+
compileTestJava {
28+
javaCompiler = javaToolchains.compilerFor {
29+
languageVersion = JavaLanguageVersion.of(11)
30+
}
31+
options.release.set(11)
1032
}

modules/oracle-xe/src/main/java/org/testcontainers/containers/OracleContainer.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public class OracleContainer extends JdbcDatabaseContainer<OracleContainer> {
2323

2424
static final String IMAGE = DEFAULT_IMAGE_NAME.getUnversionedPart();
2525

26-
private static final int ORACLE_PORT = 1521;
26+
static final int ORACLE_PORT = 1521;
2727

2828
private static final int APEX_HTTP_PORT = 8080;
2929

@@ -103,7 +103,7 @@ public Set<Integer> getLivenessCheckPortNumbers() {
103103

104104
@Override
105105
public String getDriverClassName() {
106-
return "oracle.jdbc.OracleDriver";
106+
return "oracle.jdbc.driver.OracleDriver";
107107
}
108108

109109
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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 OracleR2DBCDatabaseContainer implements R2DBCDatabaseContainer {
11+
12+
@Delegate(types = Startable.class)
13+
private final OracleContainer container;
14+
15+
public static ConnectionFactoryOptions getOptions(OracleContainer container) {
16+
ConnectionFactoryOptions options = ConnectionFactoryOptions
17+
.builder()
18+
.option(ConnectionFactoryOptions.DRIVER, OracleR2DBCDatabaseContainerProvider.DRIVER)
19+
.build();
20+
21+
return new OracleR2DBCDatabaseContainer(container).configure(options);
22+
}
23+
24+
@Override
25+
public ConnectionFactoryOptions configure(ConnectionFactoryOptions options) {
26+
return options
27+
.mutate()
28+
.option(ConnectionFactoryOptions.HOST, container.getHost())
29+
.option(ConnectionFactoryOptions.PORT, container.getMappedPort(OracleContainer.ORACLE_PORT))
30+
.option(ConnectionFactoryOptions.DATABASE, container.getDatabaseName())
31+
.option(ConnectionFactoryOptions.USER, container.getUsername())
32+
.option(ConnectionFactoryOptions.PASSWORD, container.getPassword())
33+
.build();
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package org.testcontainers.containers;
2+
3+
import com.google.auto.service.AutoService;
4+
import io.r2dbc.spi.ConnectionFactoryMetadata;
5+
import io.r2dbc.spi.ConnectionFactoryOptions;
6+
import org.jetbrains.annotations.Nullable;
7+
import org.testcontainers.r2dbc.R2DBCDatabaseContainer;
8+
import org.testcontainers.r2dbc.R2DBCDatabaseContainerProvider;
9+
10+
@AutoService(R2DBCDatabaseContainerProvider.class)
11+
public class OracleR2DBCDatabaseContainerProvider implements R2DBCDatabaseContainerProvider {
12+
13+
static final String DRIVER = "oracle";
14+
15+
@Override
16+
public boolean supports(ConnectionFactoryOptions options) {
17+
return DRIVER.equals(options.getRequiredValue(ConnectionFactoryOptions.DRIVER));
18+
}
19+
20+
@Override
21+
public R2DBCDatabaseContainer createContainer(ConnectionFactoryOptions options) {
22+
String image = OracleContainer.IMAGE + ":" + options.getRequiredValue(IMAGE_TAG_OPTION);
23+
OracleContainer container = new OracleContainer(image)
24+
.withDatabaseName((String) options.getRequiredValue(ConnectionFactoryOptions.DATABASE));
25+
if (Boolean.TRUE.equals(options.getValue(REUSABLE_OPTION))) {
26+
container.withReuse(true);
27+
}
28+
return new OracleR2DBCDatabaseContainer(container);
29+
}
30+
31+
@Nullable
32+
@Override
33+
public ConnectionFactoryMetadata getMetadata(ConnectionFactoryOptions options) {
34+
ConnectionFactoryOptions.Builder builder = options.mutate();
35+
if (!options.hasOption(ConnectionFactoryOptions.USER)) {
36+
builder.option(ConnectionFactoryOptions.USER, OracleContainer.APP_USER);
37+
}
38+
if (!options.hasOption(ConnectionFactoryOptions.PASSWORD)) {
39+
builder.option(ConnectionFactoryOptions.PASSWORD, OracleContainer.APP_USER_PASSWORD);
40+
}
41+
return R2DBCDatabaseContainerProvider.super.getMetadata(builder.build());
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package org.testcontainers.containers.r2dbc;
2+
3+
import io.r2dbc.spi.ConnectionFactoryOptions;
4+
import org.testcontainers.containers.OracleContainer;
5+
import org.testcontainers.containers.OracleR2DBCDatabaseContainer;
6+
import org.testcontainers.r2dbc.AbstractR2DBCDatabaseContainerTest;
7+
8+
public class OracleR2DBCDatabaseContainerTest extends AbstractR2DBCDatabaseContainerTest<OracleContainer> {
9+
10+
@Override
11+
protected OracleContainer createContainer() {
12+
return new OracleContainer("gvenzl/oracle-xe:21-slim-faststart");
13+
}
14+
15+
@Override
16+
protected ConnectionFactoryOptions getOptions(OracleContainer container) {
17+
ConnectionFactoryOptions options = OracleR2DBCDatabaseContainer.getOptions(container);
18+
19+
return options;
20+
}
21+
22+
protected String createR2DBCUrl() {
23+
return "r2dbc:tc:oracle:///db?TC_IMAGE_TAG=21-slim-faststart";
24+
}
25+
26+
@Override
27+
protected String query() {
28+
return "SELECT %s from dual";
29+
}
30+
}

modules/postgresql/src/main/java/org/testcontainers/containers/PostgreSQLR2DBCDatabaseContainerProvider.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public boolean supports(ConnectionFactoryOptions options) {
2323
public R2DBCDatabaseContainer createContainer(ConnectionFactoryOptions options) {
2424
String image = PostgreSQLContainer.IMAGE + ":" + options.getRequiredValue(IMAGE_TAG_OPTION);
2525
PostgreSQLContainer<?> container = new PostgreSQLContainer<>(image)
26-
.withDatabaseName(options.getRequiredValue(ConnectionFactoryOptions.DATABASE));
26+
.withDatabaseName((String) options.getRequiredValue(ConnectionFactoryOptions.DATABASE));
2727

2828
if (Boolean.TRUE.equals(options.getValue(REUSABLE_OPTION))) {
2929
container.withReuse(true);

modules/r2dbc/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ dependencies {
99
compileOnly 'com.google.auto.service:auto-service:1.0.1'
1010

1111
api project(':testcontainers')
12-
api 'io.r2dbc:r2dbc-spi:0.8.1.RELEASE'
12+
api 'io.r2dbc:r2dbc-spi:0.9.0.RELEASE'
1313

1414
testImplementation 'org.assertj:assertj-core:3.24.2'
1515
testImplementation 'io.r2dbc:r2dbc-postgresql:0.8.13.RELEASE'

modules/r2dbc/src/main/java/org/testcontainers/r2dbc/Hidden.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ private ConnectionFactoryOptions removeProxying(ConnectionFactoryOptions options
4242
// | DRIVER | tc | postgres |
4343
// | PROTOCOL | postgres | <empty> |
4444

45-
String protocol = options.getRequiredValue(ConnectionFactoryOptions.PROTOCOL);
45+
String protocol = (String) options.getRequiredValue(ConnectionFactoryOptions.PROTOCOL);
4646
if (protocol.trim().length() == 0) {
4747
throw new IllegalArgumentException("Invalid protocol: " + protocol);
4848
}

modules/r2dbc/src/testFixtures/java/org/testcontainers/r2dbc/AbstractR2DBCDatabaseContainerTest.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,12 @@ public abstract class AbstractR2DBCDatabaseContainerTest<T extends GenericContai
1919

2020
protected abstract String createR2DBCUrl();
2121

22+
protected String query() {
23+
return "SELECT %d";
24+
}
25+
2226
protected String createTestQuery(int result) {
23-
return String.format("SELECT %d", result);
27+
return String.format(query(), result);
2428
}
2529

2630
@Test

0 commit comments

Comments
 (0)