jiangxt2 commented on code in PR #12016:
URL: https://github.com/apache/gravitino/pull/12016#discussion_r3819513037


##########
catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseTableOperations.java:
##########
@@ -1376,23 +1376,174 @@ Transform[] parsePartitioning(String partitionKey) {
     return ClickHouseTableSqlUtils.parsePartitioning(partitionKey);
   }
 
+  /**
+   * Strips PROJECTION definition blocks from a {@code SHOW CREATE TABLE} DDL 
string so that
+   * internal {@code ORDER BY} / {@code PARTITION BY} clauses inside 
projection bodies are not
+   * mistaken for the table-level sort key or partitioning expression.
+   *
+   * <p>A projection block has the form {@code PROJECTION name ( SELECT ... )} 
and sits inside the
+   * column-definition body of the DDL. This method removes every such block 
including the optional
+   * trailing comma, while preserving string literals and respecting nested 
parentheses.
+   *
+   * @param createSql raw {@code SHOW CREATE TABLE} output
+   * @return the DDL with all PROJECTION blocks removed, or the original 
string if none are found
+   */
+  @VisibleForTesting
+  String stripProjections(String createSql) {
+    if (StringUtils.isBlank(createSql)) {
+      return createSql;
+    }
+
+    StringBuilder result = new StringBuilder(createSql.length());
+    int i = 0;
+    int len = createSql.length();
+
+    while (i < len) {
+      char ch = createSql.charAt(i);
+
+      // ----- skip single-quoted string literals (preserve as-is) -----
+      if (ch == '\'') {
+        result.append(ch);
+        i++;
+        while (i < len) {
+          char c = createSql.charAt(i);
+          result.append(c);
+          if (c == '\'') {
+            // escaped single quote: ''
+            if (i + 1 < len && createSql.charAt(i + 1) == '\'') {
+              result.append('\'');
+              i += 2;
+              continue;
+            }
+            i++;
+            break;
+          }
+          i++;
+        }
+        continue;
+      }
+
+      // ----- skip backtick-quoted identifiers (preserve as-is) -----
+      // NOTE: does not handle ClickHouse double-backtick escaping 
(``col``name`).
+      // This is safe because SHOW CREATE TABLE output uses only simple ASCII
+      // identifiers where escaping is never necessary, and PROJECTION is 
always
+      // a keyword (never backtick-quoted).
+      if (ch == '`') {
+        result.append(ch);
+        i++;
+        while (i < len && createSql.charAt(i) != '`') {
+          result.append(createSql.charAt(i));
+          i++;
+        }
+        if (i < len) {
+          result.append(createSql.charAt(i)); // closing backtick
+          i++;
+        }
+        continue;
+      }
+
+      // ----- detect PROJECTION keyword -----
+      if (i + "PROJECTION".length() <= len) {
+        String candidate = createSql.substring(i, i + "PROJECTION".length());
+        if ("PROJECTION".equalsIgnoreCase(candidate)) {
+          // word boundary before
+          boolean boundaryBefore =
+              i == 0 || !Character.isJavaIdentifierPart(createSql.charAt(i - 
1));
+          int afterKw = i + "PROJECTION".length();
+          // word boundary after (or end-of-string)
+          boolean boundaryAfter =
+              afterKw >= len || 
!Character.isJavaIdentifierPart(createSql.charAt(afterKw));
+          if (boundaryBefore && boundaryAfter) {
+            // Skip PROJECTION keyword and whitespace
+            i = afterKw;
+            while (i < len && Character.isWhitespace(createSql.charAt(i))) {
+              i++;
+            }
+            // Skip projection name (backtick-quoted or simple identifier)
+            if (i < len && createSql.charAt(i) == '`') {
+              i++;
+              while (i < len && createSql.charAt(i) != '`') {
+                i++;
+              }
+              if (i < len) i++; // closing backtick
+            } else {
+              while (i < len
+                  && (Character.isJavaIdentifierPart(createSql.charAt(i))
+                      || createSql.charAt(i) == '_')) {
+                i++;
+              }
+            }
+            // Skip whitespace to reach '('
+            while (i < len && Character.isWhitespace(createSql.charAt(i))) {
+              i++;
+            }
+            // Skip the projection body — bracket-counting aware
+            if (i < len && createSql.charAt(i) == '(') {
+              int depth = 1;
+              i++;
+              while (i < len && depth > 0) {
+                char bodyCh = createSql.charAt(i);
+                if (bodyCh == '\'') {
+                  // skip string literal inside projection body
+                  i++;
+                  while (i < len) {
+                    if (createSql.charAt(i) == '\'') {
+                      if (i + 1 < len && createSql.charAt(i + 1) == '\'') {
+                        i += 2;
+                        continue;
+                      }
+                      i++;
+                      break;
+                    }
+                    i++;
+                  }
+                } else {
+                  if (bodyCh == '(') depth++;
+                  else if (bodyCh == ')') depth--;
+                  i++;
+                }
+              }
+              // Skip trailing whitespace and optional comma

Review Comment:
   I avoided parsing projection syntax: table Settings now come only from 
system.tables.engine_full, and SETTINGS_PATTERN is no longer applied to the 
full SHOW CREATE TABLE output. Since engine_full excludes projection 
declarations, projection-level WITH SETTINGS cannot contaminate table Settings.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to