This is an automated email from the ASF dual-hosted git repository.
mchades pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new 437c41b0b feat(oceanbase-catalog): Support schema operations for
OceanBase JDBC catalog. (#5013)
437c41b0b is described below
commit 437c41b0bfddb732261034157df415e41d98c596
Author: yuanoOo <[email protected]>
AuthorDate: Wed Oct 9 11:59:19 2024 +0800
feat(oceanbase-catalog): Support schema operations for OceanBase JDBC
catalog. (#5013)
### What changes were proposed in this pull request?
- Support schema operations for OceanBase JDBC catalog.
- Add OceanBaseContainer to test schema operations.
### Why are the changes needed?
Fix: #4990
### Does this PR introduce _any_ user-facing change?
no
### How was this patch tested?
Add unit test: TestOceanBaseDatabaseOperations.
---
.../jdbc/operation/JdbcDatabaseOperations.java | 91 +++++++++++-
.../jdbc/operation/SqliteDatabaseOperations.java | 12 ++
.../doris/operation/DorisDatabaseOperations.java | 12 +-
.../mysql/operation/MysqlDatabaseOperations.java | 96 +------------
.../mysql/integration/test/CatalogMysqlIT.java | 2 +-
.../operation/TestMysqlDatabaseOperations.java | 9 +-
catalogs/catalog-jdbc-oceanbase/build.gradle.kts | 62 ++++++++
.../operation/OceanBaseDatabaseOperations.java | 38 +----
.../catalog/oceanbase/operation/TestOceanBase.java | 80 +++++++++++
.../operation/TestOceanBaseDatabaseOperations.java | 54 +++++++
.../operation/PostgreSqlSchemaOperations.java | 22 ++-
.../operation/TestPostgreSqlSchemaOperations.java | 8 +-
.../integration/test/container/ContainerSuite.java | 35 +++++
.../test/container/OceanBaseContainer.java | 160 +++++++++++++++++++++
14 files changed, 524 insertions(+), 157 deletions(-)
diff --git
a/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/operation/JdbcDatabaseOperations.java
b/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/operation/JdbcDatabaseOperations.java
index 96a30bc23..a1e47e032 100644
---
a/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/operation/JdbcDatabaseOperations.java
+++
b/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/operation/JdbcDatabaseOperations.java
@@ -19,18 +19,27 @@
package org.apache.gravitino.catalog.jdbc.operation;
+import com.google.common.collect.ImmutableMap;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
+import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
+import java.util.Set;
import javax.sql.DataSource;
+import org.apache.commons.collections4.MapUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.StringIdentifier;
+import org.apache.gravitino.catalog.jdbc.JdbcSchema;
import org.apache.gravitino.catalog.jdbc.converter.JdbcExceptionConverter;
import org.apache.gravitino.catalog.jdbc.utils.JdbcConnectorUtils;
import org.apache.gravitino.exceptions.NoSuchSchemaException;
import org.apache.gravitino.exceptions.SchemaAlreadyExistsException;
+import org.apache.gravitino.meta.AuditInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -53,6 +62,12 @@ public abstract class JdbcDatabaseOperations implements
DatabaseOperation {
public void create(String databaseName, String comment, Map<String, String>
properties)
throws SchemaAlreadyExistsException {
LOG.info("Beginning to create database {}", databaseName);
+ String originComment = StringIdentifier.removeIdFromComment(comment);
+ if (!supportSchemaComment() && StringUtils.isNotEmpty(originComment)) {
+ throw new UnsupportedOperationException(
+ "Doesn't support setting schema comment: " + originComment);
+ }
+
try (final Connection connection = getConnection()) {
JdbcConnectorUtils.executeUpdate(
connection, generateCreateDatabaseSql(databaseName, comment,
properties));
@@ -101,20 +116,80 @@ public abstract class JdbcDatabaseOperations implements
DatabaseOperation {
}
/**
+ * The default implementation of this method is based on MySQL syntax, and
if the catalog does not
+ * support MySQL syntax, this method needs to be rewritten.
+ *
* @param databaseName The name of the database.
* @param comment The comment of the database.
* @param properties The properties of the database.
* @return the SQL statement to create a database with the given name and
comment.
*/
- protected abstract String generateCreateDatabaseSql(
- String databaseName, String comment, Map<String, String> properties);
+ protected String generateCreateDatabaseSql(
+ String databaseName, String comment, Map<String, String> properties) {
+ String createDatabaseSql = String.format("CREATE DATABASE `%s`",
databaseName);
+ if (MapUtils.isNotEmpty(properties)) {
+ throw new UnsupportedOperationException("Properties are not supported
yet.");
+ }
+ LOG.info("Generated create database:{} sql: {}", databaseName,
createDatabaseSql);
+ return createDatabaseSql;
+ }
/**
+ * The default implementation of this method is based on MySQL syntax, and
if the catalog does not
+ * support MySQL syntax, this method needs to be rewritten.
+ *
* @param databaseName The name of the database.
* @param cascade cascade If set to true, drops all the tables in the schema
as well.
* @return the SQL statement to drop a database with the given name.
*/
- protected abstract String generateDropDatabaseSql(String databaseName,
boolean cascade);
+ protected String generateDropDatabaseSql(String databaseName, boolean
cascade) {
+ final String dropDatabaseSql = String.format("DROP DATABASE `%s`",
databaseName);
+ if (cascade) {
+ return dropDatabaseSql;
+ }
+
+ try (final Connection connection = this.dataSource.getConnection()) {
+ String query = String.format("SHOW TABLES IN `%s`", databaseName);
+ try (Statement statement = connection.createStatement()) {
+ // Execute the query and check if there exists any tables in the
database
+ try (ResultSet resultSet = statement.executeQuery(query)) {
+ if (resultSet.next()) {
+ throw new IllegalStateException(
+ String.format(
+ "Database %s is not empty, the value of cascade should be
true.",
+ databaseName));
+ }
+ }
+ }
+ } catch (SQLException sqlException) {
+ throw this.exceptionMapper.toGravitinoException(sqlException);
+ }
+ return dropDatabaseSql;
+ }
+
+ /**
+ * The default implementation of this method is based on MySQL syntax, and
if the catalog does not
+ * support MySQL syntax, this method needs to be rewritten.
+ *
+ * @param databaseName The name of the database to check.
+ * @return information object of the JDBC database.
+ */
+ @Override
+ public JdbcSchema load(String databaseName) throws NoSuchSchemaException {
+ List<String> allDatabases = listDatabases();
+ String dbName =
+ allDatabases.stream()
+ .filter(db -> db.equals(databaseName))
+ .findFirst()
+ .orElseThrow(
+ () -> new NoSuchSchemaException("Database %s could not be
found", databaseName));
+
+ return JdbcSchema.builder()
+ .withName(dbName)
+ .withProperties(ImmutableMap.of())
+ .withAuditInfo(AuditInfo.EMPTY)
+ .build();
+ }
protected Connection getConnection() throws SQLException {
return dataSource.getConnection();
@@ -124,9 +199,15 @@ public abstract class JdbcDatabaseOperations implements
DatabaseOperation {
* Check whether it is a system database.
*
* @param dbName The name of the database.
- * @return false for all cases.
+ * @return whether it is a system database.
*/
protected boolean isSystemDatabase(String dbName) {
- return false;
+ return
createSysDatabaseNameSet().contains(dbName.toLowerCase(Locale.ROOT));
}
+
+ /** Check whether support setting schema comment. */
+ protected abstract boolean supportSchemaComment();
+
+ /** Create a set of system database names. */
+ protected abstract Set<String> createSysDatabaseNameSet();
}
diff --git
a/catalogs/catalog-jdbc-common/src/test/java/org/apache/gravitino/catalog/jdbc/operation/SqliteDatabaseOperations.java
b/catalogs/catalog-jdbc-common/src/test/java/org/apache/gravitino/catalog/jdbc/operation/SqliteDatabaseOperations.java
index 282f1f48b..48c2a5611 100644
---
a/catalogs/catalog-jdbc-common/src/test/java/org/apache/gravitino/catalog/jdbc/operation/SqliteDatabaseOperations.java
+++
b/catalogs/catalog-jdbc-common/src/test/java/org/apache/gravitino/catalog/jdbc/operation/SqliteDatabaseOperations.java
@@ -24,9 +24,11 @@ import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.io.FileUtils;
import org.apache.gravitino.catalog.jdbc.JdbcSchema;
@@ -91,6 +93,16 @@ public class SqliteDatabaseOperations extends
JdbcDatabaseOperations {
return null;
}
+ @Override
+ protected boolean supportSchemaComment() {
+ return false;
+ }
+
+ @Override
+ protected Set<String> createSysDatabaseNameSet() {
+ return Collections.emptySet();
+ }
+
@Override
public boolean delete(String databaseName, boolean cascade) throws
NoSuchSchemaException {
return delete(databaseName);
diff --git
a/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisDatabaseOperations.java
b/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisDatabaseOperations.java
index bc14611e5..1f0a37c15 100644
---
a/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisDatabaseOperations.java
+++
b/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisDatabaseOperations.java
@@ -40,9 +40,6 @@ import org.apache.gravitino.meta.AuditInfo;
public class DorisDatabaseOperations extends JdbcDatabaseOperations {
public static final String COMMENT_KEY = "comment";
- private static final Set<String> DORIS_SYSTEM_DATABASE_NAMES =
- ImmutableSet.of("information_schema");
-
@Override
public String generateCreateDatabaseSql(
String databaseName, String comment, Map<String, String> properties) {
@@ -137,7 +134,12 @@ public class DorisDatabaseOperations extends
JdbcDatabaseOperations {
}
@Override
- protected boolean isSystemDatabase(String dbName) {
- return DORIS_SYSTEM_DATABASE_NAMES.contains(dbName);
+ protected boolean supportSchemaComment() {
+ return true;
+ }
+
+ @Override
+ protected Set<String> createSysDatabaseNameSet() {
+ return ImmutableSet.of("information_schema");
}
}
diff --git
a/catalogs/catalog-jdbc-mysql/src/main/java/org/apache/gravitino/catalog/mysql/operation/MysqlDatabaseOperations.java
b/catalogs/catalog-jdbc-mysql/src/main/java/org/apache/gravitino/catalog/mysql/operation/MysqlDatabaseOperations.java
index ce39a9d26..cb6206b72 100644
---
a/catalogs/catalog-jdbc-mysql/src/main/java/org/apache/gravitino/catalog/mysql/operation/MysqlDatabaseOperations.java
+++
b/catalogs/catalog-jdbc-mysql/src/main/java/org/apache/gravitino/catalog/mysql/operation/MysqlDatabaseOperations.java
@@ -18,106 +18,20 @@
*/
package org.apache.gravitino.catalog.mysql.operation;
-import com.google.common.collect.ImmutableMap;
-import java.sql.Connection;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
+import com.google.common.collect.ImmutableSet;
import java.util.Set;
-import org.apache.commons.collections4.MapUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.gravitino.StringIdentifier;
-import org.apache.gravitino.catalog.jdbc.JdbcSchema;
import org.apache.gravitino.catalog.jdbc.operation.JdbcDatabaseOperations;
-import org.apache.gravitino.exceptions.NoSuchSchemaException;
-import org.apache.gravitino.meta.AuditInfo;
/** Database operations for MySQL. */
public class MysqlDatabaseOperations extends JdbcDatabaseOperations {
- public static final Set<String> SYS_MYSQL_DATABASE_NAMES =
createSysMysqlDatabaseNames();
-
- private static Set<String> createSysMysqlDatabaseNames() {
- Set<String> set = new HashSet<>();
- set.add("information_schema");
- set.add("mysql");
- set.add("performance_schema");
- set.add("sys");
- return Collections.unmodifiableSet(set);
- }
-
- @Override
- public String generateCreateDatabaseSql(
- String databaseName, String comment, Map<String, String> properties) {
- String originComment = StringIdentifier.removeIdFromComment(comment);
- if (StringUtils.isNotEmpty(originComment)) {
- throw new UnsupportedOperationException(
- "MySQL doesn't support set schema comment: " + originComment);
- }
- StringBuilder sqlBuilder = new StringBuilder("CREATE DATABASE ");
-
- // Append database name
- sqlBuilder.append("`").append(databaseName).append("`");
- // Append options
- if (MapUtils.isNotEmpty(properties)) {
- // TODO #804 Properties will be unified in the future.
- throw new UnsupportedOperationException("Properties are not supported
yet");
- }
- String result = sqlBuilder.toString();
- LOG.info("Generated create database:{} sql: {}", databaseName, result);
- return result;
- }
-
- @Override
- public String generateDropDatabaseSql(String databaseName, boolean cascade) {
- final String dropDatabaseSql = "DROP DATABASE `" + databaseName + "`";
- if (cascade) {
- return dropDatabaseSql;
- }
-
- try (final Connection connection = this.dataSource.getConnection()) {
- String query = "SHOW TABLES IN `" + databaseName + "`";
- try (Statement statement = connection.createStatement()) {
- // Execute the query and check if there exists any tables in the
database
- try (ResultSet resultSet = statement.executeQuery(query)) {
- if (resultSet.next()) {
- throw new IllegalStateException(
- String.format(
- "Database %s is not empty, the value of cascade should be
true.",
- databaseName));
- }
- }
- }
- } catch (SQLException sqlException) {
- throw this.exceptionMapper.toGravitinoException(sqlException);
- }
- return dropDatabaseSql;
- }
-
@Override
- public JdbcSchema load(String databaseName) throws NoSuchSchemaException {
- List<String> allDatabases = listDatabases();
- String dbName =
- allDatabases.stream()
- .filter(db -> db.equals(databaseName))
- .findFirst()
- .orElseThrow(
- () -> new NoSuchSchemaException("Database %s could not be
found", databaseName));
-
- return JdbcSchema.builder()
- .withName(dbName)
- .withProperties(ImmutableMap.of())
- .withAuditInfo(AuditInfo.EMPTY)
- .build();
+ protected boolean supportSchemaComment() {
+ return false;
}
@Override
- protected boolean isSystemDatabase(String dbName) {
- return SYS_MYSQL_DATABASE_NAMES.contains(dbName.toLowerCase(Locale.ROOT));
+ protected Set<String> createSysDatabaseNameSet() {
+ return ImmutableSet.of("information_schema", "mysql", "sys",
"performance_schema");
}
}
diff --git
a/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/integration/test/CatalogMysqlIT.java
b/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/integration/test/CatalogMysqlIT.java
index bfa529452..710037205 100644
---
a/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/integration/test/CatalogMysqlIT.java
+++
b/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/integration/test/CatalogMysqlIT.java
@@ -1148,7 +1148,7 @@ public class CatalogMysqlIT extends AbstractIT {
UnsupportedOperationException.class,
() -> catalog.asSchemas().createSchema(testSchemaName, "comment",
null));
Assertions.assertTrue(
- exception.getMessage().contains("MySQL doesn't support set schema
comment: comment"));
+ exception.getMessage().contains("Doesn't support setting schema
comment: comment"));
// test null comment
String testSchemaName2 = "test2";
diff --git
a/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/operation/TestMysqlDatabaseOperations.java
b/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/operation/TestMysqlDatabaseOperations.java
index 6c14dfc0c..1ad704016 100644
---
a/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/operation/TestMysqlDatabaseOperations.java
+++
b/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/operation/TestMysqlDatabaseOperations.java
@@ -18,8 +18,6 @@
*/
package org.apache.gravitino.catalog.mysql.operation;
-import static
org.apache.gravitino.catalog.mysql.operation.MysqlDatabaseOperations.SYS_MYSQL_DATABASE_NAMES;
-
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -45,8 +43,11 @@ public class TestMysqlDatabaseOperations extends TestMysql {
// Mysql database creation does not support incoming comments.
String comment = null;
List<String> databases = DATABASE_OPERATIONS.listDatabases();
- SYS_MYSQL_DATABASE_NAMES.forEach(
- sysMysqlDatabaseName ->
Assertions.assertFalse(databases.contains(sysMysqlDatabaseName)));
+ ((MysqlDatabaseOperations) DATABASE_OPERATIONS)
+ .createSysDatabaseNameSet()
+ .forEach(
+ sysMysqlDatabaseName ->
+
Assertions.assertFalse(databases.contains(sysMysqlDatabaseName)));
testBaseOperation(databaseName, properties, comment);
testDropDatabase(databaseName);
}
diff --git a/catalogs/catalog-jdbc-oceanbase/build.gradle.kts
b/catalogs/catalog-jdbc-oceanbase/build.gradle.kts
index 2183edbd9..0df3b2199 100644
--- a/catalogs/catalog-jdbc-oceanbase/build.gradle.kts
+++ b/catalogs/catalog-jdbc-oceanbase/build.gradle.kts
@@ -45,4 +45,66 @@ dependencies {
implementation(libs.commons.collections4)
implementation(libs.commons.lang3)
implementation(libs.guava)
+
+ testImplementation(project(":catalogs:catalog-jdbc-common", "testArtifacts"))
+ testImplementation(project(":clients:client-java"))
+ testImplementation(project(":integration-test-common", "testArtifacts"))
+ testImplementation(project(":server"))
+ testImplementation(project(":server-common"))
+
+ testImplementation(libs.junit.jupiter.api)
+ testImplementation(libs.junit.jupiter.params)
+ testImplementation(libs.mysql.driver)
+ testImplementation(libs.testcontainers)
+ testImplementation(libs.testcontainers.mysql)
+
+ testRuntimeOnly(libs.junit.jupiter.engine)
+}
+
+tasks {
+ val runtimeJars by registering(Copy::class) {
+ from(configurations.runtimeClasspath)
+ into("build/libs")
+ }
+
+ val copyCatalogLibs by registering(Copy::class) {
+ dependsOn("jar", "runtimeJars")
+ from("build/libs") {
+ exclude("guava-*.jar")
+ exclude("log4j-*.jar")
+ exclude("slf4j-*.jar")
+ }
+ into("$rootDir/distribution/package/catalogs/jdbc-oceanbase/libs")
+ }
+
+ val copyCatalogConfig by registering(Copy::class) {
+ from("src/main/resources")
+ into("$rootDir/distribution/package/catalogs/jdbc-oceanbase/conf")
+
+ include("jdbc-oceanbase.conf")
+
+ exclude { details ->
+ details.file.isDirectory()
+ }
+
+ fileMode = 0b111101101
+ }
+
+ register("copyLibAndConfig", Copy::class) {
+ dependsOn(copyCatalogLibs, copyCatalogConfig)
+ }
+}
+
+tasks.test {
+ val skipITs = project.hasProperty("skipITs")
+ if (skipITs) {
+ // Exclude integration tests
+ exclude("**/integration/test/**")
+ } else {
+ dependsOn(tasks.jar)
+ }
+}
+
+tasks.getByName("generateMetadataFileForMavenJavaPublication") {
+ dependsOn("runtimeJars")
}
diff --git
a/catalogs/catalog-jdbc-oceanbase/src/main/java/org/apache/gravitino/catalog/oceanbase/operation/OceanBaseDatabaseOperations.java
b/catalogs/catalog-jdbc-oceanbase/src/main/java/org/apache/gravitino/catalog/oceanbase/operation/OceanBaseDatabaseOperations.java
index a2eff259b..8b455b374 100644
---
a/catalogs/catalog-jdbc-oceanbase/src/main/java/org/apache/gravitino/catalog/oceanbase/operation/OceanBaseDatabaseOperations.java
+++
b/catalogs/catalog-jdbc-oceanbase/src/main/java/org/apache/gravitino/catalog/oceanbase/operation/OceanBaseDatabaseOperations.java
@@ -18,48 +18,20 @@
*/
package org.apache.gravitino.catalog.oceanbase.operation;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Locale;
-import java.util.Map;
+import com.google.common.collect.ImmutableSet;
import java.util.Set;
-import org.apache.gravitino.catalog.jdbc.JdbcSchema;
import org.apache.gravitino.catalog.jdbc.operation.JdbcDatabaseOperations;
-import org.apache.gravitino.exceptions.NoSuchSchemaException;
/** Database operations for OceanBase. */
public class OceanBaseDatabaseOperations extends JdbcDatabaseOperations {
- public static final Set<String> SYS_OCEANBASE_DATABASE_NAMES =
createSysOceanBaseDatabaseNames();
-
- private static Set<String> createSysOceanBaseDatabaseNames() {
- Set<String> set = new HashSet<>();
- set.add("information_schema");
- set.add("mysql");
- set.add("sys");
- set.add("oceanbase");
- return Collections.unmodifiableSet(set);
- }
-
- @Override
- public String generateCreateDatabaseSql(
- String databaseName, String comment, Map<String, String> properties) {
-
- throw new UnsupportedOperationException("Not implemented yet.");
- }
-
- @Override
- public String generateDropDatabaseSql(String databaseName, boolean cascade) {
- throw new UnsupportedOperationException("Not implemented yet.");
- }
-
@Override
- public JdbcSchema load(String databaseName) throws NoSuchSchemaException {
- throw new UnsupportedOperationException("Not implemented yet.");
+ protected boolean supportSchemaComment() {
+ return false;
}
@Override
- protected boolean isSystemDatabase(String dbName) {
- return
SYS_OCEANBASE_DATABASE_NAMES.contains(dbName.toLowerCase(Locale.ROOT));
+ protected Set<String> createSysDatabaseNameSet() {
+ return ImmutableSet.of("information_schema", "mysql", "sys", "oceanbase");
}
}
diff --git
a/catalogs/catalog-jdbc-oceanbase/src/test/java/org/apache/gravitino/catalog/oceanbase/operation/TestOceanBase.java
b/catalogs/catalog-jdbc-oceanbase/src/test/java/org/apache/gravitino/catalog/oceanbase/operation/TestOceanBase.java
new file mode 100644
index 000000000..f904a068b
--- /dev/null
+++
b/catalogs/catalog-jdbc-oceanbase/src/test/java/org/apache/gravitino/catalog/oceanbase/operation/TestOceanBase.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.catalog.oceanbase.operation;
+
+import com.google.common.collect.Maps;
+import java.util.Collections;
+import java.util.Map;
+import org.apache.gravitino.catalog.jdbc.TestJdbc;
+import org.apache.gravitino.catalog.jdbc.config.JdbcConfig;
+import org.apache.gravitino.catalog.jdbc.utils.DataSourceUtils;
+import
org.apache.gravitino.catalog.oceanbase.converter.OceanBaseColumnDefaultValueConverter;
+import
org.apache.gravitino.catalog.oceanbase.converter.OceanBaseExceptionConverter;
+import org.apache.gravitino.catalog.oceanbase.converter.OceanBaseTypeConverter;
+import org.apache.gravitino.integration.test.container.ContainerSuite;
+import org.apache.gravitino.integration.test.container.OceanBaseContainer;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+
+public class TestOceanBase extends TestJdbc {
+ private static final ContainerSuite containerSuite =
ContainerSuite.getInstance();
+ protected static final String DRIVER_CLASS_NAME = "com.mysql.jdbc.Driver";
+
+ @BeforeAll
+ public static void startup() {
+ containerSuite.startOceanBaseContainer();
+
+ DATA_SOURCE =
DataSourceUtils.createDataSource(getOceanBaseCatalogProperties());
+
+ DATABASE_OPERATIONS = new OceanBaseDatabaseOperations();
+ TABLE_OPERATIONS = new OceanBaseTableOperations();
+ JDBC_EXCEPTION_CONVERTER = new OceanBaseExceptionConverter();
+ DATABASE_OPERATIONS.initialize(DATA_SOURCE, JDBC_EXCEPTION_CONVERTER,
Collections.emptyMap());
+ TABLE_OPERATIONS.initialize(
+ DATA_SOURCE,
+ JDBC_EXCEPTION_CONVERTER,
+ new OceanBaseTypeConverter(),
+ new OceanBaseColumnDefaultValueConverter(),
+ Collections.emptyMap());
+ }
+
+ // Overwrite the stop method to close the data source and stop the container
+ @AfterAll
+ public static void stop() {
+ DataSourceUtils.closeDataSource(DATA_SOURCE);
+ if (null != CONTAINER) {
+ CONTAINER.stop();
+ }
+ }
+
+ private static Map<String, String> getOceanBaseCatalogProperties() {
+ Map<String, String> catalogProperties = Maps.newHashMap();
+
+ OceanBaseContainer oceanBaseContainer =
containerSuite.getOceanBaseContainer();
+
+ String jdbcUrl = oceanBaseContainer.getJdbcUrl();
+
+ catalogProperties.put(JdbcConfig.JDBC_URL.getKey(), jdbcUrl);
+ catalogProperties.put(JdbcConfig.JDBC_DRIVER.getKey(), DRIVER_CLASS_NAME);
+ catalogProperties.put(JdbcConfig.USERNAME.getKey(),
OceanBaseContainer.USER_NAME);
+ catalogProperties.put(JdbcConfig.PASSWORD.getKey(),
OceanBaseContainer.PASSWORD);
+
+ return catalogProperties;
+ }
+}
diff --git
a/catalogs/catalog-jdbc-oceanbase/src/test/java/org/apache/gravitino/catalog/oceanbase/operation/TestOceanBaseDatabaseOperations.java
b/catalogs/catalog-jdbc-oceanbase/src/test/java/org/apache/gravitino/catalog/oceanbase/operation/TestOceanBaseDatabaseOperations.java
new file mode 100644
index 000000000..df27c6b70
--- /dev/null
+++
b/catalogs/catalog-jdbc-oceanbase/src/test/java/org/apache/gravitino/catalog/oceanbase/operation/TestOceanBaseDatabaseOperations.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.catalog.oceanbase.operation;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.gravitino.utils.RandomNameUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+@Tag("gravitino-docker-test")
+public class TestOceanBaseDatabaseOperations extends TestOceanBase {
+
+ @Test
+ public void testBaseOperationDatabase() {
+ String databaseName = RandomNameUtils.genRandomName("ct_db");
+ Map<String, String> properties = new HashMap<>();
+ // OceanBase database creation does not support incoming comments.
+ String comment = null;
+ List<String> databases = DATABASE_OPERATIONS.listDatabases();
+ ((OceanBaseDatabaseOperations) DATABASE_OPERATIONS)
+ .createSysDatabaseNameSet()
+ .forEach(
+ sysOceanBaseDatabaseName ->
+
Assertions.assertFalse(databases.contains(sysOceanBaseDatabaseName)));
+ testBaseOperation(databaseName, properties, comment);
+ }
+
+ @Test
+ void testDropDatabaseWithSpecificName() {
+ String databaseName = RandomNameUtils.genRandomName("ct_db") + "-abc-" +
"end";
+ Map<String, String> properties = new HashMap<>();
+ DATABASE_OPERATIONS.create(databaseName, null, properties);
+ Assertions.assertTrue(DATABASE_OPERATIONS.delete(databaseName, false));
+ }
+}
diff --git
a/catalogs/catalog-jdbc-postgresql/src/main/java/org/apache/gravitino/catalog/postgresql/operation/PostgreSqlSchemaOperations.java
b/catalogs/catalog-jdbc-postgresql/src/main/java/org/apache/gravitino/catalog/postgresql/operation/PostgreSqlSchemaOperations.java
index 8875ac38b..be3c5d627 100644
---
a/catalogs/catalog-jdbc-postgresql/src/main/java/org/apache/gravitino/catalog/postgresql/operation/PostgreSqlSchemaOperations.java
+++
b/catalogs/catalog-jdbc-postgresql/src/main/java/org/apache/gravitino/catalog/postgresql/operation/PostgreSqlSchemaOperations.java
@@ -20,6 +20,7 @@ package org.apache.gravitino.catalog.postgresql.operation;
import static
org.apache.gravitino.catalog.postgresql.operation.PostgreSqlTableOperations.PG_QUOTE;
+import com.google.common.collect.ImmutableSet;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
@@ -27,9 +28,7 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -46,16 +45,6 @@ import org.apache.gravitino.meta.AuditInfo;
/** Database operations for PostgreSQL. */
public class PostgreSqlSchemaOperations extends JdbcDatabaseOperations {
- public static final Set<String> SYS_PG_DATABASE_NAMES =
- Collections.unmodifiableSet(
- new HashSet<String>() {
- {
- add("pg_toast");
- add("pg_catalog");
- add("information_schema");
- }
- });
-
private String database;
@Override
@@ -167,8 +156,13 @@ public class PostgreSqlSchemaOperations extends
JdbcDatabaseOperations {
}
@Override
- protected boolean isSystemDatabase(String dbName) {
- return SYS_PG_DATABASE_NAMES.contains(dbName.toLowerCase(Locale.ROOT));
+ protected boolean supportSchemaComment() {
+ return true;
+ }
+
+ @Override
+ protected Set<String> createSysDatabaseNameSet() {
+ return ImmutableSet.of("pg_toast", "pg_catalog", "information_schema");
}
private String getShowSchemaCommentSql(String schema) {
diff --git
a/catalogs/catalog-jdbc-postgresql/src/test/java/org/apache/gravitino/catalog/postgresql/operation/TestPostgreSqlSchemaOperations.java
b/catalogs/catalog-jdbc-postgresql/src/test/java/org/apache/gravitino/catalog/postgresql/operation/TestPostgreSqlSchemaOperations.java
index 198394b73..8ccf5902f 100644
---
a/catalogs/catalog-jdbc-postgresql/src/test/java/org/apache/gravitino/catalog/postgresql/operation/TestPostgreSqlSchemaOperations.java
+++
b/catalogs/catalog-jdbc-postgresql/src/test/java/org/apache/gravitino/catalog/postgresql/operation/TestPostgreSqlSchemaOperations.java
@@ -18,8 +18,6 @@
*/
package org.apache.gravitino.catalog.postgresql.operation;
-import static
org.apache.gravitino.catalog.postgresql.operation.PostgreSqlSchemaOperations.SYS_PG_DATABASE_NAMES;
-
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
@@ -49,8 +47,10 @@ public class TestPostgreSqlSchemaOperations extends
TestPostgreSql {
String comment = null;
List<String> initDatabases = DATABASE_OPERATIONS.listDatabases();
- SYS_PG_DATABASE_NAMES.forEach(
- sysPgDatabaseName ->
Assertions.assertFalse(initDatabases.contains(sysPgDatabaseName)));
+ ((PostgreSqlSchemaOperations) DATABASE_OPERATIONS)
+ .createSysDatabaseNameSet()
+ .forEach(
+ sysPgDatabaseName ->
Assertions.assertFalse(initDatabases.contains(sysPgDatabaseName)));
testBaseOperation(databaseName, properties, comment);
// delete database.
diff --git
a/integration-test-common/src/test/java/org/apache/gravitino/integration/test/container/ContainerSuite.java
b/integration-test-common/src/test/java/org/apache/gravitino/integration/test/container/ContainerSuite.java
index 12a88bbd9..14398b4b2 100644
---
a/integration-test-common/src/test/java/org/apache/gravitino/integration/test/container/ContainerSuite.java
+++
b/integration-test-common/src/test/java/org/apache/gravitino/integration/test/container/ContainerSuite.java
@@ -70,6 +70,7 @@ public class ContainerSuite implements Closeable {
private static volatile MySQLContainer mySQLVersion5Container;
private static volatile Map<PGImageName, PostgreSQLContainer> pgContainerMap
=
new EnumMap<>(PGImageName.class);
+ private static volatile OceanBaseContainer oceanBaseContainer;
private static volatile GravitinoLocalStackContainer
gravitinoLocalStackContainer;
@@ -380,6 +381,36 @@ public class ContainerSuite implements Closeable {
startPostgreSQLContainer(testDatabaseName, PGImageName.VERSION_13);
}
+ public void startOceanBaseContainer() {
+ if (oceanBaseContainer == null) {
+ synchronized (ContainerSuite.class) {
+ if (oceanBaseContainer == null) {
+ // Start OceanBase container
+ OceanBaseContainer.Builder oceanBaseBuilder =
+ OceanBaseContainer.builder()
+ .withHostName("gravitino-ci-oceanbase")
+ .withEnvVars(
+ ImmutableMap.of(
+ "MODE",
+ "mini",
+ "OB_SYS_PASSWORD",
+ OceanBaseContainer.PASSWORD,
+ "OB_TENANT_PASSWORD",
+ OceanBaseContainer.PASSWORD,
+ "OB_DATAFILE_SIZE",
+ "2G",
+ "OB_LOG_DISK_SIZE",
+ "4G"))
+ .withNetwork(network)
+
.withExposePorts(ImmutableSet.of(OceanBaseContainer.OCEANBASE_PORT));
+ OceanBaseContainer container =
closer.register(oceanBaseBuilder.build());
+ container.start();
+ oceanBaseContainer = container;
+ }
+ }
+ }
+ }
+
public void startKafkaContainer() {
if (kafkaContainer == null) {
synchronized (ContainerSuite.class) {
@@ -505,6 +536,10 @@ public class ContainerSuite implements Closeable {
return pgContainerMap.get(pgImageName);
}
+ public OceanBaseContainer getOceanBaseContainer() {
+ return oceanBaseContainer;
+ }
+
// Let containers assign addresses in a fixed subnet to avoid
// `mac-docker-connector` needing to
// refresh the configuration
diff --git
a/integration-test-common/src/test/java/org/apache/gravitino/integration/test/container/OceanBaseContainer.java
b/integration-test-common/src/test/java/org/apache/gravitino/integration/test/container/OceanBaseContainer.java
new file mode 100644
index 000000000..c52a9a45b
--- /dev/null
+++
b/integration-test-common/src/test/java/org/apache/gravitino/integration/test/container/OceanBaseContainer.java
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.integration.test.container;
+
+import static java.lang.String.format;
+import static org.testcontainers.shaded.org.awaitility.Awaitility.await;
+
+import com.google.common.collect.ImmutableSet;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.time.Duration;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import org.rnorth.ducttape.Preconditions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.Network;
+import org.testcontainers.containers.wait.strategy.Wait;
+
+public class OceanBaseContainer extends BaseContainer {
+ public static final Logger LOG =
LoggerFactory.getLogger(OceanBaseContainer.class);
+
+ public static final String DEFAULT_IMAGE =
"oceanbase/oceanbase-ce:4.2.1-lts";
+ public static final String HOST_NAME = "gravitino-ci-oceanbase";
+ public static final int OCEANBASE_PORT = 2881;
+ public static final String USER_NAME = "root@test";
+ public static final String PASSWORD = "123456";
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ protected OceanBaseContainer(
+ String image,
+ String hostName,
+ Set<Integer> ports,
+ Map<String, String> extraHosts,
+ Map<String, String> filesToMount,
+ Map<String, String> envVars,
+ Optional<Network> network) {
+ super(image, hostName, ports, extraHosts, filesToMount, envVars, network);
+ }
+
+ @Override
+ protected void setupContainer() {
+ super.setupContainer();
+ withLogConsumer(new PrintingContainerLog(format("%-14s| ",
"OceanBaseContainer")));
+ waitingForLog();
+ withStartupTimeout(Duration.ofMinutes(6));
+ }
+
+ @Override
+ public void start() {
+ super.start();
+ Preconditions.check("OceanBase container startup failed!",
checkContainerStatus(5));
+ }
+
+ @Override
+ protected boolean checkContainerStatus(int retryLimit) {
+ String oceanBaseJdbcUrl = format("jdbc:mysql://%s:%d", "localhost",
getMappedPort());
+ LOG.info("OceanBase url is {}", oceanBaseJdbcUrl);
+
+ await()
+ .atMost(30, TimeUnit.SECONDS)
+ .pollInterval(30 / retryLimit, TimeUnit.SECONDS)
+ .until(
+ () -> {
+ try (Connection connection =
+ DriverManager.getConnection(oceanBaseJdbcUrl,
"root@sys", PASSWORD);
+ Statement statement = connection.createStatement()) {
+
+ // check if OceanBase server is ready
+ String query = "SELECT stop_time,status FROM
oceanbase.DBA_OB_SERVERS;";
+ try (ResultSet resultSet = statement.executeQuery(query)) {
+ while (resultSet.next()) {
+ String stopTime = resultSet.getString("stop_time");
+ String status = resultSet.getString("status");
+
+ if (Objects.isNull(stopTime) && "ACTIVE".equals(status)) {
+ LOG.info("OceanBase container startup success!");
+ return true;
+ }
+ }
+ }
+ LOG.info("OceanBase container is not ready yet!");
+ } catch (Exception e) {
+ LOG.error(e.getMessage(), e);
+ }
+ return false;
+ });
+
+ return true;
+ }
+
+ public void waitingForLog() {
+ super.container.waitingFor(Wait.forLogMessage(".*boot success!.*", 1));
+ }
+
+ public String getUsername() {
+ return USER_NAME;
+ }
+
+ public String getPassword() {
+ return PASSWORD;
+ }
+
+ public int getMappedPort() {
+ return getMappedPort(OCEANBASE_PORT);
+ }
+
+ public String getJdbcUrl() {
+ return format("jdbc:mysql://%s:%d", getContainerIpAddress(),
OCEANBASE_PORT);
+ }
+
+ public String getJdbcUrl(String testDatabaseName) {
+ return format(
+ "jdbc:mysql://%s:%d/%s", getContainerIpAddress(), OCEANBASE_PORT,
testDatabaseName);
+ }
+
+ public String getDriverClassName(String testDatabaseName) throws
SQLException {
+ return
DriverManager.getDriver(getJdbcUrl(testDatabaseName)).getClass().getName();
+ }
+
+ public static class Builder extends BaseContainer.Builder<Builder,
OceanBaseContainer> {
+
+ private Builder() {
+ this.image = DEFAULT_IMAGE;
+ this.hostName = HOST_NAME;
+ this.exposePorts = ImmutableSet.of(OCEANBASE_PORT);
+ }
+
+ @Override
+ public OceanBaseContainer build() {
+ return new OceanBaseContainer(
+ image, hostName, exposePorts, extraHosts, filesToMount, envVars,
network);
+ }
+ }
+}