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(