-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat: add oceanbase-ce module #7502
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 10 commits
04f4318
d9c4384
76aa6f5
3c2360d
299951a
8eb7836
995ab96
4d10383
4c6922b
e1a303f
66b4592
ae13ed7
5bd35cb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,6 +42,7 @@ body: | |
- MySQL | ||
- Neo4j | ||
- NGINX | ||
- OceanBase | ||
- OpenFGA | ||
- Oracle Free | ||
- Oracle XE | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,6 +42,7 @@ body: | |
- MySQL | ||
- Neo4j | ||
- NGINX | ||
- OceanBase | ||
- OpenFGA | ||
- Oracle Free | ||
- Oracle XE | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,6 +42,7 @@ body: | |
- MySQL | ||
- Neo4j | ||
- NGINX | ||
- OceanBase | ||
- OpenFGA | ||
- Oracle Free | ||
- Oracle XE | ||
|
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. |
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,112 @@ | ||||||||||||||||||||||
package org.testcontainers.oceanbase; | ||||||||||||||||||||||
|
||||||||||||||||||||||
import org.apache.commons.lang3.StringUtils; | ||||||||||||||||||||||
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 SYSTEM_TENANT_NAME = "sys"; | ||||||||||||||||||||||
|
||||||||||||||||||||||
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"; | ||||||||||||||||||||||
|
||||||||||||||||||||||
private String tenantName = DEFAULT_TEST_TENANT_NAME; | ||||||||||||||||||||||
|
||||||||||||||||||||||
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() { | ||||||||||||||||||||||
return getJdbcUrl(DEFAULT_DATABASE_NAME); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
public String getJdbcUrl(String databaseName) { | ||||||||||||||||||||||
whhe marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||
String additionalUrlParams = constructUrlParameters("?", "&"); | ||||||||||||||||||||||
String prefix = OceanBaseJdbcUtils.isMySQLDriver(getDriverClassName()) ? "jdbc:mysql://" : "jdbc:oceanbase://"; | ||||||||||||||||||||||
return prefix + getHost() + ":" + getMappedPort(SQL_PORT) + "/" + databaseName + additionalUrlParams; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
@Override | ||||||||||||||||||||||
public String getDatabaseName() { | ||||||||||||||||||||||
return DEFAULT_DATABASE_NAME; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
@Override | ||||||||||||||||||||||
public String getUsername() { | ||||||||||||||||||||||
return DEFAULT_USERNAME + "@" + tenantName; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
@Override | ||||||||||||||||||||||
public String getPassword() { | ||||||||||||||||||||||
return DEFAULT_PASSWORD; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
@Override | ||||||||||||||||||||||
protected String getTestQueryString() { | ||||||||||||||||||||||
return "SELECT 1"; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
/** | ||||||||||||||||||||||
* Set the non-system tenant to be created for testing. | ||||||||||||||||||||||
* | ||||||||||||||||||||||
* @param tenantName the name of tenant to be created | ||||||||||||||||||||||
* @return this | ||||||||||||||||||||||
*/ | ||||||||||||||||||||||
public OceanBaseCEContainer withTenant(String tenantName) { | ||||||||||||||||||||||
if (StringUtils.isEmpty(tenantName)) { | ||||||||||||||||||||||
throw new IllegalArgumentException("Tenant name cannot be null or empty"); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
if (SYSTEM_TENANT_NAME.equals(tenantName)) { | ||||||||||||||||||||||
throw new IllegalArgumentException("Tenant name cannot be " + SYSTEM_TENANT_NAME); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
this.tenantName = tenantName; | ||||||||||||||||||||||
return self(); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
@Override | ||||||||||||||||||||||
protected void configure() { | ||||||||||||||||||||||
if (!DEFAULT_TEST_TENANT_NAME.equals(tenantName)) { | ||||||||||||||||||||||
withEnv("OB_TENANT_NAME", tenantName); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. according to the docs, the default value for
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. User can also use 'withEnv' method to set it manually, in this case if we do not check the value, there may be a mistake. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't fully understand because the default is if (!SYSTEM_TENANT_NAME.equals(tenantName)) {
withEnv("OB_TENANT_NAME", tenantName);
} Once we resolve this comment I can proceed and merge it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm considering this situation: someone use the container class but not set the tenant name with given method I'm not sure if we should deal with it. WDYT? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that given there is a default value in the image itself. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The tenantName field here is required to construct the username used in jdbc connection, so I did not remove the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just noticed we were missing a test for this use case and also think a little bit more about it. In the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes you are right, we can resolve it by using the envMap, but I think it's not necessary for now. At present no other |
||||||||||||||||||||||
} |
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.jdbc.oceanbase; | ||
whhe marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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,71 @@ | ||
package org.testcontainers.junit.oceanbase; | ||
whhe marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
import org.junit.Test; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import org.testcontainers.containers.output.Slf4jLogConsumer; | ||
import org.testcontainers.db.AbstractContainerDatabaseTest; | ||
import org.testcontainers.oceanbase.OceanBaseCEContainer; | ||
import org.testcontainers.oceanbase.OceanBaseCEContainerProvider; | ||
|
||
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)); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.