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 c1449a3b4c [#10316] fix(catalog-jdbc-clickhouse): normalize quoted 
string defaults (#10318)
c1449a3b4c is described below

commit c1449a3b4cfedb0e181a11208a90456758a9cef6
Author: Qi Yu <[email protected]>
AuthorDate: Wed May 6 17:44:01 2026 +0800

    [#10316] fix(catalog-jdbc-clickhouse): normalize quoted string defaults 
(#10318)
    
    ### What changes were proposed in this pull request?
    
    This PR fixes ClickHouse string default value SQL rendering when
    creating or altering tables via API.
    
    Changes:
    - Normalize quoted string literals in
    ClickHouseColumnDefaultValueConverter#fromGravitino.
    - If input is already wrapped by outer single quotes (for example,
    'active'), strip the outer quotes before SQL wrapping.
    - Escape inner single quotes safely (' -> '').
    - Add regression tests:
    -
    
TestClickHouseTableOperations#testGenerateCreateTableSqlWithQuotedStringDefault
    -
    
TestClickHouseTableOperations#testGenerateCreateTableSqlEscapesStringDefaultQuotes
      - CatalogClickHouseIT#testCreateTableWithQuotedStringDefaultLiteral
    
    ### Why are the changes needed?
    
    When API clients pass a string default like 'active' with surrounding
    quotes in the value, generated DDL became DEFAULT ''active'' instead of
    DEFAULT 'active'.
    
    Fix: #10316
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes.
    Creating/altering ClickHouse tables with quoted string defaults now
    generates correct SQL defaults and preserves expected values.
    
    ### How was this patch tested?
    
    Ran:
    ./gradlew --no-daemon -p catalogs-contrib/catalog-jdbc-clickhouse test
    --tests
    
org.apache.gravitino.catalog.clickhouse.operations.TestClickHouseTableOperations
    --tests
    org.apache.gravitino.catalog.clickhouse.integration.test.CatalogClickHouseIT
    
    All targeted tests passed.
    
    ---------
    
    Co-authored-by: Copilot <[email protected]>
---
 .../ClickHouseColumnDefaultValueConverter.java     |  14 +-
 .../operations/ClickHouseClusterUtils.java         |   4 +
 .../integration/test/CatalogClickHouseIT.java      |  95 +++++++++++
 .../operations/TestClickHouseTableOperations.java  | 180 +++++++++++++++++++--
 .../catalog-lakehouse-iceberg/build.gradle.kts     |   6 +-
 5 files changed, 285 insertions(+), 14 deletions(-)

diff --git 
a/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/converter/ClickHouseColumnDefaultValueConverter.java
 
b/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/converter/ClickHouseColumnDefaultValueConverter.java
index 236b498172..a9458b1289 100644
--- 
a/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/converter/ClickHouseColumnDefaultValueConverter.java
+++ 
b/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/converter/ClickHouseColumnDefaultValueConverter.java
@@ -21,9 +21,11 @@ package org.apache.gravitino.catalog.clickhouse.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 com.google.common.base.Preconditions;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import org.apache.commons.lang3.StringUtils;
+import 
org.apache.gravitino.catalog.clickhouse.operations.ClickHouseClusterUtils;
 import 
org.apache.gravitino.catalog.jdbc.converter.JdbcColumnDefaultValueConverter;
 import org.apache.gravitino.catalog.jdbc.converter.JdbcTypeConverter;
 import org.apache.gravitino.rel.expressions.Expression;
@@ -57,10 +59,20 @@ public class ClickHouseColumnDefaultValueConverter extends 
JdbcColumnDefaultValu
         return literal.value().toString();
       } else {
         Object value = literal.value();
+        Preconditions.checkArgument(
+            value != null, "Null default literal value is not supported for 
ClickHouse.");
         if (value instanceof LocalDateTime) {
           value = ((LocalDateTime) value).format(DATE_TIME_FORMATTER);
+        } else if (value instanceof String str
+            && str.length() >= 2
+            && str.startsWith("'")
+            && str.endsWith("'")) {
+          // The API caller may pass quoted string literals (e.g. "'active'"), 
but DDL generation
+          // is responsible for adding the outer quotes.
+          value = ClickHouseClusterUtils.unescapeSingleQuotes(str.substring(1, 
str.length() - 1));
         }
-        return String.format("'%s'", value);
+
+        return String.format("'%s'", 
ClickHouseClusterUtils.escapeSingleQuotes(value.toString()));
       }
     }
 
diff --git 
a/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseClusterUtils.java
 
b/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseClusterUtils.java
index 830c032f70..b225102650 100644
--- 
a/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseClusterUtils.java
+++ 
b/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseClusterUtils.java
@@ -166,4 +166,8 @@ public final class ClickHouseClusterUtils {
   public static String escapeSingleQuotes(String text) {
     return text.replace("'", "''");
   }
+
+  public static String unescapeSingleQuotes(String text) {
+    return text.replace("''", "'");
+  }
 }
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 794189d0ff..ab3d95b870 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
@@ -654,6 +654,101 @@ public class CatalogClickHouseIT extends BaseIT {
         Literals.stringLiteral("now()"), 
createdTable.columns()[4].defaultValue());
   }
 
+  @Test
+  void testCreateTableWithCommonTypeLiteralDefaults() {
+    String name = GravitinoITUtils.genRandomName("default_literal_table");
+    NameIdentifier tableIdentifier = NameIdentifier.of(schemaName, name);
+
+    Column[] columns =
+        new Column[] {
+          Column.of(
+              "int_col", Types.IntegerType.get(), "int", false, false, 
Literals.integerLiteral(7)),
+          Column.of(
+              "double_col",
+              Types.DoubleType.get(),
+              "double",
+              false,
+              false,
+              Literals.doubleLiteral(123.45)),
+          Column.of(
+              "string_col",
+              Types.VarCharType.of(255),
+              "string",
+              false,
+              false,
+              Literals.stringLiteral("hello")),
+          Column.of(
+              "date_col",
+              Types.DateType.get(),
+              "date",
+              false,
+              false,
+              Literals.dateLiteral(LocalDate.of(2024, 4, 1))),
+          Column.of(
+              "decimal_col",
+              Types.DecimalType.of(5, 2),
+              "decimal",
+              false,
+              false,
+              Literals.decimalLiteral(Decimal.of("9.99", 5, 2)))
+        };
+
+    catalog
+        .asTableCatalog()
+        .createTable(
+            tableIdentifier,
+            columns,
+            table_comment,
+            createProperties(),
+            Transforms.EMPTY_TRANSFORM,
+            Distributions.NONE,
+            getSortOrders("int_col"));
+
+    Table loadedTable = catalog.asTableCatalog().loadTable(tableIdentifier);
+    Assertions.assertEquals(Literals.integerLiteral(7), 
loadedTable.columns()[0].defaultValue());
+    Assertions.assertEquals(
+        Literals.doubleLiteral(123.45), 
loadedTable.columns()[1].defaultValue());
+    Assertions.assertEquals(
+        Literals.stringLiteral("hello"), 
loadedTable.columns()[2].defaultValue());
+    Assertions.assertEquals(
+        Literals.dateLiteral(LocalDate.of(2024, 4, 1)), 
loadedTable.columns()[3].defaultValue());
+    Assertions.assertEquals(
+        Literals.decimalLiteral(Decimal.of("9.99", 5, 2)), 
loadedTable.columns()[4].defaultValue());
+  }
+
+  @Test
+  void testCreateTableWithQuotedStringDefaultLiteral() {
+    String name = GravitinoITUtils.genRandomName("quoted_default_table");
+    NameIdentifier tableIdentifier = NameIdentifier.of(schemaName, name);
+
+    Column[] columns =
+        new Column[] {
+          Column.of("id", Types.IntegerType.get(), "id", false, false, 
DEFAULT_VALUE_NOT_SET),
+          Column.of(
+              "status",
+              Types.StringType.get(),
+              "Status",
+              false,
+              false,
+              Literals.stringLiteral("'active'"))
+        };
+
+    catalog
+        .asTableCatalog()
+        .createTable(
+            tableIdentifier,
+            columns,
+            table_comment,
+            createProperties(),
+            Transforms.EMPTY_TRANSFORM,
+            Distributions.NONE,
+            getSortOrders("id"));
+
+    Table loadedTable = catalog.asTableCatalog().loadTable(tableIdentifier);
+    Assertions.assertEquals(
+        Literals.stringLiteral("active"), 
loadedTable.columns()[1].defaultValue());
+  }
+
   @Test
   // see https://dev.clickhouse.com/doc/refman/8.0/en/data-type-defaults.html
   @EnabledIf("supportColumnDefaultValueExpression")
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 a7cc763e30..47188929e7 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
@@ -32,7 +32,6 @@ import java.util.List;
 import java.util.Map;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.gravitino.catalog.clickhouse.ClickHouseConstants;
 import 
org.apache.gravitino.catalog.clickhouse.ClickHouseConstants.TableConstants;
 import 
org.apache.gravitino.catalog.clickhouse.ClickHouseTablePropertiesMetadata;
 import org.apache.gravitino.catalog.clickhouse.ClickHouseUtils;
@@ -894,9 +893,7 @@ public class TestClickHouseTableOperations extends 
TestClickHouse {
     Index[] indexes =
         new Index[] {
           Indexes.of(
-              Index.IndexType.PRIMARY_KEY,
-              Indexes.DEFAULT_PRIMARY_KEY_NAME,
-              new String[][] {{"c1"}})
+              IndexType.PRIMARY_KEY, Indexes.DEFAULT_PRIMARY_KEY_NAME, new 
String[][] {{"c1"}})
         };
 
     Map<String, String> propsWithPartition = new HashMap<>();
@@ -988,7 +985,7 @@ public class TestClickHouseTableOperations extends 
TestClickHouse {
     Map<String, String> props = new HashMap<>();
     props.put(
         TableConstants.ENGINE_UPPER, 
ClickHouseTablePropertiesMetadata.ENGINE.MERGETREE.getValue());
-    props.put(ClickHouseConstants.TableConstants.SETTINGS_PREFIX + 
"max_threads", "8");
+    props.put(TableConstants.SETTINGS_PREFIX + "max_threads", "8");
     String sql =
         ops.buildCreateSql(
             "t1",
@@ -1182,8 +1179,7 @@ public class TestClickHouseTableOperations extends 
TestClickHouse {
           TableChange.updateColumnPosition(new String[] {"c1"}, 
TableChange.ColumnPosition.first()),
           TableChange.deleteColumn(new String[] {"c3"}, false),
           TableChange.updateColumnNullability(new String[] {"c2"}, false),
-          TableChange.addIndex(
-              Index.IndexType.DATA_SKIPPING_MINMAX, "idx2", new String[][] 
{{"c2"}}),
+          TableChange.addIndex(IndexType.DATA_SKIPPING_MINMAX, "idx2", new 
String[][] {{"c2"}}),
           TableChange.deleteIndex("idx1", false),
           TableChange.renameColumn(new String[] {"c2"}, "c2_new"),
           TableChange.updateComment("new_table_comment")
@@ -1263,7 +1259,7 @@ public class TestClickHouseTableOperations extends 
TestClickHouse {
             "tbl",
             new TableChange[] {
               TableChange.addIndex(
-                  Index.IndexType.DATA_SKIPPING_MINMAX, "idx_new", new 
String[][] {{"c2"}})
+                  IndexType.DATA_SKIPPING_MINMAX, "idx_new", new String[][] 
{{"c2"}})
             });
     Assertions.assertTrue(minMaxSql.contains("ADD INDEX `idx_new` `c2` TYPE 
minmax GRANULARITY 1"));
 
@@ -1273,7 +1269,7 @@ public class TestClickHouseTableOperations extends 
TestClickHouse {
             "tbl",
             new TableChange[] {
               TableChange.addIndex(
-                  Index.IndexType.DATA_SKIPPING_BLOOM_FILTER, "idx_bf", new 
String[][] {{"c2"}})
+                  IndexType.DATA_SKIPPING_BLOOM_FILTER, "idx_bf", new 
String[][] {{"c2"}})
             });
     Assertions.assertTrue(
         bloomSql.contains("ADD INDEX `idx_bf` `c2` TYPE bloom_filter 
GRANULARITY 3"));
@@ -1286,7 +1282,7 @@ public class TestClickHouseTableOperations extends 
TestClickHouse {
                 "tbl",
                 new TableChange[] {
                   TableChange.addIndex(
-                      Index.IndexType.DATA_SKIPPING_MINMAX, "idx1", new 
String[][] {{"c2"}})
+                      IndexType.DATA_SKIPPING_MINMAX, "idx1", new String[][] 
{{"c2"}})
                 }));
 
     Assertions.assertThrows(
@@ -1296,8 +1292,7 @@ public class TestClickHouseTableOperations extends 
TestClickHouse {
                 "db",
                 "tbl",
                 new TableChange[] {
-                  TableChange.addIndex(
-                      Index.IndexType.PRIMARY_KEY, "pk_new", new String[][] 
{{"c1"}})
+                  TableChange.addIndex(IndexType.PRIMARY_KEY, "pk_new", new 
String[][] {{"c1"}})
                 }));
   }
 
@@ -1356,6 +1351,167 @@ public class TestClickHouseTableOperations extends 
TestClickHouse {
         () -> ops.buildAlterSql("db", "tbl", new TableChange[] 
{TableChange.removeProperty("k")}));
   }
 
+  @Test
+  void testGenerateCreateTableSqlWithQuotedStringDefault() {
+    TestableClickHouseTableOperations ops = new 
TestableClickHouseTableOperations();
+    ops.initialize(
+        null,
+        new ClickHouseExceptionConverter(),
+        new ClickHouseTypeConverter(),
+        new ClickHouseColumnDefaultValueConverter(),
+        new HashMap<>());
+
+    JdbcColumn[] cols =
+        new JdbcColumn[] {
+          JdbcColumn.builder()
+              .withName("id")
+              .withType(Types.IntegerType.get())
+              .withNullable(false)
+              .build(),
+          JdbcColumn.builder()
+              .withName("status")
+              .withType(Types.StringType.get())
+              .withNullable(false)
+              .withComment("Status")
+              .withDefaultValue(Literals.stringLiteral("'active'"))
+              .build()
+        };
+
+    String sql =
+        ops.buildCreateSql(
+            "t_status",
+            cols,
+            null,
+            new HashMap<>(),
+            Transforms.EMPTY_TRANSFORM,
+            Distributions.NONE,
+            new Index[0],
+            ClickHouseUtils.getSortOrders("id"));
+
+    Assertions.assertTrue(sql.contains("`status` String  DEFAULT 'active' 
COMMENT 'Status'"));
+    Assertions.assertFalse(sql.contains("DEFAULT ''active''"));
+  }
+
+  @Test
+  void testGenerateCreateTableSqlEscapesStringDefaultQuotes() {
+    TestableClickHouseTableOperations ops = new 
TestableClickHouseTableOperations();
+    ops.initialize(
+        null,
+        new ClickHouseExceptionConverter(),
+        new ClickHouseTypeConverter(),
+        new ClickHouseColumnDefaultValueConverter(),
+        new HashMap<>());
+
+    JdbcColumn[] cols =
+        new JdbcColumn[] {
+          JdbcColumn.builder()
+              .withName("id")
+              .withType(Types.IntegerType.get())
+              .withNullable(false)
+              .build(),
+          JdbcColumn.builder()
+              .withName("status")
+              .withType(Types.StringType.get())
+              .withNullable(false)
+              .withDefaultValue(Literals.stringLiteral("o'reilly"))
+              .build()
+        };
+
+    String sql =
+        ops.buildCreateSql(
+            "t_status_quote",
+            cols,
+            null,
+            new HashMap<>(),
+            Transforms.EMPTY_TRANSFORM,
+            Distributions.NONE,
+            new Index[0],
+            ClickHouseUtils.getSortOrders("id"));
+
+    Assertions.assertTrue(sql.contains("DEFAULT 'o''reilly'"));
+  }
+
+  @Test
+  void testGenerateCreateTableSqlWithPreQuotedEscapedStringDefault() {
+    TestableClickHouseTableOperations ops = new 
TestableClickHouseTableOperations();
+    ops.initialize(
+        null,
+        new ClickHouseExceptionConverter(),
+        new ClickHouseTypeConverter(),
+        new ClickHouseColumnDefaultValueConverter(),
+        new HashMap<>());
+
+    JdbcColumn[] cols =
+        new JdbcColumn[] {
+          JdbcColumn.builder()
+              .withName("id")
+              .withType(Types.IntegerType.get())
+              .withNullable(false)
+              .build(),
+          JdbcColumn.builder()
+              .withName("status")
+              .withType(Types.StringType.get())
+              .withNullable(false)
+              .withDefaultValue(Literals.stringLiteral("'o''reilly'"))
+              .build()
+        };
+
+    String sql =
+        ops.buildCreateSql(
+            "t_status_prequoted",
+            cols,
+            null,
+            new HashMap<>(),
+            Transforms.EMPTY_TRANSFORM,
+            Distributions.NONE,
+            new Index[0],
+            ClickHouseUtils.getSortOrders("id"));
+
+    Assertions.assertTrue(sql.contains("DEFAULT 'o''reilly'"));
+    Assertions.assertFalse(sql.contains("DEFAULT 'o''''reilly'"));
+  }
+
+  @Test
+  void testGenerateCreateTableSqlWithNullStringDefaultThrows() {
+    TestableClickHouseTableOperations ops = new 
TestableClickHouseTableOperations();
+    ops.initialize(
+        null,
+        new ClickHouseExceptionConverter(),
+        new ClickHouseTypeConverter(),
+        new ClickHouseColumnDefaultValueConverter(),
+        new HashMap<>());
+
+    JdbcColumn[] cols =
+        new JdbcColumn[] {
+          JdbcColumn.builder()
+              .withName("id")
+              .withType(Types.IntegerType.get())
+              .withNullable(false)
+              .build(),
+          JdbcColumn.builder()
+              .withName("status")
+              .withType(Types.StringType.get())
+              .withNullable(false)
+              .withDefaultValue(Literals.of(null, Types.StringType.get()))
+              .build()
+        };
+
+    IllegalArgumentException exception =
+        Assertions.assertThrows(
+            IllegalArgumentException.class,
+            () ->
+                ops.buildCreateSql(
+                    "t_status_null_default",
+                    cols,
+                    null,
+                    new HashMap<>(),
+                    Transforms.EMPTY_TRANSFORM,
+                    Distributions.NONE,
+                    new Index[0],
+                    ClickHouseUtils.getSortOrders("id")));
+    Assertions.assertTrue(exception.getMessage().contains("Null default 
literal value"));
+  }
+
   private static JdbcTable buildStubTable() {
     JdbcColumn c1 =
         JdbcColumn.builder()
diff --git a/catalogs/catalog-lakehouse-iceberg/build.gradle.kts 
b/catalogs/catalog-lakehouse-iceberg/build.gradle.kts
index f3a69c10fd..5537715581 100644
--- a/catalogs/catalog-lakehouse-iceberg/build.gradle.kts
+++ b/catalogs/catalog-lakehouse-iceberg/build.gradle.kts
@@ -38,7 +38,11 @@ dependencies {
   compileOnly(libs.lombok)
 
   implementation(project(":catalogs:catalog-common"))
-  implementation(project(":iceberg:iceberg-common"))
+  implementation(project(":iceberg:iceberg-common")) {
+    exclude(module = "api")
+    exclude(module = "core")
+    exclude(module = "common")
+  }
 
   implementation(libs.asm)
   implementation(libs.bundles.iceberg)

Reply via email to