Skip to content

Commit f23c1ec

Browse files
authored
Add oceanbase module (#7502)
1 parent af5863c commit f23c1ec

File tree

18 files changed

+362
-0
lines changed

18 files changed

+362
-0
lines changed

.github/ISSUE_TEMPLATE/bug_report.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ body:
4242
- MySQL
4343
- Neo4j
4444
- NGINX
45+
- OceanBase
4546
- OpenFGA
4647
- Oracle Free
4748
- Oracle XE

.github/ISSUE_TEMPLATE/enhancement.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ body:
4242
- MySQL
4343
- Neo4j
4444
- NGINX
45+
- OceanBase
4546
- OpenFGA
4647
- Oracle Free
4748
- Oracle XE

.github/ISSUE_TEMPLATE/feature.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ body:
4242
- MySQL
4343
- Neo4j
4444
- NGINX
45+
- OceanBase
4546
- OpenFGA
4647
- Oracle Free
4748
- Oracle XE

.github/dependabot.yml

+5
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,11 @@ updates:
219219
schedule:
220220
interval: "weekly"
221221
open-pull-requests-limit: 10
222+
- package-ecosystem: "gradle"
223+
directory: "/modules/oceanbase"
224+
schedule:
225+
interval: "weekly"
226+
open-pull-requests-limit: 10
222227
- package-ecosystem: "gradle"
223228
directory: "/modules/openfga"
224229
schedule:

.github/labeler.yml

+4
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@
135135
- changed-files:
136136
- any-glob-to-any-file:
137137
- modules/nginx/**/*
138+
"modules/oceanbase":
139+
- changed-files:
140+
- any-glob-to-any-file:
141+
- modules/oceanbase/**/*
138142
"modules/openfga":
139143
- changed-files:
140144
- any-glob-to-any-file:

.github/settings.yml

+3
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,9 @@ labels:
193193
- name: modules/nginx
194194
color: '#006b75'
195195

196+
- name: modules/oceanbase
197+
color: '#006b75'
198+
196199
- name: modules/openfga
197200
color: '#006b75'
198201

docs/modules/databases/jdbc.md

+4
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ Insert `tc:` after `jdbc:` as follows. Note that the hostname, port and database
5555

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

58+
#### Using OceanBase
59+
60+
`jdbc:tc:oceanbasece:4.2.2:///databasename`
61+
5862
#### Using Oracle
5963

6064
`jdbc:tc:oracle:21-slim-faststart:///databasename`

docs/modules/databases/oceanbase.md

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# OceanBase Module
2+
3+
See [Database containers](./index.md) for documentation and usage that is common to all relational database container types.
4+
5+
## Adding this module to your project dependencies
6+
7+
Add the following dependency to your `pom.xml`/`build.gradle` file:
8+
9+
=== "Gradle"
10+
```groovy
11+
testImplementation "org.testcontainers:oceanbase:{{latest_version}}"
12+
```
13+
14+
=== "Maven"
15+
```xml
16+
<dependency>
17+
<groupId>org.testcontainers</groupId>
18+
<artifactId>oceanbase</artifactId>
19+
<version>{{latest_version}}</version>
20+
<scope>test</scope>
21+
</dependency>
22+
```
23+
24+
!!! hint
25+
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.

mkdocs.yml

+1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ nav:
6464
- modules/databases/mssqlserver.md
6565
- modules/databases/mysql.md
6666
- modules/databases/neo4j.md
67+
- modules/databases/oceanbase.md
6768
- modules/databases/oraclefree.md
6869
- modules/databases/oraclexe.md
6970
- modules/databases/orientdb.md

modules/oceanbase/build.gradle

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
description = "Testcontainers :: JDBC :: OceanBase"
2+
3+
dependencies {
4+
api project(':jdbc')
5+
6+
testImplementation project(':jdbc-test')
7+
testRuntimeOnly 'mysql:mysql-connector-java:8.0.33'
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package org.testcontainers.oceanbase;
2+
3+
import org.testcontainers.containers.JdbcDatabaseContainer;
4+
import org.testcontainers.utility.DockerImageName;
5+
6+
/**
7+
* Testcontainers implementation for OceanBase Community Edition.
8+
* <p>
9+
* Supported image: {@code oceanbase/oceanbase-ce}
10+
* <p>
11+
* Exposed ports:
12+
* <ul>
13+
* <li>SQL: 2881</li>
14+
* <li>RPC: 2882</li>
15+
* </ul>
16+
*/
17+
public class OceanBaseCEContainer extends JdbcDatabaseContainer<OceanBaseCEContainer> {
18+
19+
static final String NAME = "oceanbasece";
20+
21+
static final String DOCKER_IMAGE_NAME = "oceanbase/oceanbase-ce";
22+
23+
private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(DOCKER_IMAGE_NAME);
24+
25+
private static final Integer SQL_PORT = 2881;
26+
27+
private static final Integer RPC_PORT = 2882;
28+
29+
private static final String DEFAULT_TEST_TENANT_NAME = "test";
30+
31+
private static final String DEFAULT_USERNAME = "root";
32+
33+
private static final String DEFAULT_PASSWORD = "";
34+
35+
private static final String DEFAULT_DATABASE_NAME = "test";
36+
37+
public OceanBaseCEContainer(String dockerImageName) {
38+
this(DockerImageName.parse(dockerImageName));
39+
}
40+
41+
public OceanBaseCEContainer(DockerImageName dockerImageName) {
42+
super(dockerImageName);
43+
dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);
44+
45+
addExposedPorts(SQL_PORT, RPC_PORT);
46+
}
47+
48+
@Override
49+
public String getDriverClassName() {
50+
return OceanBaseJdbcUtils.getDriverClass();
51+
}
52+
53+
@Override
54+
public String getJdbcUrl() {
55+
String additionalUrlParams = constructUrlParameters("?", "&");
56+
String prefix = OceanBaseJdbcUtils.isMySQLDriver(getDriverClassName()) ? "jdbc:mysql://" : "jdbc:oceanbase://";
57+
return prefix + getHost() + ":" + getMappedPort(SQL_PORT) + "/" + DEFAULT_DATABASE_NAME + additionalUrlParams;
58+
}
59+
60+
@Override
61+
public String getDatabaseName() {
62+
return DEFAULT_DATABASE_NAME;
63+
}
64+
65+
@Override
66+
public String getUsername() {
67+
// In OceanBase, the jdbc username is related to the name of user, tenant and cluster,
68+
// if a tenant name other than the default value 'test' is used, you should manually
69+
// construct the jdbc username by yourself.
70+
return DEFAULT_USERNAME + "@" + DEFAULT_TEST_TENANT_NAME;
71+
}
72+
73+
@Override
74+
public String getPassword() {
75+
return DEFAULT_PASSWORD;
76+
}
77+
78+
@Override
79+
protected String getTestQueryString() {
80+
return "SELECT 1";
81+
}
82+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package org.testcontainers.oceanbase;
2+
3+
import org.testcontainers.containers.JdbcDatabaseContainer;
4+
import org.testcontainers.containers.JdbcDatabaseContainerProvider;
5+
import org.testcontainers.utility.DockerImageName;
6+
7+
/**
8+
* Factory for OceanBase Community Edition containers.
9+
*/
10+
public class OceanBaseCEContainerProvider extends JdbcDatabaseContainerProvider {
11+
12+
private static final String DEFAULT_TAG = "4.2.2";
13+
14+
@Override
15+
public boolean supports(String databaseType) {
16+
return databaseType.equals(OceanBaseCEContainer.NAME);
17+
}
18+
19+
@Override
20+
public JdbcDatabaseContainer newInstance() {
21+
return newInstance(DEFAULT_TAG);
22+
}
23+
24+
@Override
25+
public JdbcDatabaseContainer newInstance(String tag) {
26+
if (tag != null) {
27+
return new OceanBaseCEContainer(DockerImageName.parse(OceanBaseCEContainer.DOCKER_IMAGE_NAME).withTag(tag));
28+
} else {
29+
return newInstance();
30+
}
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package org.testcontainers.oceanbase;
2+
3+
import java.util.Arrays;
4+
import java.util.List;
5+
6+
/**
7+
* Utils for OceanBase Jdbc Connection.
8+
*/
9+
class OceanBaseJdbcUtils {
10+
11+
static final String MYSQL_JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";
12+
13+
static final String MYSQL_LEGACY_JDBC_DRIVER = "com.mysql.jdbc.Driver";
14+
15+
static final String OCEANBASE_JDBC_DRIVER = "com.oceanbase.jdbc.Driver";
16+
17+
static final String OCEANBASE_LEGACY_JDBC_DRIVER = "com.alipay.oceanbase.jdbc.Driver";
18+
19+
static final List<String> SUPPORTED_DRIVERS = Arrays.asList(
20+
OCEANBASE_JDBC_DRIVER,
21+
OCEANBASE_LEGACY_JDBC_DRIVER,
22+
MYSQL_JDBC_DRIVER,
23+
MYSQL_LEGACY_JDBC_DRIVER
24+
);
25+
26+
static String getDriverClass() {
27+
for (String driverClass : SUPPORTED_DRIVERS) {
28+
try {
29+
Class.forName(driverClass);
30+
return driverClass;
31+
} catch (ClassNotFoundException e) {
32+
// try to load next driver
33+
}
34+
}
35+
throw new RuntimeException("Can't find valid driver class for OceanBase");
36+
}
37+
38+
static boolean isMySQLDriver(String driverClassName) {
39+
return MYSQL_JDBC_DRIVER.equals(driverClassName) || MYSQL_LEGACY_JDBC_DRIVER.equals(driverClassName);
40+
}
41+
42+
static boolean isOceanBaseDriver(String driverClassName) {
43+
return OCEANBASE_JDBC_DRIVER.equals(driverClassName) || OCEANBASE_LEGACY_JDBC_DRIVER.equals(driverClassName);
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
org.testcontainers.oceanbase.OceanBaseCEContainerProvider
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package org.testcontainers.oceanbase;
2+
3+
import org.junit.runner.RunWith;
4+
import org.junit.runners.Parameterized;
5+
import org.testcontainers.jdbc.AbstractJDBCDriverTest;
6+
7+
import java.util.Arrays;
8+
import java.util.EnumSet;
9+
10+
@RunWith(Parameterized.class)
11+
public class OceanBaseJdbcDriverTest extends AbstractJDBCDriverTest {
12+
13+
@Parameterized.Parameters(name = "{index} - {0}")
14+
public static Iterable<Object[]> data() {
15+
return Arrays.asList(
16+
new Object[][] { { "jdbc:tc:oceanbasece://hostname/databasename", EnumSet.noneOf(Options.class) } }
17+
);
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package org.testcontainers.oceanbase;
2+
3+
import org.junit.Test;
4+
import org.slf4j.Logger;
5+
import org.slf4j.LoggerFactory;
6+
import org.testcontainers.containers.output.Slf4jLogConsumer;
7+
import org.testcontainers.db.AbstractContainerDatabaseTest;
8+
9+
import java.sql.ResultSet;
10+
import java.sql.SQLException;
11+
12+
import static org.assertj.core.api.Assertions.assertThat;
13+
14+
public class SimpleOceanBaseCETest extends AbstractContainerDatabaseTest {
15+
16+
private static final Logger logger = LoggerFactory.getLogger(SimpleOceanBaseCETest.class);
17+
18+
private final OceanBaseCEContainerProvider containerProvider = new OceanBaseCEContainerProvider();
19+
20+
@SuppressWarnings("resource")
21+
private OceanBaseCEContainer testContainer() {
22+
return ((OceanBaseCEContainer) containerProvider.newInstance()).withEnv("MODE", "slim")
23+
.withEnv("FASTBOOT", "true")
24+
.withLogConsumer(new Slf4jLogConsumer(logger));
25+
}
26+
27+
@Test
28+
public void testSimple() throws SQLException {
29+
try (OceanBaseCEContainer container = testContainer()) {
30+
container.start();
31+
32+
ResultSet resultSet = performQuery(container, "SELECT 1");
33+
int resultSetInt = resultSet.getInt(1);
34+
assertThat(resultSetInt).as("A basic SELECT query succeeds").isEqualTo(1);
35+
assertHasCorrectExposedAndLivenessCheckPorts(container);
36+
}
37+
}
38+
39+
@Test
40+
public void testExplicitInitScript() throws SQLException {
41+
try (OceanBaseCEContainer container = testContainer().withInitScript("init.sql")) {
42+
container.start();
43+
44+
ResultSet resultSet = performQuery(container, "SELECT foo FROM bar");
45+
String firstColumnValue = resultSet.getString(1);
46+
assertThat(firstColumnValue).as("Value from init script should equal real value").isEqualTo("hello world");
47+
}
48+
}
49+
50+
@Test
51+
public void testWithAdditionalUrlParamInJdbcUrl() {
52+
try (OceanBaseCEContainer container = testContainer().withUrlParam("useSSL", "false")) {
53+
container.start();
54+
55+
String jdbcUrl = container.getJdbcUrl();
56+
assertThat(jdbcUrl).contains("?");
57+
assertThat(jdbcUrl).contains("useSSL=false");
58+
}
59+
}
60+
61+
private void assertHasCorrectExposedAndLivenessCheckPorts(OceanBaseCEContainer container) {
62+
int sqlPort = 2881;
63+
int rpcPort = 2882;
64+
65+
assertThat(container.getExposedPorts()).containsExactlyInAnyOrder(sqlPort, rpcPort);
66+
assertThat(container.getLivenessCheckPortNumbers())
67+
.containsExactlyInAnyOrder(container.getMappedPort(sqlPort), container.getMappedPort(rpcPort));
68+
}
69+
}

0 commit comments

Comments
 (0)