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);
+    }
+  }
+}


Reply via email to