Skip to content

Commit

Permalink
Add oceanbase module (#7502)
Browse files Browse the repository at this point in the history
  • Loading branch information
whhe committed Feb 28, 2024
1 parent af5863c commit f23c1ec
Show file tree
Hide file tree
Showing 18 changed files with 362 additions and 0 deletions.
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ body:
- MySQL
- Neo4j
- NGINX
- OceanBase
- OpenFGA
- Oracle Free
- Oracle XE
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/enhancement.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ body:
- MySQL
- Neo4j
- NGINX
- OceanBase
- OpenFGA
- Oracle Free
- Oracle XE
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/feature.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ body:
- MySQL
- Neo4j
- NGINX
- OceanBase
- OpenFGA
- Oracle Free
- Oracle XE
Expand Down
5 changes: 5 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,11 @@ updates:
schedule:
interval: "weekly"
open-pull-requests-limit: 10
- package-ecosystem: "gradle"
directory: "/modules/oceanbase"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
- package-ecosystem: "gradle"
directory: "/modules/openfga"
schedule:
Expand Down
4 changes: 4 additions & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@
- changed-files:
- any-glob-to-any-file:
- modules/nginx/**/*
"modules/oceanbase":
- changed-files:
- any-glob-to-any-file:
- modules/oceanbase/**/*
"modules/openfga":
- changed-files:
- any-glob-to-any-file:
Expand Down
3 changes: 3 additions & 0 deletions .github/settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,9 @@ labels:
- name: modules/nginx
color: '#006b75'

- name: modules/oceanbase
color: '#006b75'

- name: modules/openfga
color: '#006b75'

Expand Down
4 changes: 4 additions & 0 deletions docs/modules/databases/jdbc.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ Insert `tc:` after `jdbc:` as follows. Note that the hostname, port and database

`jdbc:tc:sqlserver:2017-CU12:///databasename`

#### Using OceanBase

`jdbc:tc:oceanbasece:4.2.2:///databasename`

#### Using Oracle

`jdbc:tc:oracle:21-slim-faststart:///databasename`
Expand Down
25 changes: 25 additions & 0 deletions docs/modules/databases/oceanbase.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# OceanBase Module

See [Database containers](./index.md) for documentation and usage that is common to all relational database container types.

## Adding this module to your project dependencies

Add the following dependency to your `pom.xml`/`build.gradle` file:

=== "Gradle"
```groovy
testImplementation "org.testcontainers:oceanbase:{{latest_version}}"
```

=== "Maven"
```xml
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>oceanbase</artifactId>
<version>{{latest_version}}</version>
<scope>test</scope>
</dependency>
```

!!! hint
Adding this Testcontainers library JAR will not automatically add a database driver JAR to your project. You should ensure that your project also has a suitable database driver as a dependency.
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ nav:
- modules/databases/mssqlserver.md
- modules/databases/mysql.md
- modules/databases/neo4j.md
- modules/databases/oceanbase.md
- modules/databases/oraclefree.md
- modules/databases/oraclexe.md
- modules/databases/orientdb.md
Expand Down
8 changes: 8 additions & 0 deletions modules/oceanbase/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
description = "Testcontainers :: JDBC :: OceanBase"

dependencies {
api project(':jdbc')

testImplementation project(':jdbc-test')
testRuntimeOnly 'mysql:mysql-connector-java:8.0.33'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package org.testcontainers.oceanbase;

import org.testcontainers.containers.JdbcDatabaseContainer;
import org.testcontainers.utility.DockerImageName;

/**
* Testcontainers implementation for OceanBase Community Edition.
* <p>
* Supported image: {@code oceanbase/oceanbase-ce}
* <p>
* Exposed ports:
* <ul>
* <li>SQL: 2881</li>
* <li>RPC: 2882</li>
* </ul>
*/
public class OceanBaseCEContainer extends JdbcDatabaseContainer<OceanBaseCEContainer> {

static final String NAME = "oceanbasece";

static final String DOCKER_IMAGE_NAME = "oceanbase/oceanbase-ce";

private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(DOCKER_IMAGE_NAME);

private static final Integer SQL_PORT = 2881;

private static final Integer RPC_PORT = 2882;

private static final String DEFAULT_TEST_TENANT_NAME = "test";

private static final String DEFAULT_USERNAME = "root";

private static final String DEFAULT_PASSWORD = "";

private static final String DEFAULT_DATABASE_NAME = "test";

public OceanBaseCEContainer(String dockerImageName) {
this(DockerImageName.parse(dockerImageName));
}

public OceanBaseCEContainer(DockerImageName dockerImageName) {
super(dockerImageName);
dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);

addExposedPorts(SQL_PORT, RPC_PORT);
}

@Override
public String getDriverClassName() {
return OceanBaseJdbcUtils.getDriverClass();
}

@Override
public String getJdbcUrl() {
String additionalUrlParams = constructUrlParameters("?", "&");
String prefix = OceanBaseJdbcUtils.isMySQLDriver(getDriverClassName()) ? "jdbc:mysql://" : "jdbc:oceanbase://";
return prefix + getHost() + ":" + getMappedPort(SQL_PORT) + "/" + DEFAULT_DATABASE_NAME + additionalUrlParams;
}

@Override
public String getDatabaseName() {
return DEFAULT_DATABASE_NAME;
}

@Override
public String getUsername() {
// In OceanBase, the jdbc username is related to the name of user, tenant and cluster,
// if a tenant name other than the default value 'test' is used, you should manually
// construct the jdbc username by yourself.
return DEFAULT_USERNAME + "@" + DEFAULT_TEST_TENANT_NAME;
}

@Override
public String getPassword() {
return DEFAULT_PASSWORD;
}

@Override
protected String getTestQueryString() {
return "SELECT 1";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.testcontainers.oceanbase;

import org.testcontainers.containers.JdbcDatabaseContainer;
import org.testcontainers.containers.JdbcDatabaseContainerProvider;
import org.testcontainers.utility.DockerImageName;

/**
* Factory for OceanBase Community Edition containers.
*/
public class OceanBaseCEContainerProvider extends JdbcDatabaseContainerProvider {

private static final String DEFAULT_TAG = "4.2.2";

@Override
public boolean supports(String databaseType) {
return databaseType.equals(OceanBaseCEContainer.NAME);
}

@Override
public JdbcDatabaseContainer newInstance() {
return newInstance(DEFAULT_TAG);
}

@Override
public JdbcDatabaseContainer newInstance(String tag) {
if (tag != null) {
return new OceanBaseCEContainer(DockerImageName.parse(OceanBaseCEContainer.DOCKER_IMAGE_NAME).withTag(tag));
} else {
return newInstance();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package org.testcontainers.oceanbase;

import java.util.Arrays;
import java.util.List;

/**
* Utils for OceanBase Jdbc Connection.
*/
class OceanBaseJdbcUtils {

static final String MYSQL_JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";

static final String MYSQL_LEGACY_JDBC_DRIVER = "com.mysql.jdbc.Driver";

static final String OCEANBASE_JDBC_DRIVER = "com.oceanbase.jdbc.Driver";

static final String OCEANBASE_LEGACY_JDBC_DRIVER = "com.alipay.oceanbase.jdbc.Driver";

static final List<String> SUPPORTED_DRIVERS = Arrays.asList(
OCEANBASE_JDBC_DRIVER,
OCEANBASE_LEGACY_JDBC_DRIVER,
MYSQL_JDBC_DRIVER,
MYSQL_LEGACY_JDBC_DRIVER
);

static String getDriverClass() {
for (String driverClass : SUPPORTED_DRIVERS) {
try {
Class.forName(driverClass);
return driverClass;
} catch (ClassNotFoundException e) {
// try to load next driver
}
}
throw new RuntimeException("Can't find valid driver class for OceanBase");
}

static boolean isMySQLDriver(String driverClassName) {
return MYSQL_JDBC_DRIVER.equals(driverClassName) || MYSQL_LEGACY_JDBC_DRIVER.equals(driverClassName);
}

static boolean isOceanBaseDriver(String driverClassName) {
return OCEANBASE_JDBC_DRIVER.equals(driverClassName) || OCEANBASE_LEGACY_JDBC_DRIVER.equals(driverClassName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.testcontainers.oceanbase.OceanBaseCEContainerProvider
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.testcontainers.oceanbase;

Check failure on line 1 in modules/oceanbase/src/test/java/org/testcontainers/oceanbase/OceanBaseJdbcDriverTest.java

View workflow job for this annotation

GitHub Actions / check (:oceanbase:check)

OceanBaseJdbcDriverTest.test[0 - jdbc:tc:oceanbasece://hostname/databasename]

com.zaxxer.hikari.pool.PoolInitializationException: Exception during pool initialization

import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.testcontainers.jdbc.AbstractJDBCDriverTest;

import java.util.Arrays;
import java.util.EnumSet;

@RunWith(Parameterized.class)
public class OceanBaseJdbcDriverTest extends AbstractJDBCDriverTest {

@Parameterized.Parameters(name = "{index} - {0}")
public static Iterable<Object[]> data() {
return Arrays.asList(
new Object[][] { { "jdbc:tc:oceanbasece://hostname/databasename", EnumSet.noneOf(Options.class) } }
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.testcontainers.oceanbase;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.output.Slf4jLogConsumer;
import org.testcontainers.db.AbstractContainerDatabaseTest;

import java.sql.ResultSet;
import java.sql.SQLException;

import static org.assertj.core.api.Assertions.assertThat;

public class SimpleOceanBaseCETest extends AbstractContainerDatabaseTest {

private static final Logger logger = LoggerFactory.getLogger(SimpleOceanBaseCETest.class);

private final OceanBaseCEContainerProvider containerProvider = new OceanBaseCEContainerProvider();

@SuppressWarnings("resource")
private OceanBaseCEContainer testContainer() {
return ((OceanBaseCEContainer) containerProvider.newInstance()).withEnv("MODE", "slim")
.withEnv("FASTBOOT", "true")
.withLogConsumer(new Slf4jLogConsumer(logger));
}

@Test
public void testSimple() throws SQLException {
try (OceanBaseCEContainer container = testContainer()) {
container.start();

ResultSet resultSet = performQuery(container, "SELECT 1");
int resultSetInt = resultSet.getInt(1);
assertThat(resultSetInt).as("A basic SELECT query succeeds").isEqualTo(1);
assertHasCorrectExposedAndLivenessCheckPorts(container);
}
}

@Test
public void testExplicitInitScript() throws SQLException {
try (OceanBaseCEContainer container = testContainer().withInitScript("init.sql")) {
container.start();

ResultSet resultSet = performQuery(container, "SELECT foo FROM bar");
String firstColumnValue = resultSet.getString(1);
assertThat(firstColumnValue).as("Value from init script should equal real value").isEqualTo("hello world");
}
}

@Test
public void testWithAdditionalUrlParamInJdbcUrl() {
try (OceanBaseCEContainer container = testContainer().withUrlParam("useSSL", "false")) {
container.start();

String jdbcUrl = container.getJdbcUrl();
assertThat(jdbcUrl).contains("?");
assertThat(jdbcUrl).contains("useSSL=false");
}
}

private void assertHasCorrectExposedAndLivenessCheckPorts(OceanBaseCEContainer container) {
int sqlPort = 2881;
int rpcPort = 2882;

assertThat(container.getExposedPorts()).containsExactlyInAnyOrder(sqlPort, rpcPort);
assertThat(container.getLivenessCheckPortNumbers())
.containsExactlyInAnyOrder(container.getMappedPort(sqlPort), container.getMappedPort(rpcPort));
}
}

0 comments on commit f23c1ec

Please sign in to comment.