Skip to content

Commit

Permalink
Add Oracle R2DBC support (#6810)
Browse files Browse the repository at this point in the history
* Update r2dbc-spi version to 0.9.0.RELEASE
* Add `OracleR2DBCDatabaseContainer`
* Testcontainers R2DBC URL support

Fixes #4475
  • Loading branch information
eddumelendez authored Mar 29, 2023
1 parent 2d57f61 commit 5522641
Show file tree
Hide file tree
Showing 12 changed files with 147 additions and 9 deletions.
4 changes: 4 additions & 0 deletions docs/modules/databases/r2dbc.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ So that the URL becomes:

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

#### Using Oracle:

`r2dbc:tc:oracle:///?TC_IMAGE_TAG=21-slim-faststart`

## Obtaining `ConnectionFactoryOptions` from database container objects

If you already have an instance of the database container, you can get an instance of `ConnectionFactoryOptions` from it:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public boolean supports(ConnectionFactoryOptions options) {
public R2DBCDatabaseContainer createContainer(ConnectionFactoryOptions options) {
String image = MariaDBContainer.IMAGE + ":" + options.getRequiredValue(IMAGE_TAG_OPTION);
MariaDBContainer<?> container = new MariaDBContainer<>(image)
.withDatabaseName(options.getRequiredValue(ConnectionFactoryOptions.DATABASE));
.withDatabaseName((String) options.getRequiredValue(ConnectionFactoryOptions.DATABASE));

if (Boolean.TRUE.equals(options.getValue(REUSABLE_OPTION))) {
container.withReuse(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public boolean supports(ConnectionFactoryOptions options) {
public R2DBCDatabaseContainer createContainer(ConnectionFactoryOptions options) {
String image = MySQLContainer.IMAGE + ":" + options.getRequiredValue(IMAGE_TAG_OPTION);
MySQLContainer<?> container = new MySQLContainer<>(image)
.withDatabaseName(options.getRequiredValue(ConnectionFactoryOptions.DATABASE));
.withDatabaseName((String) options.getRequiredValue(ConnectionFactoryOptions.DATABASE));

if (Boolean.TRUE.equals(options.getValue(REUSABLE_OPTION))) {
container.withReuse(true);
Expand Down
24 changes: 23 additions & 1 deletion modules/oracle-xe/build.gradle
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
description = "Testcontainers :: JDBC :: Oracle XE"

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

api project(':jdbc')

compileOnly project(':r2dbc')
compileOnly 'com.oracle.database.r2dbc:oracle-r2dbc:1.0.0'

testImplementation project(':jdbc-test')
testImplementation 'com.oracle.ojdbc:ojdbc8:19.3.0.0'
testImplementation 'com.oracle.database.jdbc:ojdbc11:21.5.0.0'

compileOnly 'org.jetbrains:annotations:24.0.0'

testImplementation testFixtures(project(':r2dbc'))
testImplementation 'com.oracle.database.r2dbc:oracle-r2dbc:1.0.0'
}

test {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(11)
}
}

compileTestJava {
javaCompiler = javaToolchains.compilerFor {
languageVersion = JavaLanguageVersion.of(11)
}
options.release.set(11)
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class OracleContainer extends JdbcDatabaseContainer<OracleContainer> {

static final String IMAGE = DEFAULT_IMAGE_NAME.getUnversionedPart();

private static final int ORACLE_PORT = 1521;
static final int ORACLE_PORT = 1521;

private static final int APEX_HTTP_PORT = 8080;

Expand Down Expand Up @@ -103,7 +103,7 @@ public Set<Integer> getLivenessCheckPortNumbers() {

@Override
public String getDriverClassName() {
return "oracle.jdbc.OracleDriver";
return "oracle.jdbc.driver.OracleDriver";
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
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 OracleR2DBCDatabaseContainer implements R2DBCDatabaseContainer {

@Delegate(types = Startable.class)
private final OracleContainer container;

public static ConnectionFactoryOptions getOptions(OracleContainer container) {
ConnectionFactoryOptions options = ConnectionFactoryOptions
.builder()
.option(ConnectionFactoryOptions.DRIVER, OracleR2DBCDatabaseContainerProvider.DRIVER)
.build();

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

@Override
public ConnectionFactoryOptions configure(ConnectionFactoryOptions options) {
return options
.mutate()
.option(ConnectionFactoryOptions.HOST, container.getHost())
.option(ConnectionFactoryOptions.PORT, container.getMappedPort(OracleContainer.ORACLE_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,43 @@
package org.testcontainers.containers;

import com.google.auto.service.AutoService;
import io.r2dbc.spi.ConnectionFactoryMetadata;
import io.r2dbc.spi.ConnectionFactoryOptions;
import org.jetbrains.annotations.Nullable;
import org.testcontainers.r2dbc.R2DBCDatabaseContainer;
import org.testcontainers.r2dbc.R2DBCDatabaseContainerProvider;

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

static final String DRIVER = "oracle";

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

@Override
public R2DBCDatabaseContainer createContainer(ConnectionFactoryOptions options) {
String image = OracleContainer.IMAGE + ":" + options.getRequiredValue(IMAGE_TAG_OPTION);
OracleContainer container = new OracleContainer(image)
.withDatabaseName((String) options.getRequiredValue(ConnectionFactoryOptions.DATABASE));
if (Boolean.TRUE.equals(options.getValue(REUSABLE_OPTION))) {
container.withReuse(true);
}
return new OracleR2DBCDatabaseContainer(container);
}

@Nullable
@Override
public ConnectionFactoryMetadata getMetadata(ConnectionFactoryOptions options) {
ConnectionFactoryOptions.Builder builder = options.mutate();
if (!options.hasOption(ConnectionFactoryOptions.USER)) {
builder.option(ConnectionFactoryOptions.USER, OracleContainer.APP_USER);
}
if (!options.hasOption(ConnectionFactoryOptions.PASSWORD)) {
builder.option(ConnectionFactoryOptions.PASSWORD, OracleContainer.APP_USER_PASSWORD);
}
return R2DBCDatabaseContainerProvider.super.getMetadata(builder.build());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.testcontainers.containers.r2dbc;

import io.r2dbc.spi.ConnectionFactoryOptions;
import org.testcontainers.containers.OracleContainer;
import org.testcontainers.containers.OracleR2DBCDatabaseContainer;
import org.testcontainers.r2dbc.AbstractR2DBCDatabaseContainerTest;

public class OracleR2DBCDatabaseContainerTest extends AbstractR2DBCDatabaseContainerTest<OracleContainer> {

@Override
protected OracleContainer createContainer() {
return new OracleContainer("gvenzl/oracle-xe:21-slim-faststart");
}

@Override
protected ConnectionFactoryOptions getOptions(OracleContainer container) {
ConnectionFactoryOptions options = OracleR2DBCDatabaseContainer.getOptions(container);

return options;
}

protected String createR2DBCUrl() {
return "r2dbc:tc:oracle:///db?TC_IMAGE_TAG=21-slim-faststart";
}

@Override
protected String query() {
return "SELECT %s from dual";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public boolean supports(ConnectionFactoryOptions options) {
public R2DBCDatabaseContainer createContainer(ConnectionFactoryOptions options) {
String image = PostgreSQLContainer.IMAGE + ":" + options.getRequiredValue(IMAGE_TAG_OPTION);
PostgreSQLContainer<?> container = new PostgreSQLContainer<>(image)
.withDatabaseName(options.getRequiredValue(ConnectionFactoryOptions.DATABASE));
.withDatabaseName((String) options.getRequiredValue(ConnectionFactoryOptions.DATABASE));

if (Boolean.TRUE.equals(options.getValue(REUSABLE_OPTION))) {
container.withReuse(true);
Expand Down
2 changes: 1 addition & 1 deletion modules/r2dbc/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ dependencies {
compileOnly 'com.google.auto.service:auto-service:1.0.1'

api project(':testcontainers')
api 'io.r2dbc:r2dbc-spi:0.8.1.RELEASE'
api 'io.r2dbc:r2dbc-spi:0.9.0.RELEASE'

testImplementation 'org.assertj:assertj-core:3.24.2'
testImplementation 'io.r2dbc:r2dbc-postgresql:0.8.13.RELEASE'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ private ConnectionFactoryOptions removeProxying(ConnectionFactoryOptions options
// | DRIVER | tc | postgres |
// | PROTOCOL | postgres | <empty> |

String protocol = options.getRequiredValue(ConnectionFactoryOptions.PROTOCOL);
String protocol = (String) options.getRequiredValue(ConnectionFactoryOptions.PROTOCOL);
if (protocol.trim().length() == 0) {
throw new IllegalArgumentException("Invalid protocol: " + protocol);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@ public abstract class AbstractR2DBCDatabaseContainerTest<T extends GenericContai

protected abstract String createR2DBCUrl();

protected String query() {
return "SELECT %d";
}

protected String createTestQuery(int result) {
return String.format("SELECT %d", result);
return String.format(query(), result);
}

@Test
Expand Down

0 comments on commit 5522641

Please sign in to comment.