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

diqiu50 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 6402759db7 [#11017] improvement(clickhouse): Support date partition 
transforms (#11018)
6402759db7 is described below

commit 6402759db7e85c285ba7c784b91ac703ca98f723
Author: Qi Yu <[email protected]>
AuthorDate: Fri May 15 10:36:19 2026 +0800

    [#11017] improvement(clickhouse): Support date partition transforms (#11018)
    
    ### What changes were proposed in this pull request?
    
    Support ClickHouse table creation with Gravitino date partition
    transforms:
    
    - `Transforms.year(column)` -> `PARTITION BY toYear(column)`
    - `Transforms.month(column)` -> `PARTITION BY toYYYYMM(column)`
    - `Transforms.day(column)` -> `PARTITION BY toDate(column)`
    
    This PR also removes the stale identity-only helper in
    `ClickHouseTableOperations` and uses the shared ClickHouse SQL utility
    path for partition expression rendering.
    
    ### Why are the changes needed?
    
    ClickHouse catalog table creation previously only supported identity
    partitioning, while loading existing ClickHouse tables already
    recognized `toYear`, `toYYYYMM`, and `toDate` partition expressions.
    
    Fix: #11017
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes. Users can now create ClickHouse MergeTree-family tables with
    `year`, `month`, and `day` partition transforms through Gravitino.
    
    ### How was this patch tested?
    
    ```bash
    ./gradlew :catalogs-contrib:catalog-jdbc-clickhouse:spotlessApply
    ./gradlew :catalogs-contrib:catalog-jdbc-clickhouse:test --tests 
org.apache.gravitino.catalog.clickhouse.operations.TestClickHouseTableOperations
 -PskipITs
    ./gradlew :catalogs-contrib:catalog-jdbc-clickhouse:test --tests 
org.apache.gravitino.catalog.clickhouse.integration.test.CatalogClickHouseIT.testCreateAndLoadWithPartitionSortAndIndexes
 -PskipTests -PskipDockerTests=false
    ```
    
    ---------
    
    Co-authored-by: Claude Opus 4.7 <[email protected]>
---
 .../operations/ClickHouseTableOperations.java      | 16 ------
 .../operations/ClickHouseTableSqlUtils.java        | 34 ++++++++----
 .../integration/test/CatalogClickHouseIT.java      | 46 +++++++++++++++-
 .../operations/TestClickHouseTableOperations.java  | 64 ++++++++++++++++++++++
 4 files changed, 131 insertions(+), 29 deletions(-)

diff --git 
a/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseTableOperations.java
 
b/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseTableOperations.java
index 4c453b7e7a..fc823ef78c 100644
--- 
a/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseTableOperations.java
+++ 
b/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseTableOperations.java
@@ -419,22 +419,6 @@ public class ClickHouseTableOperations extends 
JdbcTableOperations {
         .orElse(null);
   }
 
-  private String toPartitionExpression(Transform transform) {
-    Preconditions.checkArgument(transform != null, "Partition transform cannot 
be null");
-    Preconditions.checkArgument(
-        StringUtils.equalsIgnoreCase(transform.name(), 
Transforms.NAME_OF_IDENTITY),
-        "Unsupported partition transform: " + transform.name());
-    Preconditions.checkArgument(
-        transform.arguments().length == 1
-            && transform.arguments()[0] instanceof NamedReference
-            && ((NamedReference) transform.arguments()[0]).fieldName().length 
== 1,
-        "ClickHouse only supports single column identity partitioning");
-
-    String fieldName =
-        ((NamedReference) transform.arguments()[0]).fieldName()[0]; // already 
validated
-    return quoteIdentifier(fieldName);
-  }
-
   private void validateNoAutoIncrementColumns(JdbcColumn[] columns) {
     if (ArrayUtils.isEmpty(columns)) {
       return;
diff --git 
a/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseTableSqlUtils.java
 
b/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseTableSqlUtils.java
index 4f21d69cd3..f90d607f23 100644
--- 
a/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseTableSqlUtils.java
+++ 
b/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseTableSqlUtils.java
@@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import org.apache.commons.lang3.StringUtils;
@@ -68,17 +69,18 @@ final class ClickHouseTableSqlUtils {
 
   static String toPartitionExpression(Transform transform) {
     Preconditions.checkArgument(transform != null, "Partition transform cannot 
be null");
-    Preconditions.checkArgument(
-        StringUtils.equalsIgnoreCase(transform.name(), 
Transforms.NAME_OF_IDENTITY),
-        "Unsupported partition transform: " + transform.name());
-    Preconditions.checkArgument(
-        transform.arguments().length == 1
-            && transform.arguments()[0] instanceof NamedReference
-            && ((NamedReference) transform.arguments()[0]).fieldName().length 
== 1,
-        "ClickHouse only supports single column identity partitioning");
-
-    String fieldName = ((NamedReference) 
transform.arguments()[0]).fieldName()[0];
-    return quoteIdentifier(fieldName);
+    String name = transform.name().toLowerCase(Locale.ROOT);
+    return switch (name) {
+      case Transforms.NAME_OF_IDENTITY -> 
quoteIdentifier(partitionFieldName(transform));
+      case Transforms.NAME_OF_YEAR -> "toYear(%s)"
+          .formatted(quoteIdentifier(partitionFieldName(transform)));
+      case Transforms.NAME_OF_MONTH -> "toYYYYMM(%s)"
+          .formatted(quoteIdentifier(partitionFieldName(transform)));
+      case Transforms.NAME_OF_DAY -> "toDate(%s)"
+          .formatted(quoteIdentifier(partitionFieldName(transform)));
+      default -> throw new IllegalArgumentException(
+          "Unsupported partition transform: " + transform.name());
+    };
   }
 
   static List<String> extractShardingKeyColumns(String shardingKey) {
@@ -236,4 +238,14 @@ final class ClickHouseTableSqlUtils {
   private static String quoteIdentifier(String identifier) {
     return String.format("`%s`", identifier);
   }
+
+  private static String partitionFieldName(Transform transform) {
+    Preconditions.checkArgument(
+        transform.arguments().length == 1
+            && transform.arguments()[0] instanceof NamedReference
+            && ((NamedReference) transform.arguments()[0]).fieldName().length 
== 1,
+        "ClickHouse partition transform only supports a single column 
reference");
+
+    return ((NamedReference) transform.arguments()[0]).fieldName()[0];
+  }
 }
diff --git 
a/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/integration/test/CatalogClickHouseIT.java
 
b/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/integration/test/CatalogClickHouseIT.java
index ab3d95b870..eeb31d5731 100644
--- 
a/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/integration/test/CatalogClickHouseIT.java
+++ 
b/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/integration/test/CatalogClickHouseIT.java
@@ -485,7 +485,7 @@ public class CatalogClickHouseIT extends BaseIT {
     NameIdentifier ident = NameIdentifier.of(schemaName, table);
     Column[] cols =
         new Column[] {
-          Column.of("id", Types.LongType.get(), "id"),
+          Column.of("id", Types.LongType.get(), "id", false, false, 
DEFAULT_VALUE_NOT_SET),
           Column.of(
               "event_time",
               Types.TimestampType.withoutTimeZone(),
@@ -497,7 +497,7 @@ public class CatalogClickHouseIT extends BaseIT {
           Column.of("amount", Types.FloatType.get(), "amt")
         };
 
-    Transform[] partitioning = new Transform[] 
{Transforms.identity("event_time")};
+    Transform[] partitioning = new Transform[] 
{Transforms.month("event_time")};
     SortOrder[] sortOrders =
         new SortOrder[] {
           SortOrders.of(NamedReference.field("user_id"), 
SortDirection.ASCENDING),
@@ -526,6 +526,7 @@ public class CatalogClickHouseIT extends BaseIT {
 
     Table loaded = catalog.asTableCatalog().loadTable(ident);
     Assertions.assertEquals(1, loaded.partitioning().length);
+    Assertions.assertEquals(Transforms.NAME_OF_MONTH, 
loaded.partitioning()[0].name());
     Assertions.assertEquals(
         "event_time", ((NamedReference) 
loaded.partitioning()[0].arguments()[0]).fieldName()[0]);
 
@@ -555,6 +556,47 @@ public class CatalogClickHouseIT extends BaseIT {
                         && Arrays.deepEquals(idx.fieldNames(), new String[][] 
{{"amount"}})));
   }
 
+  @Test
+  void testCreateAndLoadWithPartitionTransforms() {
+    assertPartitionRoundTrip("identity_part", 
Transforms.identity("event_time"));
+    assertPartitionRoundTrip("year_part", Transforms.year("event_time"));
+    assertPartitionRoundTrip("month_part", Transforms.month("event_time"));
+    assertPartitionRoundTrip("day_part", Transforms.day("event_time"));
+  }
+
+  private void assertPartitionRoundTrip(String prefix, Transform partition) {
+    String table = GravitinoITUtils.genRandomName(prefix);
+    NameIdentifier ident = NameIdentifier.of(schemaName, table);
+    Column[] cols =
+        new Column[] {
+          Column.of("id", Types.LongType.get(), "integer", false, false, 
DEFAULT_VALUE_NOT_SET),
+          Column.of(
+              "event_time",
+              Types.TimestampType.withoutTimeZone(),
+              "ts",
+              false,
+              false,
+              DEFAULT_VALUE_NOT_SET)
+        };
+
+    catalog
+        .asTableCatalog()
+        .createTable(
+            ident,
+            cols,
+            "partition transform roundtrip",
+            createProperties(),
+            new Transform[] {partition},
+            Distributions.NONE,
+            getSortOrders("id"));
+
+    Table loaded = catalog.asTableCatalog().loadTable(ident);
+    Assertions.assertEquals(1, loaded.partitioning().length);
+    Assertions.assertEquals(partition.name(), loaded.partitioning()[0].name());
+    Assertions.assertEquals(
+        "event_time", ((NamedReference) 
loaded.partitioning()[0].arguments()[0]).fieldName()[0]);
+  }
+
   @Test
   void testColumnNameWithKeyWords() {
     // Create table from Gravitino API
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 47188929e7..68f72d2e12 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
@@ -909,6 +909,70 @@ public class TestClickHouseTableOperations extends 
TestClickHouse {
             ClickHouseUtils.getSortOrders("c1"));
     Assertions.assertTrue(partitionSql.contains("PARTITION BY `c1`"));
 
+    String yearPartitionSql =
+        ops.buildCreateSql(
+            "t_year",
+            new JdbcColumn[] {col},
+            null,
+            propsWithPartition,
+            new Transform[] {Transforms.year("c1")},
+            Distributions.NONE,
+            indexes,
+            ClickHouseUtils.getSortOrders("c1"));
+    Assertions.assertTrue(yearPartitionSql.contains("PARTITION BY 
toYear(`c1`)"));
+
+    String monthPartitionSql =
+        ops.buildCreateSql(
+            "t_month",
+            new JdbcColumn[] {col},
+            null,
+            propsWithPartition,
+            new Transform[] {Transforms.month("c1")},
+            Distributions.NONE,
+            indexes,
+            ClickHouseUtils.getSortOrders("c1"));
+    Assertions.assertTrue(monthPartitionSql.contains("PARTITION BY 
toYYYYMM(`c1`)"));
+
+    String dayPartitionSql =
+        ops.buildCreateSql(
+            "t_day",
+            new JdbcColumn[] {col},
+            null,
+            propsWithPartition,
+            new Transform[] {Transforms.day("c1")},
+            Distributions.NONE,
+            indexes,
+            ClickHouseUtils.getSortOrders("c1"));
+    Assertions.assertTrue(dayPartitionSql.contains("PARTITION BY 
toDate(`c1`)"));
+
+    String multiPartitionSql =
+        ops.buildCreateSql(
+            "t_multi",
+            new JdbcColumn[] {col},
+            null,
+            propsWithPartition,
+            new Transform[] {Transforms.year("c1"), Transforms.day("c1")},
+            Distributions.NONE,
+            indexes,
+            ClickHouseUtils.getSortOrders("c1"));
+    Assertions.assertTrue(
+        multiPartitionSql.contains("PARTITION BY tuple(toYear(`c1`), 
toDate(`c1`))"));
+
+    IllegalArgumentException exception =
+        Assertions.assertThrows(
+            IllegalArgumentException.class,
+            () ->
+                ops.buildCreateSql(
+                    "t_bucket",
+                    new JdbcColumn[] {col},
+                    null,
+                    propsWithPartition,
+                    new Transform[] {Transforms.bucket(8, new String[] 
{"c1"})},
+                    Distributions.NONE,
+                    indexes,
+                    ClickHouseUtils.getSortOrders("c1")));
+    Assertions.assertEquals("Unsupported partition transform: bucket", 
exception.getMessage());
+
     // distribution not NONE
     Assertions.assertThrows(
         IllegalArgumentException.class,

Reply via email to