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 8d60f23a58 [#11972] fix(clickhouse): strip PROJECTION blocks before 
extracting sort key from SHOW CREATE TABLE (#12016)
8d60f23a58 is described below

commit 8d60f23a587c172e3d5c4d32e4e63cf61f7a8b96
Author: StormSpirit <[email protected]>
AuthorDate: Thu Aug 27 22:22:39 2026 +0800

    [#11972] fix(clickhouse): strip PROJECTION blocks before extracting sort 
key from SHOW CREATE TABLE (#12016)
    
    ### What changes were proposed in this pull request?
    
    When a ClickHouse table contains a PROJECTION with its own ORDER BY
    clause, Gravitino parses the wrong ORDER BY from the SHOW CREATE TABLE
    output and returns incorrect sort keys.
    
    This PR strips PROJECTION definition blocks from the DDL before applying
    the existing regex patterns, so the table-level ORDER BY / PARTITION BY
    / SETTINGS clauses are matched correctly.
    
    ### Why are the changes needed?
    
    A PROJECTION block sits inside the column-definition body of the DDL:
    
    ```sql
    CREATE TABLE t (
        `id` Int64,
        PROJECTION p_normal ( SELECT * ORDER BY dt ),
        `dt` Date
    )
    ENGINE = MergeTree
    ORDER BY (id, dt)
    ```
    
    The projection-internal `ORDER BY dt` appears before the table-level
    `ORDER BY (id, dt)`, so `find()` matches the wrong one. This affects any
    table with a Normal projection (projections with no ORDER BY, like
    Aggregate projections with only GROUP BY, are not affected).
    
    Fixes #11972
    
    ### Does this PR introduce _any_ user-facing change?
    
    No. Tables without projections are unaffected. For tables with
    projections, `sortOrder()` now returns the correct table-level sort keys
    instead of garbled projection-internal content.
    
    ### How was this patch tested?
    
    - Unit tests: 19 new tests covering Normal/Aggregate/Multiple
    projections, INDEX coexistence, projection at different positions,
    string literals containing "PROJECTION", and escaped string literals
    inside projection bodies.
    - Docker integration tests: 11 tests on real ClickHouse 24.8 covering
    inline CREATE, ALTER TABLE ADD PROJECTION, PARTITION BY + PRIMARY KEY,
    ReplacingMergeTree, complex sort keys, and projections at all positions.
    - Existing tests (SETTINGS, INDEX, type mapping) continue to pass.
    
    ---------
    
    Signed-off-by: jiangxt2 <[email protected]>
    Co-authored-by: Qi Yu <[email protected]>
---
 .../operations/ClickHouseTableOperations.java      |  87 +++++++++-----
 .../integration/test/CatalogClickHouseIT.java      |  40 ++++++-
 .../operations/TestClickHouseTableOperations.java  |  58 +++-------
 .../TestClickHouseTableOperationsUnit.java         | 128 ++++++++++++++++++++-
 4 files changed, 238 insertions(+), 75 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 a5be1b0753..8d7afb9ed5 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
@@ -102,10 +102,6 @@ public class ClickHouseTableOperations extends 
JdbcTableOperations {
               ENGINE.SUMMINGMERGETREE,
               ENGINE.COLLAPSINGMERGETREE,
               ENGINE.VERSIONEDCOLLAPSINGMERGETREE));
-
-  private static final Pattern ORDER_BY_PATTERN =
-      Pattern.compile(
-          
"(?is)\\bORDER\\s+BY\\s*(.+?)(?=\\bPARTITION\\s+BY\\b|\\bPRIMARY\\s+KEY\\b|\\bSAMPLE\\s+BY\\b|\\bTTL\\b|\\bSETTINGS\\b|\\bCOMMENT\\b|$)");
   private static final Pattern PARTITION_BY_PATTERN =
       Pattern.compile(
           
"(?is)\\bPARTITION\\s+BY\\s*(.+?)(?=\\bORDER\\s+BY\\b|\\bPRIMARY\\s+KEY\\b|\\bSAMPLE\\s+BY\\b|\\bTTL\\b|\\bSETTINGS\\b|\\bCOMMENT\\b|$)");
@@ -808,25 +804,28 @@ public class ClickHouseTableOperations extends 
JdbcTableOperations {
       List<Index> indexes = getIndexes(connection, databaseName, tableName);
       jdbcTableBuilder.withIndexes(indexes.toArray(new Index[0]));
 
-      ShowCreateTableMetadata metadata = parseShowCreateTable(connection, 
tableName);
-      Transform[] partitioning = metadata.partitioning;
+      SystemTableMetadata systemTableMetadata =
+          getSystemTableMetadata(connection, databaseName, tableName);
+      ShowCreateTableMetadata showCreateMetadata = 
parseShowCreateTable(connection, tableName);
+      Transform[] partitioning = showCreateMetadata.partitioning;
       if (ArrayUtils.isEmpty(partitioning)) {
         partitioning = getTablePartitioning(connection, databaseName, 
tableName);
       }
       jdbcTableBuilder.withPartitioning(partitioning);
-      jdbcTableBuilder.withSortOrders(metadata.sortOrders);
+      jdbcTableBuilder.withSortOrders(systemTableMetadata.sortOrders());
 
       Distribution distribution = getDistributionInfo(connection, 
databaseName, tableName);
       jdbcTableBuilder.withDistribution(distribution);
 
       Map<String, String> tableProperties = getTableProperties(connection, 
tableName);
-      // Merge SETTINGS parsed from SHOW CREATE TABLE into table properties.
-      // SHOW CREATE TABLE is the authoritative source for SETTINGS; it takes 
precedence
+      // Merge SETTINGS parsed from system.tables.engine_full into table 
properties.
+      // engine_full contains only table-level storage clauses, so projection 
SETTINGS cannot be
+      // mistaken for table SETTINGS. These values take precedence
       // over any settings.* keys that might exist in system.tables (though 
getTableProperties()
       // currently does not read SETTINGS from system.tables, so no overlap 
occurs in practice).
-      if (!metadata.settings.isEmpty()) {
+      if (!systemTableMetadata.settings().isEmpty()) {
         Map<String, String> merged = new HashMap<>(tableProperties);
-        merged.putAll(metadata.settings);
+        merged.putAll(systemTableMetadata.settings());
         tableProperties = Collections.unmodifiableMap(merged);
       }
       jdbcTableBuilder.withProperties(tableProperties);
@@ -856,6 +855,26 @@ public class ClickHouseTableOperations extends 
JdbcTableOperations {
     return kinds;
   }
 
+  @VisibleForTesting
+  SystemTableMetadata getSystemTableMetadata(
+      Connection connection, String databaseName, String tableName) throws 
SQLException {
+    String sql =
+        "SELECT sorting_key, engine_full FROM system.tables WHERE database = ? 
AND name = ?";
+    try (PreparedStatement statement = connection.prepareStatement(sql)) {
+      statement.setString(1, databaseName);
+      statement.setString(2, tableName);
+      try (ResultSet resultSet = statement.executeQuery()) {
+        if (resultSet.next()) {
+          return new SystemTableMetadata(
+              parseOrderByClause(resultSet.getString("sorting_key")),
+              parseSettingsFromEngineFull(resultSet.getString("engine_full")));
+        }
+      }
+    }
+
+    throw new NoSuchTableException("Table %s does not exist in %s.", 
tableName, databaseName);
+  }
+
   @Override
   protected Transform[] getTablePartitioning(
       Connection connection, String databaseName, String tableName) throws 
SQLException {
@@ -1408,21 +1427,11 @@ public class ClickHouseTableOperations extends 
JdbcTableOperations {
       return metadata;
     }
 
-    Matcher orderMatcher = ORDER_BY_PATTERN.matcher(createSql);
-    if (orderMatcher.find()) {
-      metadata.sortOrders = parseOrderByClause(orderMatcher.group(1));
-    }
-
     Matcher partitionMatcher = PARTITION_BY_PATTERN.matcher(createSql);
     if (partitionMatcher.find()) {
       metadata.partitioning = parsePartitioning(partitionMatcher.group(1));
     }
 
-    Matcher settingsMatcher = SETTINGS_PATTERN.matcher(createSql);
-    if (settingsMatcher.find()) {
-      metadata.settings = parseSettingsClause(settingsMatcher.group(1));
-    }
-
     return metadata;
   }
 
@@ -1446,13 +1455,16 @@ public class ClickHouseTableOperations extends 
JdbcTableOperations {
   }
 
   @VisibleForTesting
-  SortOrder[] parseSortOrdersFromCreateSql(String createSql) {
-    return parseCreateStatement(createSql).sortOrders;
-  }
+  Map<String, String> parseSettingsFromEngineFull(String engineFull) {
+    if (StringUtils.isBlank(engineFull)) {
+      return Collections.emptyMap();
+    }
 
-  @VisibleForTesting
-  Map<String, String> parseSettingsFromCreateSql(String createSql) {
-    return parseCreateStatement(createSql).settings;
+    Matcher settingsMatcher = SETTINGS_PATTERN.matcher(engineFull);
+    if (settingsMatcher.find()) {
+      return parseSettingsClause(settingsMatcher.group(1));
+    }
+    return Collections.emptyMap();
   }
 
   private ShowCreateTableMetadata parseShowCreateTable(Connection connection, 
String tableName)
@@ -1571,10 +1583,27 @@ public class ClickHouseTableOperations extends 
JdbcTableOperations {
     return expression.toString();
   }
 
+  @VisibleForTesting
+  static final class SystemTableMetadata {
+    private final SortOrder[] sortOrders;
+    private final Map<String, String> settings;
+
+    private SystemTableMetadata(SortOrder[] sortOrders, Map<String, String> 
settings) {
+      this.sortOrders = sortOrders;
+      this.settings = settings;
+    }
+
+    SortOrder[] sortOrders() {
+      return sortOrders;
+    }
+
+    Map<String, String> settings() {
+      return settings;
+    }
+  }
+
   private static final class ShowCreateTableMetadata {
     private Transform[] partitioning = Transforms.EMPTY_TRANSFORM;
-    private SortOrder[] sortOrders = SortOrders.NONE;
-    private Map<String, String> settings = Collections.emptyMap();
   }
 
   @VisibleForTesting
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 6410fc1a51..fcc9c0bf24 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
@@ -476,8 +476,8 @@ public class CatalogClickHouseIT extends BaseIT {
   }
 
   @Test
-  void testLoadTableFromShowCreateParsing() {
-    String name = GravitinoITUtils.genRandomName("show_create_table");
+  void testLoadTableMetadataFromNativeSql() {
+    String name = GravitinoITUtils.genRandomName("native_table_metadata");
     clickhouseService.executeQuery(
         String.format(
             "CREATE TABLE `%s`.`%s` (\n"
@@ -2922,6 +2922,42 @@ public class CatalogClickHouseIT extends BaseIT {
                     Indexes.EMPTY_INDEXES));
   }
 
+  @Test
+  void testLoadTableWithProjectionUsesTableSortKey() {
+    String name = GravitinoITUtils.genRandomName("proj_normal");
+    clickhouseService.executeQuery(
+        String.format(
+            "CREATE TABLE `%s`.`%s` (\n"
+                + "  `id` Int64,\n"
+                + "  `dt` Date,\n"
+                + "  `val` String,\n"
+                + "  PROJECTION p_normal\n"
+                + "  (\n"
+                + "      SELECT *\n"
+                + "      ORDER BY dt\n"
+                + "  )\n"
+                + ")\n"
+                + "ENGINE = MergeTree\n"
+                + "ORDER BY (id, dt)\n"
+                + "SETTINGS index_granularity = 8192",
+            schemaName, name));
+
+    Table loaded = 
catalog.asTableCatalog().loadTable(NameIdentifier.of(schemaName, name));
+    SortOrder[] sortOrders = loaded.sortOrder();
+
+    Assertions.assertEquals(2, sortOrders.length);
+    Assertions.assertTrue(
+        sortOrders[0].expression() instanceof NamedReference,
+        "First sort key should be a named reference");
+    Assertions.assertArrayEquals(
+        new String[] {"id"}, ((NamedReference) 
sortOrders[0].expression()).fieldName());
+    Assertions.assertTrue(sortOrders[1].expression() instanceof 
NamedReference);
+    Assertions.assertArrayEquals(
+        new String[] {"dt"}, ((NamedReference) 
sortOrders[1].expression()).fieldName());
+    Assertions.assertEquals(
+        "8192", loaded.properties().get(TableConstants.SETTINGS_PREFIX + 
"index_granularity"));
+  }
+
   @Test
   void testEngineParametersRoundTrip() {
     // Create a ReplacingMergeTree table with engine parameter via Gravitino 
API and verify
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 909b5ec81a..5f3fdbd8ea 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
@@ -1362,44 +1362,20 @@ public class TestClickHouseTableOperations extends 
TestClickHouse {
   }
 
   @Test
-  void testParseSortOrdersFromMultilineShowCreateSql() {
-    TestableClickHouseTableOperations ops = new 
TestableClickHouseTableOperations();
-    String showCreateSql =
-        """
-        CREATE TABLE `t1`
-        (
-          `id` Int32,
-          `event_time` DateTime
-        )
-        ENGINE = MergeTree
-        ORDER BY
-          (`id`, toDate(`event_time`))
-        SETTINGS index_granularity = 8192
-        """;
-
-    SortOrder[] sortOrders = ops.parseSortOrders(showCreateSql);
-    Assertions.assertEquals(2, sortOrders.length);
-    Assertions.assertTrue(sortOrders[0].expression() instanceof 
NamedReference);
-    Assertions.assertEquals("id", ((NamedReference) 
sortOrders[0].expression()).fieldName()[0]);
-  }
-
-  @Test
-  void testParseSettingsFromCreateSql() {
+  void testParseSettingsFromEngineFull() {
     TestableClickHouseTableOperations ops = new 
TestableClickHouseTableOperations();
 
     // Single setting
-    String sql1 =
-        "CREATE TABLE t1 (id Int32) ENGINE = MergeTree ORDER BY id SETTINGS 
index_granularity = 4096";
-    Map<String, String> settings1 = ops.parseSettings(sql1);
+    String engineFull1 = "MergeTree ORDER BY id SETTINGS index_granularity = 
4096";
+    Map<String, String> settings1 = ops.parseSettings(engineFull1);
     Assertions.assertEquals(1, settings1.size());
     Assertions.assertEquals(
         "4096", settings1.get(TableConstants.SETTINGS_PREFIX + 
"index_granularity"));
 
     // Multiple settings
-    String sql2 =
-        "CREATE TABLE t2 (id Int32) ENGINE = MergeTree ORDER BY id"
-            + " SETTINGS index_granularity = 4096, min_bytes_for_wide_part = 
0";
-    Map<String, String> settings2 = ops.parseSettings(sql2);
+    String engineFull2 =
+        "MergeTree ORDER BY id" + " SETTINGS index_granularity = 4096, 
min_bytes_for_wide_part = 0";
+    Map<String, String> settings2 = ops.parseSettings(engineFull2);
     Assertions.assertEquals(2, settings2.size());
     Assertions.assertEquals(
         "4096", settings2.get(TableConstants.SETTINGS_PREFIX + 
"index_granularity"));
@@ -1407,15 +1383,15 @@ public class TestClickHouseTableOperations extends 
TestClickHouse {
         "0", settings2.get(TableConstants.SETTINGS_PREFIX + 
"min_bytes_for_wide_part"));
 
     // No SETTINGS clause
-    String sql3 = "CREATE TABLE t3 (id Int32) ENGINE = MergeTree ORDER BY id";
-    Map<String, String> settings3 = ops.parseSettings(sql3);
+    String engineFull3 = "MergeTree ORDER BY id";
+    Map<String, String> settings3 = ops.parseSettings(engineFull3);
     Assertions.assertTrue(settings3.isEmpty());
 
-    // SETTINGS with COMMENT after
-    String sql4 =
-        "CREATE TABLE t4 (id Int32) ENGINE = MergeTree ORDER BY id"
-            + " SETTINGS index_granularity = 8192 COMMENT 'test'";
-    Map<String, String> settings4 = ops.parseSettings(sql4);
+    // Engine arguments before SETTINGS
+    String engineFull4 =
+        "ReplicatedMergeTree('/path', '{replica}') ORDER BY id"
+            + " SETTINGS index_granularity = 8192";
+    Map<String, String> settings4 = ops.parseSettings(engineFull4);
     Assertions.assertEquals(1, settings4.size());
     Assertions.assertEquals(
         "8192", settings4.get(TableConstants.SETTINGS_PREFIX + 
"index_granularity"));
@@ -1435,12 +1411,8 @@ public class TestClickHouseTableOperations extends 
TestClickHouse {
           tableName, columns, comment, properties, partitioning, distribution, 
indexes, sortOrders);
     }
 
-    SortOrder[] parseSortOrders(String createSql) {
-      return parseSortOrdersFromCreateSql(createSql);
-    }
-
-    Map<String, String> parseSettings(String createSql) {
-      return parseSettingsFromCreateSql(createSql);
+    Map<String, String> parseSettings(String engineFull) {
+      return parseSettingsFromEngineFull(engineFull);
     }
   }
 
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 e2cfa0ad07..76d04b4525 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
@@ -32,7 +32,11 @@ import 
org.apache.gravitino.catalog.clickhouse.converter.ClickHouseColumnDefault
 import 
org.apache.gravitino.catalog.clickhouse.converter.ClickHouseExceptionConverter;
 import 
org.apache.gravitino.catalog.clickhouse.converter.ClickHouseTypeConverter;
 import org.apache.gravitino.catalog.jdbc.JdbcColumn;
+import org.apache.gravitino.exceptions.NoSuchTableException;
+import org.apache.gravitino.rel.expressions.FunctionExpression;
+import org.apache.gravitino.rel.expressions.NamedReference;
 import org.apache.gravitino.rel.expressions.distributions.Distributions;
+import org.apache.gravitino.rel.expressions.sorts.SortOrder;
 import org.apache.gravitino.rel.expressions.transforms.Transforms;
 import org.apache.gravitino.rel.indexes.Index;
 import org.apache.gravitino.rel.indexes.Indexes;
@@ -50,6 +54,11 @@ public class TestClickHouseTableOperationsUnit {
       return getIndexes(connection, databaseName, tableName);
     }
 
+    SystemTableMetadata callGetSystemTableMetadata(
+        Connection connection, String databaseName, String tableName) throws 
Exception {
+      return getSystemTableMetadata(connection, databaseName, tableName);
+    }
+
     Map<String, String> callGetTableProperties(Connection connection, String 
tableName)
         throws Exception {
       return getTableProperties(connection, tableName);
@@ -129,13 +138,130 @@ public class TestClickHouseTableOperationsUnit {
 
     ops.callGetIndexes(connection, "db'1", "t'1");
 
-    // First captured SQL is the primary-key QUERY_INDEXES_SQL 
(string-interpolated).
     String primaryKeySql = sqlCaptor.getAllValues().get(0);
     Assertions.assertTrue(
         primaryKeySql.contains("db''1"), "database single quote should be 
doubled");
     Assertions.assertTrue(primaryKeySql.contains("t''1"), "table single quote 
should be doubled");
   }
 
+  @Test
+  void testGetSystemTableMetadataQueriesExactTable() throws Exception {
+    ExposedClickHouseTableOperations ops = newOps();
+    Connection connection = Mockito.mock(Connection.class);
+    PreparedStatement statement = Mockito.mock(PreparedStatement.class);
+    ResultSet resultSet = Mockito.mock(ResultSet.class);
+    ArgumentCaptor<String> sqlCaptor = ArgumentCaptor.forClass(String.class);
+
+    
Mockito.when(connection.prepareStatement(sqlCaptor.capture())).thenReturn(statement);
+    Mockito.when(statement.executeQuery()).thenReturn(resultSet);
+    Mockito.when(resultSet.next()).thenReturn(true);
+    Mockito.when(resultSet.getString("sorting_key")).thenReturn("id");
+    Mockito.when(resultSet.getString("engine_full")).thenReturn("MergeTree 
ORDER BY id");
+
+    ClickHouseTableOperations.SystemTableMetadata metadata =
+        ops.callGetSystemTableMetadata(connection, "db_name", "table_name");
+
+    Assertions.assertEquals(
+        "SELECT sorting_key, engine_full FROM system.tables WHERE database = ? 
AND name = ?",
+        sqlCaptor.getValue());
+    Mockito.verify(statement).setString(1, "db_name");
+    Mockito.verify(statement).setString(2, "table_name");
+    Assertions.assertEquals(1, metadata.sortOrders().length);
+    Assertions.assertEquals(NamedReference.field("id"), 
metadata.sortOrders()[0].expression());
+    Assertions.assertTrue(metadata.settings().isEmpty());
+    Mockito.verify(resultSet).close();
+    Mockito.verify(statement).close();
+  }
+
+  @Test
+  void testGetSystemTableMetadataParsesCompoundAndFunctionExpressions() throws 
Exception {
+    ExposedClickHouseTableOperations ops = newOps();
+    Connection connection = Mockito.mock(Connection.class);
+    PreparedStatement statement = Mockito.mock(PreparedStatement.class);
+    ResultSet resultSet = Mockito.mock(ResultSet.class);
+
+    
Mockito.when(connection.prepareStatement(Mockito.anyString())).thenReturn(statement);
+    Mockito.when(statement.executeQuery()).thenReturn(resultSet);
+    Mockito.when(resultSet.next()).thenReturn(true);
+    Mockito.when(resultSet.getString("sorting_key")).thenReturn("id, 
toDate(event_time)");
+    Mockito.when(resultSet.getString("engine_full")).thenReturn("MergeTree 
ORDER BY id");
+
+    SortOrder[] sortOrders =
+        ops.callGetSystemTableMetadata(connection, "db_name", 
"table_name").sortOrders();
+
+    Assertions.assertEquals(2, sortOrders.length);
+    Assertions.assertEquals(NamedReference.field("id"), 
sortOrders[0].expression());
+    Assertions.assertEquals(
+        FunctionExpression.of("toDate", NamedReference.field("event_time")),
+        sortOrders[1].expression());
+  }
+
+  @Test
+  void testGetSystemTableMetadataReturnsNoneForBlankValues() throws Exception {
+    ExposedClickHouseTableOperations ops = newOps();
+    Connection connection = Mockito.mock(Connection.class);
+    PreparedStatement statement = Mockito.mock(PreparedStatement.class);
+    ResultSet resultSet = Mockito.mock(ResultSet.class);
+
+    
Mockito.when(connection.prepareStatement(Mockito.anyString())).thenReturn(statement);
+    Mockito.when(statement.executeQuery()).thenReturn(resultSet);
+    Mockito.when(resultSet.next()).thenReturn(true);
+    Mockito.when(resultSet.getString("sorting_key")).thenReturn("   ");
+    Mockito.when(resultSet.getString("engine_full")).thenReturn("   ");
+
+    ClickHouseTableOperations.SystemTableMetadata metadata =
+        ops.callGetSystemTableMetadata(connection, "db_name", "table_name");
+
+    Assertions.assertArrayEquals(new SortOrder[0], metadata.sortOrders());
+    Assertions.assertTrue(metadata.settings().isEmpty());
+  }
+
+  @Test
+  void testGetSystemTableMetadataParsesSettingsFromEngineFull() throws 
Exception {
+    ExposedClickHouseTableOperations ops = newOps();
+    Connection connection = Mockito.mock(Connection.class);
+    PreparedStatement statement = Mockito.mock(PreparedStatement.class);
+    ResultSet resultSet = Mockito.mock(ResultSet.class);
+
+    
Mockito.when(connection.prepareStatement(Mockito.anyString())).thenReturn(statement);
+    Mockito.when(statement.executeQuery()).thenReturn(resultSet);
+    Mockito.when(resultSet.next()).thenReturn(true);
+    Mockito.when(resultSet.getString("sorting_key")).thenReturn("id");
+    Mockito.when(resultSet.getString("engine_full"))
+        .thenReturn(
+            "MergeTree ORDER BY id SETTINGS index_granularity = 4096, "
+                + "min_bytes_for_wide_part = 0");
+
+    Map<String, String> settings =
+        ops.callGetSystemTableMetadata(connection, "db_name", 
"table_name").settings();
+
+    Assertions.assertEquals(2, settings.size());
+    Assertions.assertEquals(
+        "4096", settings.get(TableConstants.SETTINGS_PREFIX + 
"index_granularity"));
+    Assertions.assertEquals(
+        "0", settings.get(TableConstants.SETTINGS_PREFIX + 
"min_bytes_for_wide_part"));
+  }
+
+  @Test
+  void testGetSystemTableMetadataThrowsWhenTableIsNotVisible() throws 
Exception {
+    ExposedClickHouseTableOperations ops = newOps();
+    Connection connection = Mockito.mock(Connection.class);
+    PreparedStatement statement = Mockito.mock(PreparedStatement.class);
+    ResultSet resultSet = Mockito.mock(ResultSet.class);
+
+    
Mockito.when(connection.prepareStatement(Mockito.anyString())).thenReturn(statement);
+    Mockito.when(statement.executeQuery()).thenReturn(resultSet);
+    Mockito.when(resultSet.next()).thenReturn(false);
+
+    NoSuchTableException exception =
+        Assertions.assertThrows(
+            NoSuchTableException.class,
+            () -> ops.callGetSystemTableMetadata(connection, "db_name", 
"table_name"));
+
+    Assertions.assertTrue(exception.getMessage().contains("table_name"));
+    Assertions.assertTrue(exception.getMessage().contains("db_name"));
+  }
+
   // 
---------------------------------------------------------------------------
   // extractEngineParams
   // 
---------------------------------------------------------------------------

Reply via email to