This is an automated email from the ASF dual-hosted git repository.

yuqi4733 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 771099defa [#9913] feat(catalog-jdbc-hologres): Add Hologres JDBC 
catalog module skeleton (#10066)
771099defa is described below

commit 771099defa94a3a4fccecf413fed7106bb5b8bab
Author: Ye Ding <[email protected]>
AuthorDate: Mon Mar 2 19:46:36 2026 +0800

    [#9913] feat(catalog-jdbc-hologres): Add Hologres JDBC catalog module 
skeleton (#10066)
    
    ### What changes were proposed in this pull request?
    
    Add the Hologres JDBC catalog module skeleton under
    `catalogs-contrib/catalog-jdbc-hologres/`, including:
    - Build configuration and Gradle module registration
    - `HologresCatalog`, `HologresCatalogOperations`,
    `HologresCatalogCapability`
    - `HologresTypeConverter`, `HologresColumnConverter`,
    `HologresExceptionConverter`
    - SPI service provider configuration
    - Stub implementations for `HologresSchemaOperations` and
    `HologresTableOperations` (full implementations in follow-up PRs)
    - Unit tests: `TestHologresTypeConverter`,
    `TestHologresColumnConverter`, `TestHologresExceptionConverter`,
    `TestHologresCatalogCapability`
    
    ### Why are the changes needed?
    
    Hologres is Alibaba Cloud's real-time data warehouse service compatible
    with PostgreSQL. This PR sets up the foundational module structure for
    the Hologres JDBC catalog in Gravitino.
    
    Fix: https://github.com/apache/gravitino/issues/9913
    
    ### Does this PR introduce _any_ user-facing change?
    
    No. Schema and table operations are stubbed out and will be enabled in
    follow-up PRs.
    
    ### How was this patch tested?
    
    - Unit tests for type converter, column converter, exception converter,
    and catalog capability.
    - `./gradlew :catalogs-contrib:catalog-jdbc-hologres:test -PskipITs`
    passes.
---
 build.gradle.kts                                   |   1 +
 .../catalog-jdbc-hologres/build.gradle.kts         | 111 +++++++++
 .../catalog/hologres/HologresCatalog.java          |  91 +++++++
 .../hologres/HologresCatalogCapability.java        |  63 +++++
 .../hologres/HologresCatalogOperations.java        |  64 +++++
 .../HologresColumnDefaultValueConverter.java       | 132 ++++++++++
 .../converter/HologresExceptionConverter.java      |  86 +++++++
 .../hologres/converter/HologresTypeConverter.java  | 184 ++++++++++++++
 .../operation/HologresSchemaOperations.java        |  56 +++++
 .../operation/HologresTableOperations.java         |  75 ++++++
 .../services/org.apache.gravitino.CatalogProvider  |  19 ++
 .../src/main/resources/jdbc-hologres.conf          |  34 +++
 .../hologres/TestHologresCatalogCapability.java    | 146 +++++++++++
 .../TestHologresColumnDefaultValueConverter.java   | 209 ++++++++++++++++
 .../converter/TestHologresExceptionConverter.java  | 126 ++++++++++
 .../converter/TestHologresTypeConverter.java       | 269 +++++++++++++++++++++
 .../src/test/resources/log4j2.properties           |  30 +++
 settings.gradle.kts                                |   1 +
 18 files changed, 1697 insertions(+)

diff --git a/build.gradle.kts b/build.gradle.kts
index 19029deffe..331993b2ac 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1119,6 +1119,7 @@ tasks {
       ":catalogs:hive-metastore2-libs:copyLibs",
       ":catalogs:hive-metastore3-libs:copyLibs",
       ":catalogs:catalog-lakehouse-generic:copyLibAndConfig",
+      ":catalogs-contrib:catalog-jdbc-hologres:copyLibAndConfig",
       ":catalogs-contrib:catalog-jdbc-oceanbase:copyLibAndConfig",
       ":catalogs-contrib:catalog-jdbc-clickhouse:copyLibAndConfig"
     )
diff --git a/catalogs-contrib/catalog-jdbc-hologres/build.gradle.kts 
b/catalogs-contrib/catalog-jdbc-hologres/build.gradle.kts
new file mode 100644
index 0000000000..132c89a691
--- /dev/null
+++ b/catalogs-contrib/catalog-jdbc-hologres/build.gradle.kts
@@ -0,0 +1,111 @@
+/*
+ * 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.
+ */
+description = "catalog-jdbc-hologres"
+
+plugins {
+  `maven-publish`
+  id("java")
+  id("idea")
+}
+
+dependencies {
+  implementation(project(":api")) {
+    exclude(group = "*")
+  }
+  implementation(project(":catalogs:catalog-common")) {
+    exclude(group = "*")
+  }
+  implementation(project(":catalogs:catalog-jdbc-common")) {
+    exclude(group = "*")
+  }
+  implementation(project(":common")) {
+    exclude(group = "*")
+  }
+  implementation(project(":core")) {
+    exclude(group = "*")
+  }
+
+  implementation(libs.bundles.log4j)
+  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.awaitility)
+  testImplementation(libs.junit.jupiter.api)
+  testImplementation(libs.junit.jupiter.params)
+  testImplementation(libs.postgresql.driver)
+  testImplementation(libs.testcontainers)
+  testImplementation(libs.testcontainers.postgresql)
+
+  testRuntimeOnly(libs.junit.jupiter.engine)
+}
+
+tasks {
+  register("runtimeJars", 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-hologres/libs")
+  }
+
+  val copyCatalogConfig by registering(Copy::class) {
+    from("src/main/resources")
+    into("$rootDir/distribution/package/catalogs/jdbc-hologres/conf")
+
+    include("jdbc-hologres.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-contrib/catalog-jdbc-hologres/src/main/java/org/apache/gravitino/catalog/hologres/HologresCatalog.java
 
b/catalogs-contrib/catalog-jdbc-hologres/src/main/java/org/apache/gravitino/catalog/hologres/HologresCatalog.java
new file mode 100644
index 0000000000..be4f079061
--- /dev/null
+++ 
b/catalogs-contrib/catalog-jdbc-hologres/src/main/java/org/apache/gravitino/catalog/hologres/HologresCatalog.java
@@ -0,0 +1,91 @@
+/*
+ * 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.hologres;
+
+import java.util.Map;
+import 
org.apache.gravitino.catalog.hologres.converter.HologresColumnDefaultValueConverter;
+import 
org.apache.gravitino.catalog.hologres.converter.HologresExceptionConverter;
+import org.apache.gravitino.catalog.hologres.converter.HologresTypeConverter;
+import 
org.apache.gravitino.catalog.hologres.operation.HologresSchemaOperations;
+import org.apache.gravitino.catalog.hologres.operation.HologresTableOperations;
+import org.apache.gravitino.catalog.jdbc.JdbcCatalog;
+import 
org.apache.gravitino.catalog.jdbc.converter.JdbcColumnDefaultValueConverter;
+import org.apache.gravitino.catalog.jdbc.converter.JdbcExceptionConverter;
+import org.apache.gravitino.catalog.jdbc.converter.JdbcTypeConverter;
+import org.apache.gravitino.catalog.jdbc.operation.JdbcDatabaseOperations;
+import org.apache.gravitino.catalog.jdbc.operation.JdbcTableOperations;
+import org.apache.gravitino.connector.CatalogOperations;
+import org.apache.gravitino.connector.capability.Capability;
+
+/**
+ * Implementation of a Hologres catalog in Apache Gravitino.
+ *
+ * <p>Hologres is a real-time interactive analytics service developed by 
Alibaba Cloud that is fully
+ * compatible with PostgreSQL. It uses the PostgreSQL JDBC driver for 
connections.
+ *
+ * @see <a href="https://help.aliyun.com/zh/hologres";>Hologres 
Documentation</a>
+ */
+public class HologresCatalog extends JdbcCatalog {
+
+  @Override
+  public String shortName() {
+    return "jdbc-hologres";
+  }
+
+  @Override
+  protected CatalogOperations newOps(Map<String, String> config) {
+    JdbcTypeConverter jdbcTypeConverter = createJdbcTypeConverter();
+    return new HologresCatalogOperations(
+        createExceptionConverter(),
+        jdbcTypeConverter,
+        createJdbcDatabaseOperations(),
+        createJdbcTableOperations(),
+        createJdbcColumnDefaultValueConverter());
+  }
+
+  @Override
+  public Capability newCapability() {
+    return new HologresCatalogCapability();
+  }
+
+  @Override
+  protected JdbcExceptionConverter createExceptionConverter() {
+    return new HologresExceptionConverter();
+  }
+
+  @Override
+  protected JdbcTypeConverter createJdbcTypeConverter() {
+    return new HologresTypeConverter();
+  }
+
+  @Override
+  protected JdbcDatabaseOperations createJdbcDatabaseOperations() {
+    return new HologresSchemaOperations();
+  }
+
+  @Override
+  protected JdbcTableOperations createJdbcTableOperations() {
+    return new HologresTableOperations();
+  }
+
+  @Override
+  protected JdbcColumnDefaultValueConverter 
createJdbcColumnDefaultValueConverter() {
+    return new HologresColumnDefaultValueConverter();
+  }
+}
diff --git 
a/catalogs-contrib/catalog-jdbc-hologres/src/main/java/org/apache/gravitino/catalog/hologres/HologresCatalogCapability.java
 
b/catalogs-contrib/catalog-jdbc-hologres/src/main/java/org/apache/gravitino/catalog/hologres/HologresCatalogCapability.java
new file mode 100644
index 0000000000..9506d8147a
--- /dev/null
+++ 
b/catalogs-contrib/catalog-jdbc-hologres/src/main/java/org/apache/gravitino/catalog/hologres/HologresCatalogCapability.java
@@ -0,0 +1,63 @@
+/*
+ * 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.hologres;
+
+import java.util.Set;
+import org.apache.gravitino.connector.capability.Capability;
+import org.apache.gravitino.connector.capability.CapabilityResult;
+
+/**
+ * Defines the capabilities and naming rules for Hologres catalog.
+ *
+ * <p>Hologres is PostgreSQL-compatible, so naming rules follow PostgreSQL 
conventions.
+ */
+public class HologresCatalogCapability implements Capability {
+
+  /**
+   * Regular expression explanation: ^[_a-zA-Z\p{L}/][\w\p{L}-$/=]{0,62}$
+   *
+   * <p>^[_a-zA-Z\p{L}/] - Start with an underscore, a letter, or a letter 
from any language
+   *
+   * <p>[\w\p{L}-$/=]{0,62} - Consist of 0 to 62 characters (making the total 
length at most 63) of
+   * letters (both cases), digits, underscores, any kind of letter from any 
language, hyphens,
+   * dollar signs, slashes or equal signs
+   *
+   * <p>$ - End of the string
+   */
+  public static final String HOLOGRES_NAME_PATTERN = 
"^[_a-zA-Z\\p{L}/][\\w\\p{L}-$/=]{0,62}$";
+
+  /** Reserved schema and table names in Hologres that cannot be used for 
user-defined schemas. */
+  private static final Set<String> HOLOGRES_RESERVED_WORDS =
+      Set.of("pg_catalog", "information_schema", "hologres");
+
+  @Override
+  public CapabilityResult specificationOnName(Scope scope, String name) {
+    if (!name.matches(HOLOGRES_NAME_PATTERN)) {
+      return CapabilityResult.unsupported(
+          String.format("The %s name '%s' is illegal.", scope, name));
+    }
+
+    if (scope == Scope.SCHEMA && 
HOLOGRES_RESERVED_WORDS.contains(name.toLowerCase())) {
+      return CapabilityResult.unsupported(
+          String.format("The %s name '%s' is reserved and cannot be used.", 
scope, name));
+    }
+
+    return CapabilityResult.SUPPORTED;
+  }
+}
diff --git 
a/catalogs-contrib/catalog-jdbc-hologres/src/main/java/org/apache/gravitino/catalog/hologres/HologresCatalogOperations.java
 
b/catalogs-contrib/catalog-jdbc-hologres/src/main/java/org/apache/gravitino/catalog/hologres/HologresCatalogOperations.java
new file mode 100644
index 0000000000..135e1bd02e
--- /dev/null
+++ 
b/catalogs-contrib/catalog-jdbc-hologres/src/main/java/org/apache/gravitino/catalog/hologres/HologresCatalogOperations.java
@@ -0,0 +1,64 @@
+/*
+ * 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.hologres;
+
+import java.sql.Driver;
+import java.sql.DriverManager;
+import org.apache.gravitino.catalog.jdbc.JdbcCatalogOperations;
+import 
org.apache.gravitino.catalog.jdbc.converter.JdbcColumnDefaultValueConverter;
+import org.apache.gravitino.catalog.jdbc.converter.JdbcExceptionConverter;
+import org.apache.gravitino.catalog.jdbc.converter.JdbcTypeConverter;
+import org.apache.gravitino.catalog.jdbc.operation.JdbcDatabaseOperations;
+import org.apache.gravitino.catalog.jdbc.operation.JdbcTableOperations;
+
+/**
+ * Hologres catalog operations implementation.
+ *
+ * <p>Since Hologres uses the PostgreSQL JDBC driver, this class handles the 
driver deregistration
+ * properly to avoid memory leaks when the catalog is closed.
+ */
+public class HologresCatalogOperations extends JdbcCatalogOperations {
+
+  public HologresCatalogOperations(
+      JdbcExceptionConverter exceptionConverter,
+      JdbcTypeConverter jdbcTypeConverter,
+      JdbcDatabaseOperations databaseOperation,
+      JdbcTableOperations tableOperation,
+      JdbcColumnDefaultValueConverter columnDefaultValueConverter) {
+    super(
+        exceptionConverter,
+        jdbcTypeConverter,
+        databaseOperation,
+        tableOperation,
+        columnDefaultValueConverter);
+  }
+
+  @Override
+  public void close() {
+    super.close();
+    try {
+      // Unload the PostgreSQL driver, only unload the driver if it is loaded 
by
+      // IsolatedClassLoader. Hologres uses PostgreSQL JDBC driver.
+      Driver pgDriver = 
DriverManager.getDriver("jdbc:postgresql://dummy_address:12345/");
+      deregisterDriver(pgDriver);
+    } catch (Exception e) {
+      LOG.warn("Failed to deregister PostgreSQL driver for Hologres", e);
+    }
+  }
+}
diff --git 
a/catalogs-contrib/catalog-jdbc-hologres/src/main/java/org/apache/gravitino/catalog/hologres/converter/HologresColumnDefaultValueConverter.java
 
b/catalogs-contrib/catalog-jdbc-hologres/src/main/java/org/apache/gravitino/catalog/hologres/converter/HologresColumnDefaultValueConverter.java
new file mode 100644
index 0000000000..4cb166582f
--- /dev/null
+++ 
b/catalogs-contrib/catalog-jdbc-hologres/src/main/java/org/apache/gravitino/catalog/hologres/converter/HologresColumnDefaultValueConverter.java
@@ -0,0 +1,132 @@
+/*
+ * 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.hologres.converter;
+
+import static 
org.apache.gravitino.catalog.jdbc.converter.JdbcTypeConverter.VARCHAR;
+import static org.apache.gravitino.rel.Column.DEFAULT_VALUE_NOT_SET;
+import static 
org.apache.gravitino.rel.Column.DEFAULT_VALUE_OF_CURRENT_TIMESTAMP;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import 
org.apache.gravitino.catalog.jdbc.converter.JdbcColumnDefaultValueConverter;
+import org.apache.gravitino.catalog.jdbc.converter.JdbcTypeConverter;
+import org.apache.gravitino.rel.expressions.Expression;
+import org.apache.gravitino.rel.expressions.UnparsedExpression;
+import org.apache.gravitino.rel.expressions.literals.Literals;
+import org.apache.gravitino.rel.types.Decimal;
+
+/**
+ * Column default value converter for Hologres.
+ *
+ * <p>Hologres is PostgreSQL-compatible, so default value conversion follows 
PostgreSQL conventions.
+ */
+public class HologresColumnDefaultValueConverter extends 
JdbcColumnDefaultValueConverter {
+
+  public HologresColumnDefaultValueConverter() {}
+
+  // for example:
+  // '2021-10-01 00:00:00'::timestamp without time zone
+  // ''something'::character varying'
+  private static final Pattern VALUE_WITH_TYPE = 
Pattern.compile("'(.+?)'::\\s*(.+)");
+
+  // for example:
+  // NULL::character varying
+  private static final String NULL_PREFIX = "NULL::";
+
+  @Override
+  public String fromGravitino(Expression defaultValue) {
+    if (defaultValue instanceof UnparsedExpression) {
+      return ((UnparsedExpression) defaultValue).unparsedExpression();
+    }
+    return super.fromGravitino(defaultValue);
+  }
+
+  @Override
+  public Expression toGravitino(
+      JdbcTypeConverter.JdbcTypeBean type,
+      String columnDefaultValue,
+      boolean isExpression,
+      boolean nullable) {
+    if (columnDefaultValue == null) {
+      return nullable ? Literals.NULL : DEFAULT_VALUE_NOT_SET;
+    }
+
+    if (isExpression) {
+      // The parsing of Hologres (PostgreSQL) expressions is complex, so we 
are not currently
+      // undertaking the parsing.
+      return UnparsedExpression.of(columnDefaultValue);
+    }
+
+    try {
+      return parseLiteral(type, columnDefaultValue);
+    } catch (Exception e) {
+      if (columnDefaultValue.equals(CURRENT_TIMESTAMP)) {
+        return DEFAULT_VALUE_OF_CURRENT_TIMESTAMP;
+      }
+      return UnparsedExpression.of(columnDefaultValue);
+    }
+  }
+
+  private Expression parseLiteral(JdbcTypeConverter.JdbcTypeBean type, String 
columnDefaultValue) {
+    Matcher matcher = VALUE_WITH_TYPE.matcher(columnDefaultValue);
+    if (matcher.find()) {
+      columnDefaultValue = matcher.group(1);
+    }
+
+    if (columnDefaultValue.startsWith(NULL_PREFIX)) {
+      return Literals.NULL;
+    }
+
+    switch (type.getTypeName().toLowerCase()) {
+      case HologresTypeConverter.BOOL:
+        return Literals.booleanLiteral(Boolean.valueOf(columnDefaultValue));
+      case HologresTypeConverter.INT_2:
+        return Literals.shortLiteral(Short.valueOf(columnDefaultValue));
+      case HologresTypeConverter.INT_4:
+        return Literals.integerLiteral(Integer.valueOf(columnDefaultValue));
+      case HologresTypeConverter.INT_8:
+        return Literals.longLiteral(Long.valueOf(columnDefaultValue));
+      case HologresTypeConverter.FLOAT_4:
+        return Literals.floatLiteral(Float.valueOf(columnDefaultValue));
+      case HologresTypeConverter.FLOAT_8:
+        return Literals.doubleLiteral(Double.valueOf(columnDefaultValue));
+      case HologresTypeConverter.NUMERIC:
+        return Literals.decimalLiteral(
+            Decimal.of(columnDefaultValue, type.getColumnSize(), 
type.getScale()));
+      case JdbcTypeConverter.DATE:
+        return Literals.dateLiteral(LocalDate.parse(columnDefaultValue, 
DATE_FORMATTER));
+      case JdbcTypeConverter.TIME:
+        return Literals.timeLiteral(LocalTime.parse(columnDefaultValue, 
TIME_FORMATTER));
+      case JdbcTypeConverter.TIMESTAMP:
+      case HologresTypeConverter.TIMESTAMP_TZ:
+        return Literals.timestampLiteral(
+            LocalDateTime.parse(columnDefaultValue, DATE_TIME_FORMATTER));
+      case VARCHAR:
+        return Literals.varcharLiteral(type.getColumnSize(), 
columnDefaultValue);
+      case HologresTypeConverter.BPCHAR:
+      case JdbcTypeConverter.TEXT:
+        return Literals.stringLiteral(columnDefaultValue);
+      default:
+        throw new IllegalArgumentException("Unknown data type for literal: " + 
type);
+    }
+  }
+}
diff --git 
a/catalogs-contrib/catalog-jdbc-hologres/src/main/java/org/apache/gravitino/catalog/hologres/converter/HologresExceptionConverter.java
 
b/catalogs-contrib/catalog-jdbc-hologres/src/main/java/org/apache/gravitino/catalog/hologres/converter/HologresExceptionConverter.java
new file mode 100644
index 0000000000..acf3f0cb45
--- /dev/null
+++ 
b/catalogs-contrib/catalog-jdbc-hologres/src/main/java/org/apache/gravitino/catalog/hologres/converter/HologresExceptionConverter.java
@@ -0,0 +1,86 @@
+/*
+ * 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.hologres.converter;
+
+import java.sql.SQLException;
+import org.apache.gravitino.catalog.jdbc.converter.JdbcExceptionConverter;
+import org.apache.gravitino.exceptions.ConnectionFailedException;
+import org.apache.gravitino.exceptions.GravitinoRuntimeException;
+import org.apache.gravitino.exceptions.NoSuchSchemaException;
+import org.apache.gravitino.exceptions.NoSuchTableException;
+import org.apache.gravitino.exceptions.SchemaAlreadyExistsException;
+import org.apache.gravitino.exceptions.TableAlreadyExistsException;
+
+/**
+ * Exception converter for Hologres.
+ *
+ * <p>Hologres is PostgreSQL-compatible, so SQL error codes follow PostgreSQL 
conventions.
+ *
+ * @see <a 
href="https://www.postgresql.org/docs/current/errcodes-appendix.html#ERRCODES-TABLE";>
+ *     PostgreSQL Error Codes</a>
+ */
+public class HologresExceptionConverter extends JdbcExceptionConverter {
+
+  private static final String DUPLICATE_DATABASE = "42P04";
+  private static final String DUPLICATE_SCHEMA = "42P06";
+  private static final String DUPLICATE_TABLE = "42P07";
+  private static final String INVALID_SCHEMA_NAME = "3D000";
+  private static final String INVALID_SCHEMA = "3F000";
+  private static final String UNDEFINED_TABLE = "42P01";
+
+  /**
+   * SQLSTATE '08' is the class code of connection exceptions See <a
+   * 
href="https://www.postgresql.org/docs/current/errcodes-appendix.html#ERRCODES-TABLE";>PostgreSQL
+   * errcodes appendix</a>.
+   */
+  private static final String CONNECTION_EXCEPTION = "08";
+
+  @SuppressWarnings("FormatStringAnnotation")
+  @Override
+  public GravitinoRuntimeException toGravitinoException(SQLException se) {
+    String message = se.getMessage() != null ? se.getMessage() : "";
+
+    if (null != se.getSQLState()) {
+      switch (se.getSQLState()) {
+        case DUPLICATE_DATABASE:
+        case DUPLICATE_SCHEMA:
+          return new SchemaAlreadyExistsException(message, se);
+        case DUPLICATE_TABLE:
+          return new TableAlreadyExistsException(message, se);
+        case INVALID_SCHEMA_NAME:
+        case INVALID_SCHEMA:
+          return new NoSuchSchemaException(message, se);
+        case UNDEFINED_TABLE:
+          return new NoSuchTableException(message, se);
+        default:
+          {
+            if (se.getSQLState().startsWith(CONNECTION_EXCEPTION)) {
+              return new ConnectionFailedException(message, se);
+            }
+            return new GravitinoRuntimeException(message, se);
+          }
+      }
+    } else {
+      if (message.contains("password authentication failed")) {
+        return new ConnectionFailedException(message, se);
+      }
+      return new GravitinoRuntimeException(message, se);
+    }
+  }
+}
diff --git 
a/catalogs-contrib/catalog-jdbc-hologres/src/main/java/org/apache/gravitino/catalog/hologres/converter/HologresTypeConverter.java
 
b/catalogs-contrib/catalog-jdbc-hologres/src/main/java/org/apache/gravitino/catalog/hologres/converter/HologresTypeConverter.java
new file mode 100644
index 0000000000..b9b6967dec
--- /dev/null
+++ 
b/catalogs-contrib/catalog-jdbc-hologres/src/main/java/org/apache/gravitino/catalog/hologres/converter/HologresTypeConverter.java
@@ -0,0 +1,184 @@
+/*
+ * 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.hologres.converter;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import java.util.Optional;
+import org.apache.gravitino.catalog.jdbc.converter.JdbcTypeConverter;
+import org.apache.gravitino.rel.types.Type;
+import org.apache.gravitino.rel.types.Types;
+import org.apache.gravitino.rel.types.Types.ListType;
+
+/**
+ * Type converter for Hologres.
+ *
+ * <p>Hologres is PostgreSQL-compatible, so type conversion follows PostgreSQL 
conventions. Hologres
+ * supports the following data types:
+ *
+ * <ul>
+ *   <li>BOOLEAN (bool)
+ *   <li>SMALLINT (int2)
+ *   <li>INTEGER (int4)
+ *   <li>BIGINT (int8)
+ *   <li>REAL (float4)
+ *   <li>DOUBLE PRECISION (float8)
+ *   <li>NUMERIC/DECIMAL
+ *   <li>TEXT, VARCHAR, CHAR (bpchar)
+ *   <li>BYTEA
+ *   <li>DATE, TIME, TIMESTAMP, TIMESTAMPTZ
+ *   <li>JSON, JSONB
+ *   <li>Array types
+ * </ul>
+ */
+public class HologresTypeConverter extends JdbcTypeConverter {
+
+  static final String BOOL = "bool";
+  static final String INT_2 = "int2";
+  static final String INT_4 = "int4";
+  static final String INT_8 = "int8";
+  static final String FLOAT_4 = "float4";
+  static final String FLOAT_8 = "float8";
+
+  static final String TIMESTAMP_TZ = "timestamptz";
+  static final String NUMERIC = "numeric";
+  static final String BPCHAR = "bpchar";
+  static final String BYTEA = "bytea";
+  @VisibleForTesting static final String JDBC_ARRAY_PREFIX = "_";
+  @VisibleForTesting static final String ARRAY_TOKEN = "[]";
+
+  @Override
+  public Type toGravitino(JdbcTypeBean typeBean) {
+    String typeName = typeBean.getTypeName().toLowerCase();
+    if (typeName.startsWith(JDBC_ARRAY_PREFIX)) {
+      return toGravitinoArrayType(typeName);
+    }
+    switch (typeName) {
+      case BOOL:
+        return Types.BooleanType.get();
+      case INT_2:
+        return Types.ShortType.get();
+      case INT_4:
+        return Types.IntegerType.get();
+      case INT_8:
+        return Types.LongType.get();
+      case FLOAT_4:
+        return Types.FloatType.get();
+      case FLOAT_8:
+        return Types.DoubleType.get();
+      case DATE:
+        return Types.DateType.get();
+      case TIME:
+        return Optional.ofNullable(typeBean.getDatetimePrecision())
+            .map(Types.TimeType::of)
+            .orElseGet(Types.TimeType::get);
+      case TIMESTAMP:
+        return Optional.ofNullable(typeBean.getDatetimePrecision())
+            .map(Types.TimestampType::withoutTimeZone)
+            .orElseGet(Types.TimestampType::withoutTimeZone);
+      case TIMESTAMP_TZ:
+        return Optional.ofNullable(typeBean.getDatetimePrecision())
+            .map(Types.TimestampType::withTimeZone)
+            .orElseGet(Types.TimestampType::withTimeZone);
+      case NUMERIC:
+        return Types.DecimalType.of(typeBean.getColumnSize(), 
typeBean.getScale());
+      case VARCHAR:
+        return typeBean.getColumnSize() == null
+            ? Types.StringType.get()
+            : Types.VarCharType.of(typeBean.getColumnSize());
+      case BPCHAR:
+        return Types.FixedCharType.of(typeBean.getColumnSize());
+      case TEXT:
+        return Types.StringType.get();
+      case BYTEA:
+        return Types.BinaryType.get();
+      default:
+        return Types.ExternalType.of(typeBean.getTypeName());
+    }
+  }
+
+  @Override
+  public String fromGravitino(Type type) {
+    if (type instanceof Types.BooleanType) {
+      return BOOL;
+    } else if (type instanceof Types.ShortType) {
+      return INT_2;
+    } else if (type instanceof Types.IntegerType) {
+      return INT_4;
+    } else if (type instanceof Types.LongType) {
+      return INT_8;
+    } else if (type instanceof Types.FloatType) {
+      return FLOAT_4;
+    } else if (type instanceof Types.DoubleType) {
+      return FLOAT_8;
+    } else if (type instanceof Types.StringType) {
+      return TEXT;
+    } else if (type instanceof Types.DateType) {
+      return type.simpleString();
+    } else if (type instanceof Types.TimeType) {
+      return type.simpleString();
+    } else if (type instanceof Types.TimestampType) {
+      // Hologres does not support precision for TIMESTAMP/TIMESTAMPTZ (e.g. 
timestamptz(6)),
+      // so we always emit the base type without precision.
+      Types.TimestampType timestampType = (Types.TimestampType) type;
+      return timestampType.hasTimeZone() ? TIMESTAMP_TZ : TIMESTAMP;
+    } else if (type instanceof Types.DecimalType) {
+      return NUMERIC
+          + "("
+          + ((Types.DecimalType) type).precision()
+          + ","
+          + ((Types.DecimalType) type).scale()
+          + ")";
+    } else if (type instanceof Types.VarCharType) {
+      return VARCHAR + "(" + ((Types.VarCharType) type).length() + ")";
+    } else if (type instanceof Types.FixedCharType) {
+      return BPCHAR + "(" + ((Types.FixedCharType) type).length() + ")";
+    } else if (type instanceof Types.BinaryType) {
+      return BYTEA;
+    } else if (type instanceof Types.ListType) {
+      return fromGravitinoArrayType((ListType) type);
+    } else if (type instanceof Types.ExternalType) {
+      return ((Types.ExternalType) type).catalogString();
+    }
+    throw new IllegalArgumentException(
+        String.format("Couldn't convert Gravitino type %s to Hologres type", 
type.simpleString()));
+  }
+
+  // Hologres (like PostgreSQL) doesn't support the multidimensional array 
internally. The current
+  // implementation does not enforce the declared number of dimensions either. 
Arrays of a
+  // particular element type are all considered to be of the same type, 
regardless of size or number
+  // of dimensions. So, declaring the array size or number of dimensions in 
CREATE TABLE is simply
+  // documentation; it does not affect run-time behavior.
+  private String fromGravitinoArrayType(ListType listType) {
+    Type elementType = listType.elementType();
+    Preconditions.checkArgument(
+        !listType.elementNullable(), "Hologres doesn't support element to 
nullable");
+    Preconditions.checkArgument(
+        !(elementType instanceof ListType),
+        "Hologres doesn't support multidimensional list internally, please use 
one dimensional list");
+    String elementTypeString = fromGravitino(elementType);
+    return elementTypeString + ARRAY_TOKEN;
+  }
+
+  private ListType toGravitinoArrayType(String typeName) {
+    String elementTypeName = typeName.substring(JDBC_ARRAY_PREFIX.length(), 
typeName.length());
+    JdbcTypeBean bean = new JdbcTypeBean(elementTypeName);
+    return ListType.of(toGravitino(bean), false);
+  }
+}
diff --git 
a/catalogs-contrib/catalog-jdbc-hologres/src/main/java/org/apache/gravitino/catalog/hologres/operation/HologresSchemaOperations.java
 
b/catalogs-contrib/catalog-jdbc-hologres/src/main/java/org/apache/gravitino/catalog/hologres/operation/HologresSchemaOperations.java
new file mode 100644
index 0000000000..c179f70b0d
--- /dev/null
+++ 
b/catalogs-contrib/catalog-jdbc-hologres/src/main/java/org/apache/gravitino/catalog/hologres/operation/HologresSchemaOperations.java
@@ -0,0 +1,56 @@
+/*
+ * 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.hologres.operation;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.Set;
+import org.apache.gravitino.catalog.jdbc.operation.JdbcDatabaseOperations;
+
+/**
+ * Schema (Database) operations for Hologres.
+ *
+ * <p>Hologres uses the PostgreSQL schema concept where Database in PostgreSQL 
corresponds to
+ * Catalog in JDBC, and Schema in PostgreSQL corresponds to Schema in JDBC.
+ *
+ * <p>TODO: Full implementation will be added in a follow-up PR.
+ */
+public class HologresSchemaOperations extends JdbcDatabaseOperations {
+
+  @Override
+  protected boolean supportSchemaComment() {
+    return true;
+  }
+
+  @Override
+  protected Set<String> createSysDatabaseNameSet() {
+    return ImmutableSet.of(
+        // PostgreSQL system schemas
+        "pg_toast",
+        "pg_catalog",
+        "information_schema",
+        // Hologres internal schemas
+        "hologres",
+        "hg_internal",
+        "hg_recyclebin",
+        "hologres_object_table",
+        "hologres_sample",
+        "hologres_streaming_mv",
+        "hologres_statistic");
+  }
+}
diff --git 
a/catalogs-contrib/catalog-jdbc-hologres/src/main/java/org/apache/gravitino/catalog/hologres/operation/HologresTableOperations.java
 
b/catalogs-contrib/catalog-jdbc-hologres/src/main/java/org/apache/gravitino/catalog/hologres/operation/HologresTableOperations.java
new file mode 100644
index 0000000000..5beb9c0178
--- /dev/null
+++ 
b/catalogs-contrib/catalog-jdbc-hologres/src/main/java/org/apache/gravitino/catalog/hologres/operation/HologresTableOperations.java
@@ -0,0 +1,75 @@
+/*
+ * 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.hologres.operation;
+
+import java.util.Map;
+import org.apache.gravitino.catalog.jdbc.JdbcColumn;
+import org.apache.gravitino.catalog.jdbc.operation.DatabaseOperation;
+import org.apache.gravitino.catalog.jdbc.operation.JdbcTableOperations;
+import org.apache.gravitino.catalog.jdbc.operation.RequireDatabaseOperation;
+import org.apache.gravitino.rel.TableChange;
+import org.apache.gravitino.rel.expressions.distributions.Distribution;
+import org.apache.gravitino.rel.expressions.transforms.Transform;
+import org.apache.gravitino.rel.indexes.Index;
+
+/**
+ * Table operations for Hologres.
+ *
+ * <p>Hologres is PostgreSQL-compatible, so most table operations follow 
PostgreSQL conventions.
+ * However, Hologres has specific features like table properties (orientation, 
distribution_key,
+ * etc.) that are handled through the WITH clause in CREATE TABLE statements.
+ *
+ * <p>TODO: Full implementation will be added in a follow-up PR.
+ */
+public class HologresTableOperations extends JdbcTableOperations
+    implements RequireDatabaseOperation {
+
+  public static final String HOLO_QUOTE = "\"";
+
+  @Override
+  public void setDatabaseOperation(DatabaseOperation databaseOperation) {
+    // Will be implemented in a follow-up PR.
+  }
+
+  @Override
+  protected String generateCreateTableSql(
+      String tableName,
+      JdbcColumn[] columns,
+      String comment,
+      Map<String, String> properties,
+      Transform[] partitioning,
+      Distribution distribution,
+      Index[] indexes) {
+    throw new UnsupportedOperationException(
+        "Hologres table creation will be implemented in a follow-up PR.");
+  }
+
+  @Override
+  protected String generateAlterTableSql(
+      String schemaName, String tableName, TableChange... changes) {
+    throw new UnsupportedOperationException(
+        "Hologres table alteration will be implemented in a follow-up PR.");
+  }
+
+  @Override
+  protected String generatePurgeTableSql(String tableName) {
+    throw new UnsupportedOperationException(
+        "Hologres does not support purge table in Gravitino, please use drop 
table");
+  }
+}
diff --git 
a/catalogs-contrib/catalog-jdbc-hologres/src/main/resources/META-INF/services/org.apache.gravitino.CatalogProvider
 
b/catalogs-contrib/catalog-jdbc-hologres/src/main/resources/META-INF/services/org.apache.gravitino.CatalogProvider
new file mode 100644
index 0000000000..7ac2c0499a
--- /dev/null
+++ 
b/catalogs-contrib/catalog-jdbc-hologres/src/main/resources/META-INF/services/org.apache.gravitino.CatalogProvider
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+org.apache.gravitino.catalog.hologres.HologresCatalog
diff --git 
a/catalogs-contrib/catalog-jdbc-hologres/src/main/resources/jdbc-hologres.conf 
b/catalogs-contrib/catalog-jdbc-hologres/src/main/resources/jdbc-hologres.conf
new file mode 100644
index 0000000000..8108778203
--- /dev/null
+++ 
b/catalogs-contrib/catalog-jdbc-hologres/src/main/resources/jdbc-hologres.conf
@@ -0,0 +1,34 @@
+#
+# 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.
+#
+
+# Hologres JDBC Configuration
+# Hologres uses PostgreSQL JDBC driver for connections
+#
+# jdbc-url = jdbc:postgresql://{ENDPOINT}:{PORT}/{DBNAME}
+# jdbc-user = your_access_id
+# jdbc-password = your_access_key
+# jdbc-database = your_database
+# jdbc-driver = org.postgresql.Driver
+#
+# Optional parameters for better performance:
+# - Add reWriteBatchedInserts=true for batch write optimization
+# - Add ApplicationName=YourAppName for query tracking
+#
+# Example:
+# jdbc-url = 
jdbc:postgresql://hgprecn-cn-xxx.hologres.aliyuncs.com:80/my_database?reWriteBatchedInserts=true&ApplicationName=Gravitino
diff --git 
a/catalogs-contrib/catalog-jdbc-hologres/src/test/java/org/apache/gravitino/catalog/hologres/TestHologresCatalogCapability.java
 
b/catalogs-contrib/catalog-jdbc-hologres/src/test/java/org/apache/gravitino/catalog/hologres/TestHologresCatalogCapability.java
new file mode 100644
index 0000000000..21b0ef9c68
--- /dev/null
+++ 
b/catalogs-contrib/catalog-jdbc-hologres/src/test/java/org/apache/gravitino/catalog/hologres/TestHologresCatalogCapability.java
@@ -0,0 +1,146 @@
+/*
+ * 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.hologres;
+
+import org.apache.gravitino.connector.capability.Capability;
+import org.apache.gravitino.connector.capability.CapabilityResult;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/** Unit tests for {@link HologresCatalogCapability}. */
+public class TestHologresCatalogCapability {
+
+  private final HologresCatalogCapability capability = new 
HologresCatalogCapability();
+
+  // --- Valid names ---
+
+  @Test
+  public void testValidNameStartingWithLetter() {
+    CapabilityResult result = 
capability.specificationOnName(Capability.Scope.TABLE, "my_table");
+    Assertions.assertEquals(CapabilityResult.SUPPORTED, result);
+  }
+
+  @Test
+  public void testValidNameStartingWithUnderscore() {
+    CapabilityResult result = 
capability.specificationOnName(Capability.Scope.TABLE, "_my_table");
+    Assertions.assertEquals(CapabilityResult.SUPPORTED, result);
+  }
+
+  @Test
+  public void testValidNameWithDigits() {
+    CapabilityResult result = 
capability.specificationOnName(Capability.Scope.TABLE, "table123");
+    Assertions.assertEquals(CapabilityResult.SUPPORTED, result);
+  }
+
+  @Test
+  public void testValidNameWithHyphen() {
+    CapabilityResult result = 
capability.specificationOnName(Capability.Scope.TABLE, "my-table");
+    Assertions.assertEquals(CapabilityResult.SUPPORTED, result);
+  }
+
+  @Test
+  public void testValidNameWithDollar() {
+    CapabilityResult result = 
capability.specificationOnName(Capability.Scope.TABLE, "my$table");
+    Assertions.assertEquals(CapabilityResult.SUPPORTED, result);
+  }
+
+  @Test
+  public void testValidNameWithSlash() {
+    CapabilityResult result = 
capability.specificationOnName(Capability.Scope.TABLE, "/my_table");
+    Assertions.assertEquals(CapabilityResult.SUPPORTED, result);
+  }
+
+  @Test
+  public void testValidNameWithUnicode() {
+    CapabilityResult result = 
capability.specificationOnName(Capability.Scope.TABLE, "表名test");
+    Assertions.assertEquals(CapabilityResult.SUPPORTED, result);
+  }
+
+  @Test
+  public void testValidNameSingleChar() {
+    CapabilityResult result = 
capability.specificationOnName(Capability.Scope.TABLE, "a");
+    Assertions.assertEquals(CapabilityResult.SUPPORTED, result);
+  }
+
+  // --- Invalid names ---
+
+  @Test
+  public void testInvalidNameStartingWithDigit() {
+    CapabilityResult result = 
capability.specificationOnName(Capability.Scope.TABLE, "1table");
+    Assertions.assertNotEquals(CapabilityResult.SUPPORTED, result);
+  }
+
+  @Test
+  public void testInvalidNameEmpty() {
+    CapabilityResult result = 
capability.specificationOnName(Capability.Scope.TABLE, "");
+    Assertions.assertNotEquals(CapabilityResult.SUPPORTED, result);
+  }
+
+  @Test
+  public void testInvalidNameTooLong() {
+    // 64 characters (max allowed is 63)
+    String longName = "a" + "b".repeat(63);
+    Assertions.assertEquals(64, longName.length());
+    CapabilityResult result = 
capability.specificationOnName(Capability.Scope.TABLE, longName);
+    Assertions.assertNotEquals(CapabilityResult.SUPPORTED, result);
+  }
+
+  @Test
+  public void testValidNameMaxLength() {
+    // 63 characters (max allowed)
+    String maxName = "a" + "b".repeat(62);
+    Assertions.assertEquals(63, maxName.length());
+    CapabilityResult result = 
capability.specificationOnName(Capability.Scope.TABLE, maxName);
+    Assertions.assertEquals(CapabilityResult.SUPPORTED, result);
+  }
+
+  // --- Reserved words for SCHEMA scope ---
+
+  @Test
+  public void testReservedWordPgCatalog() {
+    CapabilityResult result = 
capability.specificationOnName(Capability.Scope.SCHEMA, "pg_catalog");
+    Assertions.assertNotEquals(CapabilityResult.SUPPORTED, result);
+  }
+
+  @Test
+  public void testReservedWordInformationSchema() {
+    CapabilityResult result =
+        capability.specificationOnName(Capability.Scope.SCHEMA, 
"information_schema");
+    Assertions.assertNotEquals(CapabilityResult.SUPPORTED, result);
+  }
+
+  @Test
+  public void testReservedWordHologres() {
+    CapabilityResult result = 
capability.specificationOnName(Capability.Scope.SCHEMA, "hologres");
+    Assertions.assertNotEquals(CapabilityResult.SUPPORTED, result);
+  }
+
+  @Test
+  public void testReservedWordNotAppliedToTableScope() {
+    // "hologres" as a table name should be valid (reserved words only apply 
to SCHEMA scope)
+    CapabilityResult result = 
capability.specificationOnName(Capability.Scope.TABLE, "hologres");
+    Assertions.assertEquals(CapabilityResult.SUPPORTED, result);
+  }
+
+  @Test
+  public void testNonReservedSchemaName() {
+    CapabilityResult result = 
capability.specificationOnName(Capability.Scope.SCHEMA, "my_schema");
+    Assertions.assertEquals(CapabilityResult.SUPPORTED, result);
+  }
+}
diff --git 
a/catalogs-contrib/catalog-jdbc-hologres/src/test/java/org/apache/gravitino/catalog/hologres/converter/TestHologresColumnDefaultValueConverter.java
 
b/catalogs-contrib/catalog-jdbc-hologres/src/test/java/org/apache/gravitino/catalog/hologres/converter/TestHologresColumnDefaultValueConverter.java
new file mode 100644
index 0000000000..cd38893a58
--- /dev/null
+++ 
b/catalogs-contrib/catalog-jdbc-hologres/src/test/java/org/apache/gravitino/catalog/hologres/converter/TestHologresColumnDefaultValueConverter.java
@@ -0,0 +1,209 @@
+/*
+ * 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.hologres.converter;
+
+import static org.apache.gravitino.rel.Column.DEFAULT_VALUE_NOT_SET;
+import static 
org.apache.gravitino.rel.Column.DEFAULT_VALUE_OF_CURRENT_TIMESTAMP;
+
+import org.apache.gravitino.catalog.jdbc.converter.JdbcTypeConverter;
+import org.apache.gravitino.rel.expressions.Expression;
+import org.apache.gravitino.rel.expressions.UnparsedExpression;
+import org.apache.gravitino.rel.expressions.literals.Literals;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/** Unit tests for {@link HologresColumnDefaultValueConverter}. */
+public class TestHologresColumnDefaultValueConverter {
+
+  private final HologresColumnDefaultValueConverter converter =
+      new HologresColumnDefaultValueConverter();
+
+  @Test
+  public void testNullDefaultValueNullable() {
+    JdbcTypeConverter.JdbcTypeBean type = new 
JdbcTypeConverter.JdbcTypeBean("int4");
+    Expression result = converter.toGravitino(type, null, false, true);
+    Assertions.assertEquals(Literals.NULL, result);
+  }
+
+  @Test
+  public void testNullDefaultValueNotNullable() {
+    JdbcTypeConverter.JdbcTypeBean type = new 
JdbcTypeConverter.JdbcTypeBean("int4");
+    Expression result = converter.toGravitino(type, null, false, false);
+    Assertions.assertEquals(DEFAULT_VALUE_NOT_SET, result);
+  }
+
+  @Test
+  public void testExpressionReturnsUnparsed() {
+    JdbcTypeConverter.JdbcTypeBean type = new 
JdbcTypeConverter.JdbcTypeBean("int4");
+    Expression result = converter.toGravitino(type, "nextval('seq')", true, 
true);
+    Assertions.assertInstanceOf(UnparsedExpression.class, result);
+    Assertions.assertEquals("nextval('seq')", ((UnparsedExpression) 
result).unparsedExpression());
+  }
+
+  @Test
+  public void testBooleanLiteral() {
+    JdbcTypeConverter.JdbcTypeBean type = new 
JdbcTypeConverter.JdbcTypeBean("bool");
+    Expression result = converter.toGravitino(type, "true", false, true);
+    Assertions.assertEquals(Literals.booleanLiteral(true), result);
+  }
+
+  @Test
+  public void testBooleanLiteralFalse() {
+    JdbcTypeConverter.JdbcTypeBean type = new 
JdbcTypeConverter.JdbcTypeBean("bool");
+    Expression result = converter.toGravitino(type, "false", false, true);
+    Assertions.assertEquals(Literals.booleanLiteral(false), result);
+  }
+
+  @Test
+  public void testShortLiteral() {
+    JdbcTypeConverter.JdbcTypeBean type = new 
JdbcTypeConverter.JdbcTypeBean("int2");
+    Expression result = converter.toGravitino(type, "42", false, true);
+    Assertions.assertEquals(Literals.shortLiteral((short) 42), result);
+  }
+
+  @Test
+  public void testIntegerLiteral() {
+    JdbcTypeConverter.JdbcTypeBean type = new 
JdbcTypeConverter.JdbcTypeBean("int4");
+    Expression result = converter.toGravitino(type, "100", false, true);
+    Assertions.assertEquals(Literals.integerLiteral(100), result);
+  }
+
+  @Test
+  public void testLongLiteral() {
+    JdbcTypeConverter.JdbcTypeBean type = new 
JdbcTypeConverter.JdbcTypeBean("int8");
+    Expression result = converter.toGravitino(type, "9999999999", false, true);
+    Assertions.assertEquals(Literals.longLiteral(9999999999L), result);
+  }
+
+  @Test
+  public void testFloatLiteral() {
+    JdbcTypeConverter.JdbcTypeBean type = new 
JdbcTypeConverter.JdbcTypeBean("float4");
+    Expression result = converter.toGravitino(type, "3.14", false, true);
+    Assertions.assertEquals(Literals.floatLiteral(3.14f), result);
+  }
+
+  @Test
+  public void testDoubleLiteral() {
+    JdbcTypeConverter.JdbcTypeBean type = new 
JdbcTypeConverter.JdbcTypeBean("float8");
+    Expression result = converter.toGravitino(type, "3.14159265", false, true);
+    Assertions.assertEquals(Literals.doubleLiteral(3.14159265), result);
+  }
+
+  @Test
+  public void testDecimalLiteral() {
+    JdbcTypeConverter.JdbcTypeBean type = new 
JdbcTypeConverter.JdbcTypeBean("numeric");
+    type.setColumnSize(10);
+    type.setScale(2);
+    Expression result = converter.toGravitino(type, "'12.34'::numeric", false, 
true);
+    Assertions.assertEquals(
+        
Literals.decimalLiteral(org.apache.gravitino.rel.types.Decimal.of("12.34", 10, 
2)), result);
+  }
+
+  @Test
+  public void testVarcharLiteral() {
+    JdbcTypeConverter.JdbcTypeBean type = new 
JdbcTypeConverter.JdbcTypeBean("varchar");
+    type.setColumnSize(100);
+    Expression result =
+        converter.toGravitino(type, "'hello world'::character varying", false, 
true);
+    Assertions.assertEquals(Literals.varcharLiteral(100, "hello world"), 
result);
+  }
+
+  @Test
+  public void testTextLiteral() {
+    JdbcTypeConverter.JdbcTypeBean type = new 
JdbcTypeConverter.JdbcTypeBean("text");
+    Expression result = converter.toGravitino(type, "'some text'::text", 
false, true);
+    Assertions.assertEquals(Literals.stringLiteral("some text"), result);
+  }
+
+  @Test
+  public void testBpcharLiteral() {
+    JdbcTypeConverter.JdbcTypeBean type = new 
JdbcTypeConverter.JdbcTypeBean("bpchar");
+    type.setColumnSize(10);
+    Expression result = converter.toGravitino(type, "'abc'::bpchar", false, 
true);
+    Assertions.assertEquals(Literals.stringLiteral("abc"), result);
+  }
+
+  @Test
+  public void testDateLiteral() {
+    JdbcTypeConverter.JdbcTypeBean type = new 
JdbcTypeConverter.JdbcTypeBean("date");
+    Expression result = converter.toGravitino(type, "'2024-01-15'::date", 
false, true);
+    Assertions.assertEquals(Literals.dateLiteral(java.time.LocalDate.of(2024, 
1, 15)), result);
+  }
+
+  @Test
+  public void testTimeLiteral() {
+    JdbcTypeConverter.JdbcTypeBean type = new 
JdbcTypeConverter.JdbcTypeBean("time");
+    Expression result =
+        converter.toGravitino(type, "'10:30:00'::time without time zone", 
false, true);
+    Assertions.assertEquals(Literals.timeLiteral(java.time.LocalTime.of(10, 
30, 0)), result);
+  }
+
+  @Test
+  public void testTimestampLiteral() {
+    JdbcTypeConverter.JdbcTypeBean type = new 
JdbcTypeConverter.JdbcTypeBean("timestamp");
+    Expression result =
+        converter.toGravitino(
+            type, "'2024-01-15 10:30:00'::timestamp without time zone", false, 
true);
+    Assertions.assertEquals(
+        Literals.timestampLiteral(java.time.LocalDateTime.of(2024, 1, 15, 10, 
30, 0)), result);
+  }
+
+  @Test
+  public void testCurrentTimestamp() {
+    JdbcTypeConverter.JdbcTypeBean type = new 
JdbcTypeConverter.JdbcTypeBean("timestamptz");
+    // When parseLiteral fails but value equals CURRENT_TIMESTAMP
+    Expression result = converter.toGravitino(type, "CURRENT_TIMESTAMP", 
false, true);
+    Assertions.assertEquals(DEFAULT_VALUE_OF_CURRENT_TIMESTAMP, result);
+  }
+
+  @Test
+  public void testNullCastReturnsNull() {
+    JdbcTypeConverter.JdbcTypeBean type = new 
JdbcTypeConverter.JdbcTypeBean("varchar");
+    type.setColumnSize(100);
+    Expression result = converter.toGravitino(type, "NULL::character varying", 
false, true);
+    Assertions.assertEquals(Literals.NULL, result);
+  }
+
+  @Test
+  public void testUnknownTypeReturnsUnparsed() {
+    // When parseLiteral throws for unknown type and it's not CURRENT_TIMESTAMP
+    JdbcTypeConverter.JdbcTypeBean type = new 
JdbcTypeConverter.JdbcTypeBean("unknown_type");
+    Expression result = converter.toGravitino(type, "some_value", false, true);
+    Assertions.assertInstanceOf(UnparsedExpression.class, result);
+  }
+
+  @Test
+  public void testFromGravitinoUnparsedExpression() {
+    UnparsedExpression unparsed = 
UnparsedExpression.of("date_trunc('day'::text, order_time)");
+    String result = converter.fromGravitino(unparsed);
+    Assertions.assertEquals("date_trunc('day'::text, order_time)", result);
+  }
+
+  @Test
+  public void testFromGravitinoLiteralDelegatesToSuper() {
+    String result = converter.fromGravitino(Literals.integerLiteral(42));
+    Assertions.assertEquals("42", result);
+  }
+
+  @Test
+  public void testFromGravitinoDefaultValueNotSet() {
+    String result = converter.fromGravitino(DEFAULT_VALUE_NOT_SET);
+    Assertions.assertNull(result);
+  }
+}
diff --git 
a/catalogs-contrib/catalog-jdbc-hologres/src/test/java/org/apache/gravitino/catalog/hologres/converter/TestHologresExceptionConverter.java
 
b/catalogs-contrib/catalog-jdbc-hologres/src/test/java/org/apache/gravitino/catalog/hologres/converter/TestHologresExceptionConverter.java
new file mode 100644
index 0000000000..37921dd57e
--- /dev/null
+++ 
b/catalogs-contrib/catalog-jdbc-hologres/src/test/java/org/apache/gravitino/catalog/hologres/converter/TestHologresExceptionConverter.java
@@ -0,0 +1,126 @@
+/*
+ * 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.hologres.converter;
+
+import java.sql.SQLException;
+import org.apache.gravitino.exceptions.ConnectionFailedException;
+import org.apache.gravitino.exceptions.GravitinoRuntimeException;
+import org.apache.gravitino.exceptions.NoSuchSchemaException;
+import org.apache.gravitino.exceptions.NoSuchTableException;
+import org.apache.gravitino.exceptions.SchemaAlreadyExistsException;
+import org.apache.gravitino.exceptions.TableAlreadyExistsException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/** Unit tests for {@link HologresExceptionConverter}. */
+public class TestHologresExceptionConverter {
+
+  private final HologresExceptionConverter converter = new 
HologresExceptionConverter();
+
+  @Test
+  public void testDuplicateDatabase() {
+    SQLException se = new SQLException("database already exists", "42P04");
+    GravitinoRuntimeException result = converter.toGravitinoException(se);
+    Assertions.assertInstanceOf(SchemaAlreadyExistsException.class, result);
+  }
+
+  @Test
+  public void testDuplicateSchema() {
+    SQLException se = new SQLException("schema already exists", "42P06");
+    GravitinoRuntimeException result = converter.toGravitinoException(se);
+    Assertions.assertInstanceOf(SchemaAlreadyExistsException.class, result);
+  }
+
+  @Test
+  public void testDuplicateTable() {
+    SQLException se = new SQLException("table already exists", "42P07");
+    GravitinoRuntimeException result = converter.toGravitinoException(se);
+    Assertions.assertInstanceOf(TableAlreadyExistsException.class, result);
+  }
+
+  @Test
+  public void testInvalidSchemaName() {
+    SQLException se = new SQLException("invalid schema name", "3D000");
+    GravitinoRuntimeException result = converter.toGravitinoException(se);
+    Assertions.assertInstanceOf(NoSuchSchemaException.class, result);
+  }
+
+  @Test
+  public void testInvalidSchema() {
+    SQLException se = new SQLException("invalid schema", "3F000");
+    GravitinoRuntimeException result = converter.toGravitinoException(se);
+    Assertions.assertInstanceOf(NoSuchSchemaException.class, result);
+  }
+
+  @Test
+  public void testUndefinedTable() {
+    SQLException se = new SQLException("undefined table", "42P01");
+    GravitinoRuntimeException result = converter.toGravitinoException(se);
+    Assertions.assertInstanceOf(NoSuchTableException.class, result);
+  }
+
+  @Test
+  public void testConnectionException() {
+    SQLException se = new SQLException("connection refused", "08001");
+    GravitinoRuntimeException result = converter.toGravitinoException(se);
+    Assertions.assertInstanceOf(ConnectionFailedException.class, result);
+  }
+
+  @Test
+  public void testConnectionExceptionOtherSubclass() {
+    SQLException se = new SQLException("connection broken", "08006");
+    GravitinoRuntimeException result = converter.toGravitinoException(se);
+    Assertions.assertInstanceOf(ConnectionFailedException.class, result);
+  }
+
+  @Test
+  public void testUnknownSqlState() {
+    SQLException se = new SQLException("some error", "99999");
+    GravitinoRuntimeException result = converter.toGravitinoException(se);
+    Assertions.assertInstanceOf(GravitinoRuntimeException.class, result);
+    Assertions.assertFalse(result instanceof ConnectionFailedException);
+    Assertions.assertFalse(result instanceof SchemaAlreadyExistsException);
+    Assertions.assertFalse(result instanceof TableAlreadyExistsException);
+    Assertions.assertFalse(result instanceof NoSuchSchemaException);
+    Assertions.assertFalse(result instanceof NoSuchTableException);
+  }
+
+  @Test
+  public void testNullSqlState() {
+    SQLException se = new SQLException("some error");
+    GravitinoRuntimeException result = converter.toGravitinoException(se);
+    Assertions.assertInstanceOf(GravitinoRuntimeException.class, result);
+  }
+
+  @Test
+  public void testNullSqlStateWithPasswordAuthenticationFailed() {
+    SQLException se = new SQLException("password authentication failed for 
user");
+    GravitinoRuntimeException result = converter.toGravitinoException(se);
+    Assertions.assertInstanceOf(ConnectionFailedException.class, result);
+  }
+
+  @Test
+  public void testNullSqlStateWithNullMessage() {
+    // After null-message fix, converter should return 
GravitinoRuntimeException with empty message
+    // instead of throwing NPE.
+    SQLException se = new SQLException((String) null);
+    GravitinoRuntimeException result = converter.toGravitinoException(se);
+    Assertions.assertInstanceOf(GravitinoRuntimeException.class, result);
+  }
+}
diff --git 
a/catalogs-contrib/catalog-jdbc-hologres/src/test/java/org/apache/gravitino/catalog/hologres/converter/TestHologresTypeConverter.java
 
b/catalogs-contrib/catalog-jdbc-hologres/src/test/java/org/apache/gravitino/catalog/hologres/converter/TestHologresTypeConverter.java
new file mode 100644
index 0000000000..0c4d8d911b
--- /dev/null
+++ 
b/catalogs-contrib/catalog-jdbc-hologres/src/test/java/org/apache/gravitino/catalog/hologres/converter/TestHologresTypeConverter.java
@@ -0,0 +1,269 @@
+/*
+ * 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.hologres.converter;
+
+import org.apache.gravitino.catalog.jdbc.converter.JdbcTypeConverter;
+import org.apache.gravitino.rel.types.Type;
+import org.apache.gravitino.rel.types.Types;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/** Unit tests for {@link HologresTypeConverter}. */
+public class TestHologresTypeConverter {
+
+  private final HologresTypeConverter converter = new HologresTypeConverter();
+
+  @Test
+  public void testBooleanType() {
+    // Test toGravitino
+    JdbcTypeConverter.JdbcTypeBean typeBean = new 
JdbcTypeConverter.JdbcTypeBean("bool");
+    Type gravitinoType = converter.toGravitino(typeBean);
+    Assertions.assertEquals(Types.BooleanType.get(), gravitinoType);
+
+    // Test fromGravitino
+    String hologresType = converter.fromGravitino(Types.BooleanType.get());
+    Assertions.assertEquals("bool", hologresType);
+  }
+
+  @Test
+  public void testShortType() {
+    // Test toGravitino
+    JdbcTypeConverter.JdbcTypeBean typeBean = new 
JdbcTypeConverter.JdbcTypeBean("int2");
+    Type gravitinoType = converter.toGravitino(typeBean);
+    Assertions.assertEquals(Types.ShortType.get(), gravitinoType);
+
+    // Test fromGravitino
+    String hologresType = converter.fromGravitino(Types.ShortType.get());
+    Assertions.assertEquals("int2", hologresType);
+  }
+
+  @Test
+  public void testIntegerType() {
+    // Test toGravitino
+    JdbcTypeConverter.JdbcTypeBean typeBean = new 
JdbcTypeConverter.JdbcTypeBean("int4");
+    Type gravitinoType = converter.toGravitino(typeBean);
+    Assertions.assertEquals(Types.IntegerType.get(), gravitinoType);
+
+    // Test fromGravitino
+    String hologresType = converter.fromGravitino(Types.IntegerType.get());
+    Assertions.assertEquals("int4", hologresType);
+  }
+
+  @Test
+  public void testLongType() {
+    // Test toGravitino
+    JdbcTypeConverter.JdbcTypeBean typeBean = new 
JdbcTypeConverter.JdbcTypeBean("int8");
+    Type gravitinoType = converter.toGravitino(typeBean);
+    Assertions.assertEquals(Types.LongType.get(), gravitinoType);
+
+    // Test fromGravitino
+    String hologresType = converter.fromGravitino(Types.LongType.get());
+    Assertions.assertEquals("int8", hologresType);
+  }
+
+  @Test
+  public void testFloatType() {
+    // Test toGravitino
+    JdbcTypeConverter.JdbcTypeBean typeBean = new 
JdbcTypeConverter.JdbcTypeBean("float4");
+    Type gravitinoType = converter.toGravitino(typeBean);
+    Assertions.assertEquals(Types.FloatType.get(), gravitinoType);
+
+    // Test fromGravitino
+    String hologresType = converter.fromGravitino(Types.FloatType.get());
+    Assertions.assertEquals("float4", hologresType);
+  }
+
+  @Test
+  public void testDoubleType() {
+    // Test toGravitino
+    JdbcTypeConverter.JdbcTypeBean typeBean = new 
JdbcTypeConverter.JdbcTypeBean("float8");
+    Type gravitinoType = converter.toGravitino(typeBean);
+    Assertions.assertEquals(Types.DoubleType.get(), gravitinoType);
+
+    // Test fromGravitino
+    String hologresType = converter.fromGravitino(Types.DoubleType.get());
+    Assertions.assertEquals("float8", hologresType);
+  }
+
+  @Test
+  public void testDateType() {
+    // Test toGravitino
+    JdbcTypeConverter.JdbcTypeBean typeBean = new 
JdbcTypeConverter.JdbcTypeBean("date");
+    Type gravitinoType = converter.toGravitino(typeBean);
+    Assertions.assertEquals(Types.DateType.get(), gravitinoType);
+
+    // Test fromGravitino
+    String hologresType = converter.fromGravitino(Types.DateType.get());
+    Assertions.assertEquals("date", hologresType);
+  }
+
+  @Test
+  public void testTimeType() {
+    // Test toGravitino
+    JdbcTypeConverter.JdbcTypeBean typeBean = new 
JdbcTypeConverter.JdbcTypeBean("time");
+    Type gravitinoType = converter.toGravitino(typeBean);
+    Assertions.assertEquals(Types.TimeType.get(), gravitinoType);
+
+    // Test fromGravitino
+    String hologresType = converter.fromGravitino(Types.TimeType.get());
+    Assertions.assertEquals("time", hologresType);
+  }
+
+  @Test
+  public void testTimestampType() {
+    // Test toGravitino without timezone
+    JdbcTypeConverter.JdbcTypeBean typeBean = new 
JdbcTypeConverter.JdbcTypeBean("timestamp");
+    Type gravitinoType = converter.toGravitino(typeBean);
+    Assertions.assertEquals(Types.TimestampType.withoutTimeZone(), 
gravitinoType);
+
+    // Test toGravitino with timezone
+    typeBean = new JdbcTypeConverter.JdbcTypeBean("timestamptz");
+    gravitinoType = converter.toGravitino(typeBean);
+    Assertions.assertEquals(Types.TimestampType.withTimeZone(), gravitinoType);
+
+    // Test fromGravitino without timezone
+    String hologresType = 
converter.fromGravitino(Types.TimestampType.withoutTimeZone());
+    Assertions.assertEquals("timestamp", hologresType);
+
+    // Test fromGravitino with timezone
+    hologresType = converter.fromGravitino(Types.TimestampType.withTimeZone());
+    Assertions.assertEquals("timestamptz", hologresType);
+
+    // Test fromGravitino with timezone and precision - Hologres does not 
support precision
+    hologresType = 
converter.fromGravitino(Types.TimestampType.withTimeZone(6));
+    Assertions.assertEquals("timestamptz", hologresType);
+
+    // Test fromGravitino without timezone and precision - Hologres does not 
support precision
+    hologresType = 
converter.fromGravitino(Types.TimestampType.withoutTimeZone(6));
+    Assertions.assertEquals("timestamp", hologresType);
+  }
+
+  @Test
+  public void testDecimalType() {
+    // Test toGravitino
+    JdbcTypeConverter.JdbcTypeBean typeBean = new 
JdbcTypeConverter.JdbcTypeBean("numeric");
+    typeBean.setColumnSize(10);
+    typeBean.setScale(2);
+    Type gravitinoType = converter.toGravitino(typeBean);
+    Assertions.assertEquals(Types.DecimalType.of(10, 2), gravitinoType);
+
+    // Test fromGravitino
+    String hologresType = converter.fromGravitino(Types.DecimalType.of(10, 2));
+    Assertions.assertEquals("numeric(10,2)", hologresType);
+  }
+
+  @Test
+  public void testVarCharType() {
+    // Test toGravitino
+    JdbcTypeConverter.JdbcTypeBean typeBean = new 
JdbcTypeConverter.JdbcTypeBean("varchar");
+    typeBean.setColumnSize(255);
+    Type gravitinoType = converter.toGravitino(typeBean);
+    Assertions.assertEquals(Types.VarCharType.of(255), gravitinoType);
+
+    // Test fromGravitino
+    String hologresType = converter.fromGravitino(Types.VarCharType.of(255));
+    Assertions.assertEquals("varchar(255)", hologresType);
+  }
+
+  @Test
+  public void testFixedCharType() {
+    // Test toGravitino
+    JdbcTypeConverter.JdbcTypeBean typeBean = new 
JdbcTypeConverter.JdbcTypeBean("bpchar");
+    typeBean.setColumnSize(10);
+    Type gravitinoType = converter.toGravitino(typeBean);
+    Assertions.assertEquals(Types.FixedCharType.of(10), gravitinoType);
+
+    // Test fromGravitino
+    String hologresType = converter.fromGravitino(Types.FixedCharType.of(10));
+    Assertions.assertEquals("bpchar(10)", hologresType);
+  }
+
+  @Test
+  public void testTextType() {
+    // Test toGravitino
+    JdbcTypeConverter.JdbcTypeBean typeBean = new 
JdbcTypeConverter.JdbcTypeBean("text");
+    Type gravitinoType = converter.toGravitino(typeBean);
+    Assertions.assertEquals(Types.StringType.get(), gravitinoType);
+
+    // Test fromGravitino
+    String hologresType = converter.fromGravitino(Types.StringType.get());
+    Assertions.assertEquals("text", hologresType);
+  }
+
+  @Test
+  public void testBinaryType() {
+    // Test toGravitino
+    JdbcTypeConverter.JdbcTypeBean typeBean = new 
JdbcTypeConverter.JdbcTypeBean("bytea");
+    Type gravitinoType = converter.toGravitino(typeBean);
+    Assertions.assertEquals(Types.BinaryType.get(), gravitinoType);
+
+    // Test fromGravitino
+    String hologresType = converter.fromGravitino(Types.BinaryType.get());
+    Assertions.assertEquals("bytea", hologresType);
+  }
+
+  @Test
+  public void testArrayType() {
+    // Test toGravitino for int4 array
+    JdbcTypeConverter.JdbcTypeBean typeBean = new 
JdbcTypeConverter.JdbcTypeBean("_int4");
+    Type gravitinoType = converter.toGravitino(typeBean);
+    Assertions.assertEquals(Types.ListType.of(Types.IntegerType.get(), false), 
gravitinoType);
+
+    // Test fromGravitino for int4 array
+    String hologresType =
+        converter.fromGravitino(Types.ListType.of(Types.IntegerType.get(), 
false));
+    Assertions.assertEquals("int4[]", hologresType);
+
+    // Test toGravitino for text array
+    typeBean = new JdbcTypeConverter.JdbcTypeBean("_text");
+    gravitinoType = converter.toGravitino(typeBean);
+    Assertions.assertEquals(Types.ListType.of(Types.StringType.get(), false), 
gravitinoType);
+
+    // Test fromGravitino for text array
+    hologresType = 
converter.fromGravitino(Types.ListType.of(Types.StringType.get(), false));
+    Assertions.assertEquals("text[]", hologresType);
+  }
+
+  @Test
+  public void testExternalType() {
+    // Test toGravitino for unknown type
+    JdbcTypeConverter.JdbcTypeBean typeBean = new 
JdbcTypeConverter.JdbcTypeBean("json");
+    Type gravitinoType = converter.toGravitino(typeBean);
+    Assertions.assertEquals(Types.ExternalType.of("json"), gravitinoType);
+
+    // Test fromGravitino for external type
+    String hologresType = 
converter.fromGravitino(Types.ExternalType.of("jsonb"));
+    Assertions.assertEquals("jsonb", hologresType);
+  }
+
+  @Test
+  public void testNullableArrayThrowsException() {
+    Assertions.assertThrows(
+        IllegalArgumentException.class,
+        () -> 
converter.fromGravitino(Types.ListType.of(Types.IntegerType.get(), true)));
+  }
+
+  @Test
+  public void testMultidimensionalArrayThrowsException() {
+    Types.ListType nestedList =
+        Types.ListType.of(Types.ListType.of(Types.IntegerType.get(), false), 
false);
+    Assertions.assertThrows(
+        IllegalArgumentException.class, () -> 
converter.fromGravitino(nestedList));
+  }
+}
diff --git 
a/catalogs-contrib/catalog-jdbc-hologres/src/test/resources/log4j2.properties 
b/catalogs-contrib/catalog-jdbc-hologres/src/test/resources/log4j2.properties
new file mode 100644
index 0000000000..995f5061f8
--- /dev/null
+++ 
b/catalogs-contrib/catalog-jdbc-hologres/src/test/resources/log4j2.properties
@@ -0,0 +1,30 @@
+#
+# 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.
+#
+
+# Log4j2 Configuration for Testing
+status = error
+name = TestLog4j2Config
+
+appender.console.type = Console
+appender.console.name = STDOUT
+appender.console.layout.type = PatternLayout
+appender.console.layout.pattern = %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - 
%msg%n
+
+rootLogger.level = info
+rootLogger.appenderRef.stdout.ref = STDOUT
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 8ec52fc206..5b737ca14f 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -47,6 +47,7 @@ include("catalogs:catalog-kafka")
 include("catalogs:catalog-model")
 
 include("catalogs-contrib:catalog-jdbc-clickhouse")
+include("catalogs-contrib:catalog-jdbc-hologres")
 include("catalogs-contrib:catalog-jdbc-oceanbase")
 
 include(

Reply via email to