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 3a449c59d2 [#13490] feat(clickhouse): support quarter/year start 
partition transforms (#13492)
3a449c59d2 is described below

commit 3a449c59d289c511ef050178fb455762e3eb4b2e
Author: Thunderkeg <[email protected]>
AuthorDate: Sat Oct 10 16:57:42 2026 +0800

    [#13490] feat(clickhouse): support quarter/year start partition transforms 
(#13492)
    
    ### What changes were proposed in this pull request?
    
    Add structured read and create support for the one-column ClickHouse
    partition expressions `toStartOfQuarter(column)` and
    `toStartOfYear(column)`. When these tables are loaded, the catalog
    preserves the function name and column reference in a function
    transform; when tables are created, it emits the corresponding
    ClickHouse expression.
    
    ### Why are the changes needed?
    
    ClickHouse MergeTree tables can use these calendar boundary functions in
    `PARTITION BY`, but the ClickHouse catalog currently leaves them
    unstructured and exposes the native expressions only through the
    read-only `partition-key` property. This prevents clients from handling
    quarter- and year-based partitions through structured metadata across
    load and create workflows.
    
    Fix: #13490
    
    ### Does this PR introduce any user-facing change?
    
    Yes. The ClickHouse catalog now supports one-column
    `toStartOfQuarter(column)` and `toStartOfYear(column)` partition
    transforms for MergeTree-family tables. Calls with additional arguments,
    nested functions, and other unsupported expressions remain unstructured
    and are preserved through the read-only `partition-key` property.
    
    ### How was this patch tested?
    
    The equivalent Gradle 8.2 tasks passed using Corretto 17.0.18:
    
    - `./gradlew :catalogs-contrib:catalog-jdbc-clickhouse:test -PskipITs
    --tests '*TestClickHouseTableOperationsPartitioning' --tests
    '*TestClickHouseTableOperationsUnit'` — passed.
    - `./gradlew :catalogs-contrib:catalog-jdbc-clickhouse:test --tests
    
'org.apache.gravitino.catalog.clickhouse.integration.test.CatalogClickHouseIT.testLoadAndCreateWithStartOfDatePartitionTransforms'
    -PskipDockerTests=false` — 1 test passed.
    - `./gradlew :catalogs-contrib:catalog-jdbc-clickhouse:test --tests
    
'org.apache.gravitino.catalog.clickhouse.integration.test.CatalogClickHouseClusterIT'
    -PskipDockerTests=false` — 20 tests passed.
    - `./gradlew :catalogs-contrib:catalog-jdbc-clickhouse:spotlessCheck
    rat` — passed.
    - `git diff --check` — passed.
    
    Signed-off-by: jiangxt2 <[email protected]>
---
 .../operations/ClickHouseTableSqlUtils.java        | 25 ++++++++++++++
 .../integration/test/CatalogClickHouseIT.java      |  5 +--
 .../TestClickHouseTableOperationsPartitioning.java | 38 ++++++++++++++++++++--
 .../TestClickHouseTableOperationsUnit.java         | 30 ++++++++++++++++-
 docs/jdbc-clickhouse-catalog.md                    |  4 +--
 5 files changed, 95 insertions(+), 7 deletions(-)

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 905a090fe2..1294ed6085 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
@@ -58,6 +58,10 @@ final class ClickHouseTableSqlUtils {
       Pattern.compile("toStartOfWeek[(](.+)[)]", Pattern.CASE_INSENSITIVE);
   private static final Pattern TO_START_OF_MONTH_PATTERN =
       Pattern.compile("toStartOfMonth[(](.+)[)]", Pattern.CASE_INSENSITIVE);
+  private static final Pattern TO_START_OF_QUARTER_PATTERN =
+      Pattern.compile("toStartOfQuarter[(](.+)[)]", Pattern.CASE_INSENSITIVE);
+  private static final Pattern TO_START_OF_YEAR_PATTERN =
+      Pattern.compile("toStartOfYear[(](.+)[)]", Pattern.CASE_INSENSITIVE);
   private static final Pattern FUNCTION_WRAPPER_PATTERN =
       Pattern.compile("^\\s*([A-Za-z0-9_]+)\\((.*)\\)\\s*$");
   private static final Pattern PROJECTION_SETTING_NAME_PATTERN =
@@ -392,6 +396,10 @@ final class ClickHouseTableSqlUtils {
           .formatted(quoteIdentifier(partitionFieldName(transform)));
       case "tostartofmonth" -> "toStartOfMonth(%s)"
           .formatted(quoteIdentifier(partitionFieldName(transform)));
+      case "tostartofquarter" -> "toStartOfQuarter(%s)"
+          .formatted(quoteIdentifier(partitionFieldName(transform)));
+      case "tostartofyear" -> "toStartOfYear(%s)"
+          .formatted(quoteIdentifier(partitionFieldName(transform)));
       default -> throw new IllegalArgumentException(
           "Unsupported partition transform: " + transform.name());
     };
@@ -530,6 +538,23 @@ final class ClickHouseTableSqlUtils {
           : Transforms.apply("toStartOfMonth", new Expression[] 
{NamedReference.field(identifier)});
     }
 
+    Matcher toStartOfQuarterMatcher = 
TO_START_OF_QUARTER_PATTERN.matcher(trimmedExpression);
+    if (toStartOfQuarterMatcher.matches()) {
+      String identifier = 
extractPartitionIdentifier(toStartOfQuarterMatcher.group(1));
+      return identifier == null
+          ? null
+          : Transforms.apply(
+              "toStartOfQuarter", new Expression[] 
{NamedReference.field(identifier)});
+    }
+
+    Matcher toStartOfYearMatcher = 
TO_START_OF_YEAR_PATTERN.matcher(trimmedExpression);
+    if (toStartOfYearMatcher.matches()) {
+      String identifier = 
extractPartitionIdentifier(toStartOfYearMatcher.group(1));
+      return identifier == null
+          ? null
+          : Transforms.apply("toStartOfYear", new Expression[] 
{NamedReference.field(identifier)});
+    }
+
     String identifier = extractPartitionIdentifier(trimmedExpression);
     return identifier == null ? null : Transforms.identity(identifier);
   }
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 c52f77715b..f1705e2a3d 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
@@ -640,7 +640,7 @@ public class CatalogClickHouseIT extends BaseIT {
   }
 
   @Test
-  void testLoadAndCreateWithStartOfWeekAndMonthPartitionTransforms() {
+  void testLoadAndCreateWithStartOfDatePartitionTransforms() {
     TableCatalog tableCatalog = catalog.asTableCatalog();
     Column[] columns =
         new Column[] {
@@ -653,7 +653,8 @@ public class CatalogClickHouseIT extends BaseIT {
               DEFAULT_VALUE_NOT_SET)
         };
 
-    for (String functionName : new String[] {"toStartOfWeek", 
"toStartOfMonth"}) {
+    for (String functionName :
+        new String[] {"toStartOfWeek", "toStartOfMonth", "toStartOfQuarter", 
"toStartOfYear"}) {
       String nativeTableName = 
GravitinoITUtils.genRandomName("native_start_partition");
       String createdTableName = 
GravitinoITUtils.genRandomName("created_start_partition");
       String createNativeSql =
diff --git 
a/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/operations/TestClickHouseTableOperationsPartitioning.java
 
b/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/operations/TestClickHouseTableOperationsPartitioning.java
index a44edd8333..411403319e 100644
--- 
a/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/operations/TestClickHouseTableOperationsPartitioning.java
+++ 
b/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/operations/TestClickHouseTableOperationsPartitioning.java
@@ -49,9 +49,19 @@ public class TestClickHouseTableOperationsPartitioning {
     Assertions.assertEquals(1, monthStartPartitions.length);
     assertFunctionTransform(monthStartPartitions[0], "toStartOfMonth", 
"event_time");
 
+    Transform[] quarterStartPartitions =
+        operations.parsePartitioning("toStartOfQuarter(event_time)");
+    Assertions.assertEquals(1, quarterStartPartitions.length);
+    assertFunctionTransform(quarterStartPartitions[0], "toStartOfQuarter", 
"event_time");
+
+    Transform[] yearStartPartitions = 
operations.parsePartitioning("toStartOfYear(event_time)");
+    Assertions.assertEquals(1, yearStartPartitions.length);
+    assertFunctionTransform(yearStartPartitions[0], "toStartOfYear", 
"event_time");
+
     // A native expression that cannot be structured returns an empty 
transform array. The raw
     // expression is instead exposed through the read-only partition-key 
property during load.
     Assertions.assertEquals(0, 
operations.parsePartitioning("cityHash64(user_id) % 16").length);
+    Assertions.assertEquals(0, 
operations.parsePartitioning("toStartOfDay(event_time)").length);
 
     Transform[] identityPartitions = 
operations.parsePartitioning("metric_type");
     Assertions.assertEquals(1, identityPartitions.length);
@@ -63,10 +73,14 @@ public class TestClickHouseTableOperationsPartitioning {
     assertSingleFieldTransform(tuplePartitions[1], 
Transforms.NAME_OF_IDENTITY, "tenant_id");
 
     Transform[] functionTuplePartitions =
-        operations.parsePartitioning("(toStartOfWeek(ts), 
toStartOfMonth(created_at))");
-    Assertions.assertEquals(2, functionTuplePartitions.length);
+        operations.parsePartitioning(
+            "(toStartOfWeek(ts), toStartOfMonth(created_at), "
+                + "toStartOfQuarter(created_at), toStartOfYear(ts))");
+    Assertions.assertEquals(4, functionTuplePartitions.length);
     assertFunctionTransform(functionTuplePartitions[0], "toStartOfWeek", "ts");
     assertFunctionTransform(functionTuplePartitions[1], "toStartOfMonth", 
"created_at");
+    assertFunctionTransform(functionTuplePartitions[2], "toStartOfQuarter", 
"created_at");
+    assertFunctionTransform(functionTuplePartitions[3], "toStartOfYear", "ts");
 
     Assertions.assertEquals(0, operations.parsePartitioning("tuple()").length);
     Assertions.assertEquals(0, operations.parsePartitioning("  ").length);
@@ -80,6 +94,10 @@ public class TestClickHouseTableOperationsPartitioning {
     Assertions.assertEquals(0, 
operations.parsePartitioning("toYear(toString(event_time))").length);
     Assertions.assertEquals(
         0, 
operations.parsePartitioning("toStartOfMonth(toDate(event_time))").length);
+    Assertions.assertEquals(
+        0, 
operations.parsePartitioning("toStartOfQuarter(toDate(event_time))").length);
+    Assertions.assertEquals(
+        0, 
operations.parsePartitioning("toStartOfYear(toDate(event_time))").length);
   }
 
   @Test
@@ -98,6 +116,15 @@ public class TestClickHouseTableOperationsPartitioning {
     Assertions.assertEquals(1, monthStartPartitions.length);
     assertFunctionTransform(monthStartPartitions[0], "toStartOfMonth", 
"event-time");
 
+    Transform[] quarterStartPartitions =
+        operations.parsePartitioning("toStartOfQuarter(`event-time`)");
+    Assertions.assertEquals(1, quarterStartPartitions.length);
+    assertFunctionTransform(quarterStartPartitions[0], "toStartOfQuarter", 
"event-time");
+
+    Transform[] yearStartPartitions = 
operations.parsePartitioning("toStartOfYear(`event-time`)");
+    Assertions.assertEquals(1, yearStartPartitions.length);
+    assertFunctionTransform(yearStartPartitions[0], "toStartOfYear", 
"event-time");
+
     Transform[] identityPartitions = 
operations.parsePartitioning("`event-time`");
     Assertions.assertEquals(1, identityPartitions.length);
     assertSingleFieldTransform(identityPartitions[0], 
Transforms.NAME_OF_IDENTITY, "event-time");
@@ -110,6 +137,13 @@ public class TestClickHouseTableOperationsPartitioning {
         0, operations.parsePartitioning("toStartOfWeek(event_time, 1, 
'Asia/Shanghai')").length);
   }
 
+  @Test
+  public void testQuarterAndYearAdditionalArgumentsRemainUnstructured() {
+    Assertions.assertEquals(
+        0, operations.parsePartitioning("toStartOfQuarter(event_time, 
1)").length);
+    Assertions.assertEquals(0, 
operations.parsePartitioning("toStartOfYear(event_time, 1)").length);
+  }
+
   private void assertFunctionTransform(
       Transform transform, String expectedName, String expectedColumn) {
     Assertions.assertEquals(expectedName, transform.name());
diff --git 
a/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/operations/TestClickHouseTableOperationsUnit.java
 
b/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/operations/TestClickHouseTableOperationsUnit.java
index 1759dce3df..cc8c1f4c85 100644
--- 
a/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/operations/TestClickHouseTableOperationsUnit.java
+++ 
b/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/operations/TestClickHouseTableOperationsUnit.java
@@ -159,6 +159,16 @@ public class TestClickHouseTableOperationsUnit {
         ClickHouseTableSqlUtils.toPartitionExpression(
             Transforms.apply(
                 "toStartOfMonth", new Expression[] 
{NamedReference.field("event_time")})));
+    Assertions.assertEquals(
+        "toStartOfQuarter(`event_time`)",
+        ClickHouseTableSqlUtils.toPartitionExpression(
+            Transforms.apply(
+                "toStartOfQuarter", new Expression[] 
{NamedReference.field("event_time")})));
+    Assertions.assertEquals(
+        "toStartOfYear(`event_time`)",
+        ClickHouseTableSqlUtils.toPartitionExpression(
+            Transforms.apply(
+                "toStartOfYear", new Expression[] 
{NamedReference.field("event_time")})));
   }
 
   @Test
@@ -168,7 +178,25 @@ public class TestClickHouseTableOperationsUnit {
         () ->
             ClickHouseTableSqlUtils.toPartitionExpression(
                 Transforms.apply(
-                    "toStartOfQuarter", new Expression[] 
{NamedReference.field("event_time")})));
+                    "toStartOfDay", new Expression[] 
{NamedReference.field("event_time")})));
+    Assertions.assertThrows(
+        IllegalArgumentException.class,
+        () ->
+            ClickHouseTableSqlUtils.toPartitionExpression(
+                Transforms.apply(
+                    "toStartOfQuarter",
+                    new Expression[] {
+                      NamedReference.field("event_time"), 
NamedReference.field("tenant_id")
+                    })));
+    Assertions.assertThrows(
+        IllegalArgumentException.class,
+        () ->
+            ClickHouseTableSqlUtils.toPartitionExpression(
+                Transforms.apply(
+                    "toStartOfYear",
+                    new Expression[] {
+                      FunctionExpression.of("toDate", 
NamedReference.field("event_time"))
+                    })));
     Assertions.assertThrows(
         IllegalArgumentException.class,
         () ->
diff --git a/docs/jdbc-clickhouse-catalog.md b/docs/jdbc-clickhouse-catalog.md
index d01c601285..ddab8ff151 100644
--- a/docs/jdbc-clickhouse-catalog.md
+++ b/docs/jdbc-clickhouse-catalog.md
@@ -319,10 +319,10 @@ The `engine_parameters` property applies to 
`ReplacingMergeTree`, `SummingMergeT
    - Accept format: `id`, `(id, name)`, `(func(id), name)`, `func(id)`;
    - Reject format: `(id + 1)`, `(func(id) + 1)`, etc.
 
-- `PARTITION BY`: single-column identity and some functions are supported 
only, and only for MergeTree-family engines. For example `PARTITION BY 
created_at`, `PARTITION BY toYYYYMM(created_at)`, `PARTITION BY 
toStartOfWeek(created_at)`, and `PARTITION BY toStartOfMonth(created_at)` are 
supported, but `PARTITION BY (created_at + 1)` is not supported.
+- `PARTITION BY`: single-column identity and some functions are supported 
only, and only for MergeTree-family engines. For example `PARTITION BY 
created_at`, `PARTITION BY toYYYYMM(created_at)`, `PARTITION BY 
toStartOfWeek(created_at)`, `PARTITION BY toStartOfMonth(created_at)`, 
`PARTITION BY toStartOfQuarter(created_at)`, and `PARTITION BY 
toStartOfYear(created_at)` are supported, but `PARTITION BY (created_at + 1)` 
is not supported.
    In all, the following partitioning expressions are supported:
    - Identity: `PARTITION BY column_name`
-   - Functions: `PARTITION BY toDate(column_name)`, `PARTITION BY 
toYear(column_name)`, `PARTITION BY toYYYYMM(column_name)`, `PARTITION BY 
toStartOfWeek(column_name)`, and `PARTITION BY toStartOfMonth(column_name)`. 
Other function expressions are not supported as structured transforms.
+   - Functions: `PARTITION BY toDate(column_name)`, `PARTITION BY 
toYear(column_name)`, `PARTITION BY toYYYYMM(column_name)`, `PARTITION BY 
toStartOfWeek(column_name)`, `PARTITION BY toStartOfMonth(column_name)`, 
`PARTITION BY toStartOfQuarter(column_name)`, and `PARTITION BY 
toStartOfYear(column_name)`. Other function expressions are not supported as 
structured transforms.
    - `toStartOfWeek(column_name)` uses ClickHouse's default mode `0` (Sunday 
start) and the server timezone. Calls with an explicit mode or timezone are not 
structured.
    - Not support: `PARTITION BY (column_name + 1)`, `PARTITION BY 
(toYear(column_name) + 1)`, etc. (Note: ClickHouse itself does support 
arbitrary partitioning expressions, but Gravitino supports only the above 
patterns for partitioning). 
 

Reply via email to