From 0210dcf06bf93f5f731584ff0839af0f1e108ff3 Mon Sep 17 00:00:00 2001 From: He Wang Date: Thu, 31 Aug 2023 20:50:27 +0800 Subject: [PATCH] feat: add oceanbase-ce module --- .github/ISSUE_TEMPLATE/bug_report.yaml | 1 + .github/ISSUE_TEMPLATE/enhancement.yaml | 1 + .github/ISSUE_TEMPLATE/feature.yaml | 1 + .github/dependabot.yml | 5 + .github/labeler.yml | 4 + docs/modules/databases/jdbc.md | 4 + docs/modules/databases/oceanbasece.md | 25 ++++ mkdocs.yml | 1 + modules/oceanbase-ce/build.gradle | 8 + .../containers/OceanBaseContainer.java | 139 ++++++++++++++++++ .../OceanBaseContainerProvider.java | 30 ++++ ...s.containers.JdbcDatabaseContainerProvider | 1 + .../testcontainers/OceanBaseTestImages.java | 8 + .../oceanbase/OceanBaseJdbcDriverTest.java | 19 +++ .../junit/oceanbase/SimpleOceanBaseTest.java | 79 ++++++++++ .../oceanbase-ce/src/test/resources/init.sql | 45 ++++++ .../src/test/resources/logback-test.xml | 16 ++ 17 files changed, 387 insertions(+) create mode 100644 docs/modules/databases/oceanbasece.md create mode 100644 modules/oceanbase-ce/build.gradle create mode 100644 modules/oceanbase-ce/src/main/java/org/testcontainers/containers/OceanBaseContainer.java create mode 100644 modules/oceanbase-ce/src/main/java/org/testcontainers/containers/OceanBaseContainerProvider.java create mode 100644 modules/oceanbase-ce/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider create mode 100644 modules/oceanbase-ce/src/test/java/org/testcontainers/OceanBaseTestImages.java create mode 100644 modules/oceanbase-ce/src/test/java/org/testcontainers/jdbc/oceanbase/OceanBaseJdbcDriverTest.java create mode 100644 modules/oceanbase-ce/src/test/java/org/testcontainers/junit/oceanbase/SimpleOceanBaseTest.java create mode 100644 modules/oceanbase-ce/src/test/resources/init.sql create mode 100644 modules/oceanbase-ce/src/test/resources/logback-test.xml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 135c8323866..4336804c5f4 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -38,6 +38,7 @@ body: - MySQL - Neo4j - NGINX + - OceanBase CE - Oracle Free - Oracle XE - OrientDB diff --git a/.github/ISSUE_TEMPLATE/enhancement.yaml b/.github/ISSUE_TEMPLATE/enhancement.yaml index d77b4ce07a2..d98eba32b71 100644 --- a/.github/ISSUE_TEMPLATE/enhancement.yaml +++ b/.github/ISSUE_TEMPLATE/enhancement.yaml @@ -38,6 +38,7 @@ body: - MySQL - Neo4j - NGINX + - OceanBase CE - Oracle Free - Oracle XE - OrientDB diff --git a/.github/ISSUE_TEMPLATE/feature.yaml b/.github/ISSUE_TEMPLATE/feature.yaml index 65a27e5f99a..a00c8dcd3f3 100644 --- a/.github/ISSUE_TEMPLATE/feature.yaml +++ b/.github/ISSUE_TEMPLATE/feature.yaml @@ -38,6 +38,7 @@ body: - MySQL - Neo4j - NGINX + - OceanBase CE - Oracle Free - Oracle XE - OrientDB diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3cb3e0fd222..d962092d345 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -194,6 +194,11 @@ updates: schedule: interval: "weekly" open-pull-requests-limit: 10 + - package-ecosystem: "gradle" + directory: "/modules/oceanbase-ce" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 - package-ecosystem: "gradle" directory: "/modules/oracle-free" schedule: diff --git a/.github/labeler.yml b/.github/labeler.yml index eec54b67829..a965c4bebbb 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -119,6 +119,10 @@ - changed-files: - any-glob-to-any-file: - modules/nginx/**/* +"modules/oceanbase-ce": + - changed-files: + - any-glob-to-any-file: + - modules/oceanbase-ce/**/* "modules/oracle": - changed-files: - any-glob-to-any-file: diff --git a/docs/modules/databases/jdbc.md b/docs/modules/databases/jdbc.md index ab4baa75e22..e0535962310 100644 --- a/docs/modules/databases/jdbc.md +++ b/docs/modules/databases/jdbc.md @@ -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:oceanbase:4.1.0.0:///databasename` + #### Using Oracle `jdbc:tc:oracle:21-slim-faststart:///databasename` diff --git a/docs/modules/databases/oceanbasece.md b/docs/modules/databases/oceanbasece.md new file mode 100644 index 00000000000..87e84f9b5f5 --- /dev/null +++ b/docs/modules/databases/oceanbasece.md @@ -0,0 +1,25 @@ +# OceanBase-CE 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-ce:{{latest_version}}" + ``` + +=== "Maven" + ```xml + + org.testcontainers + oceanbase-ce + {{latest_version}} + test + + ``` + +!!! 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. diff --git a/mkdocs.yml b/mkdocs.yml index 43645183075..3067bf405f1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -64,6 +64,7 @@ nav: - modules/databases/mssqlserver.md - modules/databases/mysql.md - modules/databases/neo4j.md + - modules/databases/oceanbasece.md - modules/databases/oraclefree.md - modules/databases/oraclexe.md - modules/databases/orientdb.md diff --git a/modules/oceanbase-ce/build.gradle b/modules/oceanbase-ce/build.gradle new file mode 100644 index 00000000000..0e4f901fada --- /dev/null +++ b/modules/oceanbase-ce/build.gradle @@ -0,0 +1,8 @@ +description = "Testcontainers :: JDBC :: OceanBase CE" + +dependencies { + api project(':jdbc') + + testImplementation project(':jdbc-test') + testRuntimeOnly 'mysql:mysql-connector-java:8.0.33' +} diff --git a/modules/oceanbase-ce/src/main/java/org/testcontainers/containers/OceanBaseContainer.java b/modules/oceanbase-ce/src/main/java/org/testcontainers/containers/OceanBaseContainer.java new file mode 100644 index 00000000000..12687f21c5c --- /dev/null +++ b/modules/oceanbase-ce/src/main/java/org/testcontainers/containers/OceanBaseContainer.java @@ -0,0 +1,139 @@ +package org.testcontainers.containers; + +import org.apache.commons.lang3.StringUtils; +import org.testcontainers.utility.DockerImageName; + +/** + * Testcontainers implementation for OceanBase. + *

+ * Supported image: {@code oceanbase/oceanbase-ce} + *

+ * Exposed ports: + *

+ */ +public class OceanBaseContainer extends JdbcDatabaseContainer { + + static final String NAME = "oceanbase"; + + 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 boolean enableFastboot; + private String mode; + private String tenantName = DEFAULT_TEST_TENANT_NAME; + + public OceanBaseContainer(String dockerImageName) { + this(DockerImageName.parse(dockerImageName)); + } + + public OceanBaseContainer(DockerImageName dockerImageName) { + super(dockerImageName); + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + + addExposedPorts(SQL_PORT, RPC_PORT); + } + + @Override + public Integer getMappedPort(int originalPort) { + return "host".equals(getNetworkMode()) ? originalPort : super.getMappedPort(originalPort); + } + + @Override + public String getDriverClassName() { + return "com.mysql.cj.jdbc.Driver"; + } + + @Override + public String getJdbcUrl() { + return getJdbcUrl(DEFAULT_DATABASE_NAME); + } + + public String getJdbcUrl(String databaseName) { + String additionalUrlParams = constructUrlParameters("?", "&"); + return "jdbc:mysql://" + 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"; + } + + /** + * Enable fastboot. + * + * @return this + */ + public OceanBaseContainer enableFastboot() { + this.enableFastboot = true; + return self(); + } + + /** + * Set the deployment mode, see Docker Hub for more details. + * + * @param mode the deployment mode + * @return this + */ + public OceanBaseContainer withMode(String mode) { + this.mode = mode; + return self(); + } + + /** + * Set the non-system tenant to be created for testing. + * + * @param tenantName the name of tenant to be created + * @return this + */ + public OceanBaseContainer 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 (StringUtils.isNotBlank(mode)) { + withEnv("MODE", mode); + } + if (enableFastboot) { + withEnv("FASTBOOT", "true"); + } + if (!DEFAULT_TEST_TENANT_NAME.equals(tenantName)) { + withEnv("OB_TENANT_NAME", tenantName); + } + } +} diff --git a/modules/oceanbase-ce/src/main/java/org/testcontainers/containers/OceanBaseContainerProvider.java b/modules/oceanbase-ce/src/main/java/org/testcontainers/containers/OceanBaseContainerProvider.java new file mode 100644 index 00000000000..fd84a04d328 --- /dev/null +++ b/modules/oceanbase-ce/src/main/java/org/testcontainers/containers/OceanBaseContainerProvider.java @@ -0,0 +1,30 @@ +package org.testcontainers.containers; + +import org.testcontainers.utility.DockerImageName; + +/** + * Factory for OceanBase containers. + */ +public class OceanBaseContainerProvider extends JdbcDatabaseContainerProvider { + + private static final String DEFAULT_TAG = "4.2.1_bp3"; + + @Override + public boolean supports(String databaseType) { + return databaseType.equals(OceanBaseContainer.NAME); + } + + @Override + public JdbcDatabaseContainer newInstance() { + return newInstance(DEFAULT_TAG); + } + + @Override + public JdbcDatabaseContainer newInstance(String tag) { + if (tag != null) { + return new OceanBaseContainer(DockerImageName.parse(OceanBaseContainer.DOCKER_IMAGE_NAME).withTag(tag)); + } else { + return newInstance(); + } + } +} diff --git a/modules/oceanbase-ce/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider b/modules/oceanbase-ce/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider new file mode 100644 index 00000000000..977e58989c9 --- /dev/null +++ b/modules/oceanbase-ce/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider @@ -0,0 +1 @@ +org.testcontainers.containers.OceanBaseContainerProvider diff --git a/modules/oceanbase-ce/src/test/java/org/testcontainers/OceanBaseTestImages.java b/modules/oceanbase-ce/src/test/java/org/testcontainers/OceanBaseTestImages.java new file mode 100644 index 00000000000..b758e8b605f --- /dev/null +++ b/modules/oceanbase-ce/src/test/java/org/testcontainers/OceanBaseTestImages.java @@ -0,0 +1,8 @@ +package org.testcontainers; + +import org.testcontainers.utility.DockerImageName; + +public class OceanBaseTestImages { + + public static final DockerImageName OCEANBASE_CE_IMAGE = DockerImageName.parse("oceanbase/oceanbase-ce:4.2.1_bp3"); +} diff --git a/modules/oceanbase-ce/src/test/java/org/testcontainers/jdbc/oceanbase/OceanBaseJdbcDriverTest.java b/modules/oceanbase-ce/src/test/java/org/testcontainers/jdbc/oceanbase/OceanBaseJdbcDriverTest.java new file mode 100644 index 00000000000..d540c71e7cf --- /dev/null +++ b/modules/oceanbase-ce/src/test/java/org/testcontainers/jdbc/oceanbase/OceanBaseJdbcDriverTest.java @@ -0,0 +1,19 @@ +package org.testcontainers.jdbc.oceanbase; + +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 data() { + return Arrays.asList( + new Object[][] { { "jdbc:tc:oceanbase://hostname/databasename", EnumSet.noneOf(Options.class) } } + ); + } +} diff --git a/modules/oceanbase-ce/src/test/java/org/testcontainers/junit/oceanbase/SimpleOceanBaseTest.java b/modules/oceanbase-ce/src/test/java/org/testcontainers/junit/oceanbase/SimpleOceanBaseTest.java new file mode 100644 index 00000000000..c447883beff --- /dev/null +++ b/modules/oceanbase-ce/src/test/java/org/testcontainers/junit/oceanbase/SimpleOceanBaseTest.java @@ -0,0 +1,79 @@ +package org.testcontainers.junit.oceanbase; + +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.OceanBaseTestImages; +import org.testcontainers.containers.OceanBaseContainer; +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 SimpleOceanBaseTest extends AbstractContainerDatabaseTest { + + private static final Logger logger = LoggerFactory.getLogger(SimpleOceanBaseTest.class); + + @Test + public void testSimple() throws SQLException { + try ( + OceanBaseContainer container = new OceanBaseContainer(OceanBaseTestImages.OCEANBASE_CE_IMAGE) + .withMode("slim") + .enableFastboot() + .withLogConsumer(new Slf4jLogConsumer(logger)) + ) { + 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 ( + OceanBaseContainer container = new OceanBaseContainer(OceanBaseTestImages.OCEANBASE_CE_IMAGE) + .withMode("slim") + .enableFastboot() + .withInitScript("init.sql") + .withLogConsumer(new Slf4jLogConsumer(logger)) + ) { + 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 ( + OceanBaseContainer container = new OceanBaseContainer(OceanBaseTestImages.OCEANBASE_CE_IMAGE) + .withMode("slim") + .enableFastboot() + .withUrlParam("useSSL", "false") + .withLogConsumer(new Slf4jLogConsumer(logger)) + ) { + container.start(); + + String jdbcUrl = container.getJdbcUrl(); + assertThat(jdbcUrl).contains("?"); + assertThat(jdbcUrl).contains("useSSL=false"); + } + } + + private void assertHasCorrectExposedAndLivenessCheckPorts(OceanBaseContainer container) { + int sqlPort = 2881; + int rpcPort = 2882; + + assertThat(container.getExposedPorts()).containsExactlyInAnyOrder(sqlPort, rpcPort); + assertThat(container.getLivenessCheckPortNumbers()) + .containsExactlyInAnyOrder(container.getMappedPort(sqlPort), container.getMappedPort(rpcPort)); + } +} diff --git a/modules/oceanbase-ce/src/test/resources/init.sql b/modules/oceanbase-ce/src/test/resources/init.sql new file mode 100644 index 00000000000..98d6889b078 --- /dev/null +++ b/modules/oceanbase-ce/src/test/resources/init.sql @@ -0,0 +1,45 @@ +CREATE TABLE bar ( + foo VARCHAR(255) +); + +DROP PROCEDURE IF EXISTS -- ; + count_foo; + +SELECT "a /* string literal containing comment characters like -- here"; +SELECT "a 'quoting' \"scenario ` involving BEGIN keyword\" here"; +SELECT * from `bar`; + +-- What about a line comment containing imbalanced string delimiters? " + +CREATE PROCEDURE count_foo() +BEGIN + + BEGIN + SELECT * + FROM bar; + SELECT 1 + FROM dual; + END; + + BEGIN + select * from bar; + END; + + -- we can do comments + + /* including block + comments + */ + + /* what if BEGIN appears inside a comment? */ + + select "or what if BEGIN appears inside a literal?"; + +END /*; */; + +/* or a block comment + containing imbalanced string delimiters? + ' " + */ + +INSERT INTO bar (foo) /* ; */ VALUES ('hello world'); diff --git a/modules/oceanbase-ce/src/test/resources/logback-test.xml b/modules/oceanbase-ce/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..83ef7a1a3ef --- /dev/null +++ b/modules/oceanbase-ce/src/test/resources/logback-test.xml @@ -0,0 +1,16 @@ + + + + + + %d{HH:mm:ss.SSS} %-5level %logger - %msg%n + + + + + + + + +