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

yuqi1129 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 b096dd2c42 [#11879] fix(clickhouse): fix type conversion for Decimal, 
DateTime64, LowCardinality, and IPv4/IPv6 (#11884)
b096dd2c42 is described below

commit b096dd2c42b041fcf4aa03af0c713394fd94bc94
Author: StormSpirit <[email protected]>
AuthorDate: Fri Jul 3 19:40:01 2026 +0800

    [#11879] fix(clickhouse): fix type conversion for Decimal, DateTime64, 
LowCardinality, and IPv4/IPv6 (#11884)
    
    ### What changes were proposed in this pull request?
    
    Fix four type conversion issues in the ClickHouse catalog's
    `ClickHouseTypeConverter`:
    
    1. **Decimal(precision > 38) → ExternalType**: ClickHouse supports
    Decimal up to precision 76
    (Decimal128/Decimal256), but Gravitino core `DecimalType` enforces
    precision ≤ 38. Tables with
    `Decimal(50, 10)` etc. previously threw `IllegalArgumentException` on
    `loadTable`. Now mapped to
    `ExternalType` for precision > 38, consistent with the existing pattern
    for non-standard types.
    
    2. **DateTime64(N) precision preserved on round-trip**:
    `TypeUtils.extractDateTimePrecision()`
    regex did not match `DateTime64(3)` — the entire type fell through to
    `ExternalType`. Extended
    the regex to `^DateTime(?:64)?\((\d+)\)$`. In `fromGravitino`,
    `TimestampType` with
       precision > 0 now returns `DateTime64(N)` instead of bare `DateTime`.
    
    3. **LowCardinality wrapper stripped before Nullable**: For
    `LowCardinality(Nullable(String))`, the outermost wrapper is
    `LowCardinality`. Added
    `TypeUtils.stripLowCardinality()` and call it before `stripNullable()`
    in `toGravitino()`.
    
    4. **IPv4/IPv6 mapped to ExternalType**: Constants `IPV4` and `IPV6`
    were declared but had no
    `case` branches. Added `case IPV4:` / `case IPV6:` returning
    `ExternalType.of(...)`.
    
    ### Why are the changes needed?
    
    Fix: #11879
    
    These issues cause ClickHouse tables with common column types
    (Decimal(50,x), DateTime64,
    LowCardinality, IPv4/IPv6) to either crash on `loadTable` or lose type
    semantics on round-trip.
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes. Tables with Decimal(precision > 38), DateTime64(N),
    LowCardinality(Nullable(...)),
    IPv4, or IPv6 columns can now be loaded without errors. Type round-trip
    fidelity is improved.
    
    ### How was this patch tested?
    
    - New unit tests in `TestClickHouseTypeConverter`: `./gradlew
    :catalogs-contrib:catalog-jdbc-clickhouse:test -PskipITs`
    (7 test scenarios: 4 toGravitino assertions — IPv6, DateTime64(3),
    LowCardinality(Nullable(String)),
    Decimal(50,10); 3 fromGravitino direction checks — DateTime64(3), IPv4,
    IPv6)
    - Updated existing Docker IT assertion in
    `TestClickHouseTableOperations.testTypeConversionAgainstCluster`:
    `c_dt64` column type changed from `ExternalType.of("DateTime64(3)")` to
    `TimestampType.withoutTimeZone(3)`
      to match the new `toGravitino()` behavior
    - Docker integration tests: `./gradlew
    :catalogs-contrib:catalog-jdbc-clickhouse:test -PskipDockerTests=false
    --tests "TestClickHouseTableOperations"`
    
    Signed-off-by: jiangxt2 <[email protected]>
---
 .../converter/ClickHouseTypeConverter.java         | 23 ++++++++++++++---
 .../catalog/clickhouse/converter/TypeUtils.java    |  6 ++++-
 .../converter/TestClickHouseTypeConverter.java     | 29 ++++++++++++++++++++++
 .../operations/TestClickHouseTableOperations.java  |  4 +--
 4 files changed, 55 insertions(+), 7 deletions(-)

diff --git 
a/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/converter/ClickHouseTypeConverter.java
 
b/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/converter/ClickHouseTypeConverter.java
index 14acbfa84c..8915a8c0c3 100644
--- 
a/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/converter/ClickHouseTypeConverter.java
+++ 
b/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/converter/ClickHouseTypeConverter.java
@@ -76,7 +76,10 @@ public class ClickHouseTypeConverter extends 
JdbcTypeConverter {
 
   @Override
   public Type toGravitino(JdbcTypeBean typeBean) {
-    String typeName = TypeUtils.stripNullable(typeBean.getTypeName());
+    // ClickHouse allows LowCardinality wrapping Nullable: 
LowCardinality(Nullable(String)).
+    // Nullable(LowCardinality(X)) is invalid in ClickHouse and not handled 
here.
+    String typeName = TypeUtils.stripLowCardinality(typeBean.getTypeName());
+    typeName = TypeUtils.stripNullable(typeName);
 
     Integer dateTimePrecision = TypeUtils.extractDateTimePrecision(typeName);
     if (dateTimePrecision != null) {
@@ -126,6 +129,11 @@ public class ClickHouseTypeConverter extends 
JdbcTypeConverter {
               String.format("Decimal scale %s is out of range [0, %s]", scale, 
precision));
         }
 
+        // ClickHouse supports Decimal up to precision 76 (Decimal128=38, 
Decimal256=76),
+        // but Gravitino core DecimalType enforces precision <= 38. Use 
ExternalType for larger.
+        if (precision > 38) {
+          return Types.ExternalType.of(String.format("%s(%s,%s)", DECIMAL, 
precision, scale));
+        }
         return Types.DecimalType.of(precision, scale);
       case STRING:
         return Types.StringType.get();
@@ -141,6 +149,10 @@ public class ClickHouseTypeConverter extends 
JdbcTypeConverter {
         return Types.BooleanType.get();
       case UUID:
         return Types.UUIDType.get();
+      case IPV4:
+        return Types.ExternalType.of(IPV4);
+      case IPV6:
+        return Types.ExternalType.of(IPV6);
       default:
         return Types.ExternalType.of(typeBean.getTypeName());
     }
@@ -164,9 +176,12 @@ public class ClickHouseTypeConverter extends 
JdbcTypeConverter {
       return STRING;
     } else if (type instanceof Types.DateType) {
       return DATE;
-    } else if (type instanceof Types.TimestampType) {
-      // Gravitino timestamp type maps to ClickHouse DateTime with precision 
0, and
-      // Use the external type to handle DateTime64
+    } else if (type instanceof Types.TimestampType timestampType) {
+      // Gravitino timestamp type maps to ClickHouse DateTime with precision 0.
+      // For precision > 0, use DateTime64(N).
+      if (timestampType.precision() > 0) {
+        return DATETIME64 + "(" + timestampType.precision() + ")";
+      }
       return DATETIME;
     } else if (type instanceof Types.TimeType) {
       return TIME;
diff --git 
a/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/converter/TypeUtils.java
 
b/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/converter/TypeUtils.java
index 8a9bb76bb4..7ac74a143d 100644
--- 
a/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/converter/TypeUtils.java
+++ 
b/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/converter/TypeUtils.java
@@ -29,8 +29,12 @@ public class TypeUtils {
     return typeName.replaceFirst("^Nullable\\((.*)\\)$", "$1");
   }
 
+  public static String stripLowCardinality(String typeName) {
+    return typeName.replaceFirst("^LowCardinality\\((.*)\\)$", "$1");
+  }
+
   public static Integer extractDateTimePrecision(String typeName) {
-    Matcher matcher = 
Pattern.compile("^DateTime\\((\\d+)\\)$").matcher(typeName);
+    Matcher matcher = 
Pattern.compile("^DateTime(?:64)?\\((\\d+)\\)$").matcher(typeName);
     if (matcher.matches()) {
       return Integer.parseInt(matcher.group(1));
     }
diff --git 
a/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/converter/TestClickHouseTypeConverter.java
 
b/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/converter/TestClickHouseTypeConverter.java
index 36e2ad51dc..c644724994 100644
--- 
a/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/converter/TestClickHouseTypeConverter.java
+++ 
b/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/converter/TestClickHouseTypeConverter.java
@@ -31,6 +31,8 @@ import static 
org.apache.gravitino.catalog.clickhouse.converter.ClickHouseTypeCo
 import static 
org.apache.gravitino.catalog.clickhouse.converter.ClickHouseTypeConverter.INT32;
 import static 
org.apache.gravitino.catalog.clickhouse.converter.ClickHouseTypeConverter.INT64;
 import static 
org.apache.gravitino.catalog.clickhouse.converter.ClickHouseTypeConverter.INT8;
+import static 
org.apache.gravitino.catalog.clickhouse.converter.ClickHouseTypeConverter.IPV4;
+import static 
org.apache.gravitino.catalog.clickhouse.converter.ClickHouseTypeConverter.IPV6;
 import static 
org.apache.gravitino.catalog.clickhouse.converter.ClickHouseTypeConverter.STRING;
 import static 
org.apache.gravitino.catalog.clickhouse.converter.ClickHouseTypeConverter.UINT16;
 import static 
org.apache.gravitino.catalog.clickhouse.converter.ClickHouseTypeConverter.UINT32;
@@ -95,6 +97,28 @@ public class TestClickHouseTypeConverter {
     Assertions.assertEquals(
         Types.ExternalType.of("IPv4"), 
CLICKHOUSE_TYPE_CONVERTER.toGravitino(ipv4));
 
+    JdbcTypeConverter.JdbcTypeBean ipv6 = createTypeBean("IPv6", null, null);
+    Assertions.assertEquals(
+        Types.ExternalType.of("IPv6"), 
CLICKHOUSE_TYPE_CONVERTER.toGravitino(ipv6));
+
+    // DateTime64(3) should map to TimestampType.withoutTimeZone(3)
+    JdbcTypeConverter.JdbcTypeBean dateTime64WithPrecision =
+        createTypeBean("DateTime64(3)", null, null);
+    Assertions.assertEquals(
+        Types.TimestampType.withoutTimeZone(3),
+        CLICKHOUSE_TYPE_CONVERTER.toGravitino(dateTime64WithPrecision));
+
+    // LowCardinality(Nullable(String)) should map to StringType
+    JdbcTypeConverter.JdbcTypeBean lowCardNullable =
+        createTypeBean("LowCardinality(Nullable(String))", null, null);
+    Assertions.assertEquals(
+        Types.StringType.get(), 
CLICKHOUSE_TYPE_CONVERTER.toGravitino(lowCardNullable));
+
+    // Decimal(50, 10) should map to ExternalType (not crash, since precision 
> 38)
+    JdbcTypeConverter.JdbcTypeBean decimal50 = createTypeBean("Decimal", 50, 
10);
+    Assertions.assertEquals(
+        Types.ExternalType.of("Decimal(50,10)"), 
CLICKHOUSE_TYPE_CONVERTER.toGravitino(decimal50));
+
     JdbcTypeConverter.JdbcTypeBean decimalTooLarge = createTypeBean("Decimal", 
77, 2);
     Assertions.assertThrows(
         IllegalArgumentException.class,
@@ -128,6 +152,11 @@ public class TestClickHouseTypeConverter {
     checkGravitinoTypeToJdbcType(UUID, Types.UUIDType.get());
     checkGravitinoTypeToJdbcType(USER_DEFINED_TYPE, 
Types.ExternalType.of(USER_DEFINED_TYPE));
     checkGravitinoTypeToJdbcType("DateTime", 
Types.TimestampType.withoutTimeZone(0));
+    // DateTime64(3) round-trip
+    checkGravitinoTypeToJdbcType(DATETIME64 + "(3)", 
Types.TimestampType.withoutTimeZone(3));
+    // IPv4/IPv6 round-trip
+    checkGravitinoTypeToJdbcType(IPV4, Types.ExternalType.of(IPV4));
+    checkGravitinoTypeToJdbcType(IPV6, Types.ExternalType.of(IPV6));
     checkGravitinoTypeToJdbcType(TIME, Types.TimeType.get());
     Assertions.assertThrows(
         IllegalArgumentException.class,
diff --git 
a/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/operations/TestClickHouseTableOperations.java
 
b/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/operations/TestClickHouseTableOperations.java
index 68f72d2e12..93c928a539 100644
--- 
a/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/operations/TestClickHouseTableOperations.java
+++ 
b/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/operations/TestClickHouseTableOperations.java
@@ -589,7 +589,7 @@ public class TestClickHouseTableOperations extends 
TestClickHouse {
     columns.add(
         JdbcColumn.builder()
             .withName("c_dt64")
-            .withType(Types.ExternalType.of("DateTime64(3)"))
+            .withType(Types.TimestampType.withoutTimeZone(3))
             .withNullable(false)
             .build());
     columns.add(
@@ -647,7 +647,7 @@ public class TestClickHouseTableOperations extends 
TestClickHouse {
     Assertions.assertEquals(
         Types.TimestampType.withoutTimeZone(0), 
loaded.columns()[14].dataType());
     Assertions.assertEquals(
-        Types.ExternalType.of("DateTime64(3)"), 
loaded.columns()[15].dataType());
+        Types.TimestampType.withoutTimeZone(3), 
loaded.columns()[15].dataType());
     Assertions.assertEquals(Types.BooleanType.get(), 
loaded.columns()[16].dataType());
     Assertions.assertEquals(Types.UUIDType.get(), 
loaded.columns()[17].dataType());
     Assertions.assertEquals(Types.ExternalType.of("IPv4"), 
loaded.columns()[18].dataType());

Reply via email to