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

mchades pushed a commit to branch branch-1.3
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/branch-1.3 by this push:
     new ef81d1e375 [Cherry-pick to branch-1.3] [MINOR] fix(jdbc): Quote 
comments in generated DDL (#12007) (#12122)
ef81d1e375 is described below

commit ef81d1e3755caeabd4d82c61506faf0704e4f82c
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue Jul 21 19:45:00 2026 +0800

    [Cherry-pick to branch-1.3] [MINOR] fix(jdbc): Quote comments in generated 
DDL (#12007) (#12122)
    
    **Cherry-pick Information:**
    - Original commit: afbd958c45c3007d7324a69e402a9d126ad3d361
    - Target branch: `branch-1.3`
    - Status: ✅ Clean cherry-pick (no conflicts)
    
    Co-authored-by: Qi Yu <[email protected]>
---
 .../catalog/jdbc/utils/JdbcConnectorUtils.java     | 61 +++++++++++++++++
 .../catalog/jdbc/utils/TestJdbcConnectorUtils.java | 69 +++++++++++++++++++
 .../doris/operation/DorisTableOperations.java      | 15 +++--
 .../gravitino/catalog/doris/utils/DorisUtils.java  | 17 ++++-
 .../operation/TestDorisDatabaseOperations.java     |  2 +-
 .../TestDorisDatabaseOperationsSqlGeneration.java  | 39 +++++++++++
 .../TestDorisTableOperationsSqlGeneration.java     | 63 +++++++++++++++++-
 .../catalog/doris/utils/TestDorisUtils.java        | 21 ++++++
 .../mysql/operation/MysqlTableOperations.java      | 12 ++--
 .../TestMysqlTableOperationsSqlGeneration.java     | 60 ++++++++++++++++-
 .../operation/PostgreSqlSchemaOperations.java      |  5 +-
 .../operation/PostgreSqlTableOperations.java       | 20 ++++--
 ...estPostgreSqlSchemaOperationsSqlGeneration.java | 40 +++++++++++
 ...TestPostgreSqlTableOperationsSqlGeneration.java | 59 ++++++++++++++++-
 .../operations/StarRocksTableOperations.java       | 22 +++++--
 .../catalog/starrocks/utils/StarRocksUtils.java    |  7 +-
 .../operation/TestStarRocksTableOperations.java    | 50 ++++++++++++++
 .../TestStarRocksTableOperationsSqlGeneration.java | 77 +++++++++++++++++++++-
 .../starrocks/utils/TestStarRocksUtils.java        | 25 +++++++
 19 files changed, 633 insertions(+), 31 deletions(-)

diff --git 
a/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/utils/JdbcConnectorUtils.java
 
b/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/utils/JdbcConnectorUtils.java
index 62ab29610f..b4d1a8dd92 100644
--- 
a/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/utils/JdbcConnectorUtils.java
+++ 
b/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/utils/JdbcConnectorUtils.java
@@ -22,6 +22,7 @@ import com.google.common.collect.ImmutableList;
 import java.sql.Connection;
 import java.sql.SQLException;
 import java.sql.Statement;
+import javax.annotation.Nullable;
 
 public final class JdbcConnectorUtils {
   public static final ImmutableList<String> TABLE_TYPES = 
ImmutableList.of("TABLE");
@@ -43,6 +44,66 @@ public final class JdbcConnectorUtils {
     }
   }
 
+  /**
+   * Escapes a value for inclusion in a quoted SQL string literal.
+   *
+   * <p>This method escapes both backslashes and the selected quote character. 
Callers are
+   * responsible for adding the surrounding quote characters and any 
dialect-specific literal
+   * prefix.
+   *
+   * @param value the literal value to escape; {@code null} is represented as 
the text {@code null}
+   * @param quote the quote character used by the SQL dialect
+   * @return the escaped literal value
+   * @throws IllegalArgumentException if {@code quote} is neither a single nor 
double quote
+   */
+  public static String escapeSqlLiteral(@Nullable String value, char quote) {
+    if (quote != '\'' && quote != '"') {
+      throw new IllegalArgumentException("SQL literal quote must be a single 
or double quote");
+    }
+
+    String literalValue = String.valueOf(value);
+    String quoteString = String.valueOf(quote);
+    return literalValue.replace("\\", "\\\\").replace(quoteString, quoteString 
+ quoteString);
+  }
+
+  /**
+   * Reverses {@link #escapeSqlLiteral(String, char)} for text captured from a 
quoted SQL string
+   * literal, e.g. when parsing {@code SHOW CREATE} output.
+   *
+   * <p>Both doubled-quote ({@code ""} or {@code ''}) and backslash ({@code 
\"}, {@code \\}) escape
+   * styles are unescaped, since dialects differ in which form they emit. Any 
other character is
+   * kept as is, so text that was never escaped passes through unchanged.
+   *
+   * @param value the captured literal text without the surrounding quote 
characters
+   * @param quote the quote character used by the SQL dialect
+   * @return the unescaped literal value
+   * @throws IllegalArgumentException if {@code quote} is neither a single nor 
double quote
+   */
+  public static String unescapeSqlLiteral(String value, char quote) {
+    if (quote != '\'' && quote != '"') {
+      throw new IllegalArgumentException("SQL literal quote must be a single 
or double quote");
+    }
+
+    StringBuilder result = new StringBuilder(value.length());
+    for (int i = 0; i < value.length(); i++) {
+      char current = value.charAt(i);
+      if (current == '\\' && i + 1 < value.length()) {
+        char next = value.charAt(i + 1);
+        if (next == '\\' || next == quote) {
+          result.append(next);
+          i++;
+          continue;
+        }
+      } else if (current == quote && i + 1 < value.length() && value.charAt(i 
+ 1) == quote) {
+        result.append(quote);
+        i++;
+        continue;
+      }
+      result.append(current);
+    }
+    return result.toString();
+  }
+
   public static String[] getTableTypes() {
     return TABLE_TYPES.toArray(new String[0]);
   }
diff --git 
a/catalogs/catalog-jdbc-common/src/test/java/org/apache/gravitino/catalog/jdbc/utils/TestJdbcConnectorUtils.java
 
b/catalogs/catalog-jdbc-common/src/test/java/org/apache/gravitino/catalog/jdbc/utils/TestJdbcConnectorUtils.java
new file mode 100644
index 0000000000..26c91d0564
--- /dev/null
+++ 
b/catalogs/catalog-jdbc-common/src/test/java/org/apache/gravitino/catalog/jdbc/utils/TestJdbcConnectorUtils.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.catalog.jdbc.utils;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestJdbcConnectorUtils {
+
+  @Test
+  public void testEscapeSqlLiteral() {
+    String value = "owner\\'s \"comment\"; DROP TABLE marker; --";
+
+    Assertions.assertEquals(
+        "owner\\\\''s \"comment\"; DROP TABLE marker; --",
+        JdbcConnectorUtils.escapeSqlLiteral(value, '\''));
+    Assertions.assertEquals(
+        "owner\\\\'s \"\"comment\"\"; DROP TABLE marker; --",
+        JdbcConnectorUtils.escapeSqlLiteral(value, '"'));
+    Assertions.assertThrows(
+        IllegalArgumentException.class, () -> 
JdbcConnectorUtils.escapeSqlLiteral(value, '`'));
+    Assertions.assertEquals("null", JdbcConnectorUtils.escapeSqlLiteral(null, 
'\''));
+  }
+
+  @Test
+  public void testUnescapeSqlLiteral() {
+    String value = "owner\\'s \"comment\"; DROP TABLE marker; --";
+
+    // Round-trip: unescape(escape(x)) == x for both quote styles.
+    Assertions.assertEquals(
+        value,
+        JdbcConnectorUtils.unescapeSqlLiteral(
+            JdbcConnectorUtils.escapeSqlLiteral(value, '\''), '\''));
+    Assertions.assertEquals(
+        value,
+        JdbcConnectorUtils.unescapeSqlLiteral(
+            JdbcConnectorUtils.escapeSqlLiteral(value, '"'), '"'));
+
+    // Backslash-style escaping is unescaped too.
+    Assertions.assertEquals(
+        "owner's \"comment\"",
+        JdbcConnectorUtils.unescapeSqlLiteral("owner's \\\"comment\\\"", '"'));
+
+    // Text that was never escaped passes through unchanged, including a lone 
backslash before an
+    // ordinary character and a lone trailing backslash.
+    Assertions.assertEquals(
+        "D:\\data; --", JdbcConnectorUtils.unescapeSqlLiteral("D:\\data; --", 
'"'));
+    Assertions.assertEquals("tail\\", 
JdbcConnectorUtils.unescapeSqlLiteral("tail\\", '"'));
+
+    Assertions.assertThrows(
+        IllegalArgumentException.class, () -> 
JdbcConnectorUtils.unescapeSqlLiteral(value, '`'));
+  }
+}
diff --git 
a/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisTableOperations.java
 
b/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisTableOperations.java
index e82e44240b..926c193267 100644
--- 
a/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisTableOperations.java
+++ 
b/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisTableOperations.java
@@ -22,6 +22,7 @@ import static 
org.apache.gravitino.catalog.doris.DorisCatalog.DORIS_TABLE_PROPER
 import static 
org.apache.gravitino.catalog.doris.DorisTablePropertiesMetadata.DEFAULT_REPLICATION_FACTOR_IN_SERVER_SIDE;
 import static 
org.apache.gravitino.catalog.doris.DorisTablePropertiesMetadata.REPLICATION_FACTOR;
 import static 
org.apache.gravitino.catalog.doris.utils.DorisUtils.generatePartitionSqlFragment;
+import static 
org.apache.gravitino.catalog.jdbc.utils.JdbcConnectorUtils.escapeSqlLiteral;
 import static org.apache.gravitino.rel.Column.DEFAULT_VALUE_NOT_SET;
 
 import com.google.common.annotations.VisibleForTesting;
@@ -136,7 +137,7 @@ public class DorisTableOperations extends 
JdbcTableOperations {
 
     // Add table comment if specified
     if (StringUtils.isNotEmpty(comment)) {
-      sqlBuilder.append(" COMMENT \"").append(comment).append("\"");
+      sqlBuilder.append(" COMMENT \"").append(escapeSqlLiteral(comment, 
'"')).append("\"");
     }
 
     // Add Partition Info
@@ -796,7 +797,7 @@ public class DorisTableOperations extends 
JdbcTableOperations {
           newComment = StringIdentifier.addToComment(identifier, newComment);
         }
       }
-      alterSql.add("MODIFY COMMENT \"" + newComment + "\"");
+      alterSql.add("MODIFY COMMENT \"" + escapeSqlLiteral(newComment, '"') + 
"\"");
     }
 
     if (CollectionUtils.isEmpty(alterSql)) {
@@ -848,7 +849,8 @@ public class DorisTableOperations extends 
JdbcTableOperations {
     }
     String col = updateColumnComment.fieldName()[0];
 
-    return String.format("MODIFY COLUMN `%s` COMMENT '%s'", col, newComment);
+    return String.format(
+        "MODIFY COLUMN `%s` COMMENT '%s'", col, escapeSqlLiteral(newComment, 
'\''));
   }
 
   private String addColumnFieldDefinition(TableChange.AddColumn addColumn) {
@@ -873,7 +875,10 @@ public class DorisTableOperations extends 
JdbcTableOperations {
     }
     // Append comment if available
     if (StringUtils.isNotEmpty(addColumn.getComment())) {
-      columnDefinition.append("COMMENT 
'").append(addColumn.getComment()).append("' ");
+      columnDefinition
+          .append("COMMENT '")
+          .append(escapeSqlLiteral(addColumn.getComment(), '\''))
+          .append("' ");
     }
 
     // Append position if available
@@ -985,7 +990,7 @@ public class DorisTableOperations extends 
JdbcTableOperations {
 
     // Add column comment if specified
     if (StringUtils.isNotEmpty(column.comment())) {
-      sqlBuilder.append("COMMENT '").append(column.comment()).append("' ");
+      sqlBuilder.append("COMMENT '").append(escapeSqlLiteral(column.comment(), 
'\'')).append("' ");
     }
     return sqlBuilder;
   }
diff --git 
a/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/utils/DorisUtils.java
 
b/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/utils/DorisUtils.java
index 03d4dd136e..80a376411e 100644
--- 
a/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/utils/DorisUtils.java
+++ 
b/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/utils/DorisUtils.java
@@ -18,6 +18,9 @@
  */
 package org.apache.gravitino.catalog.doris.utils;
 
+import static 
org.apache.gravitino.catalog.jdbc.utils.JdbcConnectorUtils.escapeSqlLiteral;
+import static 
org.apache.gravitino.catalog.jdbc.utils.JdbcConnectorUtils.unescapeSqlLiteral;
+
 import com.google.common.collect.ImmutableList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -65,7 +68,13 @@ public final class DorisUtils {
     StringBuilder sqlBuilder = new StringBuilder(" PROPERTIES (\n");
     sqlBuilder.append(
         properties.entrySet().stream()
-            .map(entry -> "\"" + entry.getKey() + "\"=\"" + entry.getValue() + 
"\"")
+            .map(
+                entry ->
+                    "\""
+                        + escapeSqlLiteral(entry.getKey(), '"')
+                        + "\"=\""
+                        + escapeSqlLiteral(entry.getValue(), '"')
+                        + "\"")
             .collect(Collectors.joining(",\n")));
     sqlBuilder.append("\n)");
     return sqlBuilder.toString();
@@ -87,8 +96,10 @@ public final class DorisUtils {
       if (isProperties) {
         final Matcher matcherProperties = patternProperties.matcher(line);
         if (matcherProperties.find()) {
-          final String key = matcherProperties.group(1).trim();
-          String value = matcherProperties.group(2).trim();
+          // generatePropertiesSql escapes keys and values for double-quoted 
literals, and SHOW
+          // CREATE echoes them escaped; unescape both so callers observe the 
original text.
+          final String key = 
unescapeSqlLiteral(matcherProperties.group(1).trim(), '"');
+          String value = unescapeSqlLiteral(matcherProperties.group(2).trim(), 
'"');
           properties.put(key, value);
         }
       }
diff --git 
a/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/operation/TestDorisDatabaseOperations.java
 
b/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/operation/TestDorisDatabaseOperations.java
index 2ea78fac7b..6713452816 100644
--- 
a/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/operation/TestDorisDatabaseOperations.java
+++ 
b/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/operation/TestDorisDatabaseOperations.java
@@ -33,7 +33,7 @@ public class TestDorisDatabaseOperations extends TestDoris {
   @Test
   public void testBaseOperationDatabase() {
     String databaseName = RandomNameUtils.genRandomName("it_db");
-    String comment = "comment";
+    String comment = "owner's \"comment\" C:\\tmp; DROP DATABASE marker; --";
 
     Map<String, String> properties = new HashMap<>();
     properties.put("property1", "value1");
diff --git 
a/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/operation/TestDorisDatabaseOperationsSqlGeneration.java
 
b/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/operation/TestDorisDatabaseOperationsSqlGeneration.java
new file mode 100644
index 0000000000..3f995a21c8
--- /dev/null
+++ 
b/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/operation/TestDorisDatabaseOperationsSqlGeneration.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.catalog.doris.operation;
+
+import java.util.Collections;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class TestDorisDatabaseOperationsSqlGeneration {
+
+  @Test
+  public void testEscapeCommentInGeneratedSql() {
+    DorisDatabaseOperations operations = new DorisDatabaseOperations();
+    String comment = "owner's \"comment\" C:\\tmp; DROP DATABASE marker; --";
+
+    String sql =
+        operations.generateCreateDatabaseSql("test_database", comment, 
Collections.emptyMap());
+
+    Assertions.assertTrue(
+        sql.contains("\"comment\"=\"owner's \"\"comment\"\" C:\\\\tmp; DROP 
DATABASE marker; --\""),
+        sql);
+  }
+}
diff --git 
a/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/operation/TestDorisTableOperationsSqlGeneration.java
 
b/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/operation/TestDorisTableOperationsSqlGeneration.java
index 705b23a735..68b442bbe9 100644
--- 
a/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/operation/TestDorisTableOperationsSqlGeneration.java
+++ 
b/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/operation/TestDorisTableOperationsSqlGeneration.java
@@ -73,16 +73,31 @@ public class TestDorisTableOperationsSqlGeneration {
 
     public String createTableSql(
         String tableName, JdbcColumn[] columns, Distribution distribution) {
+      return createTableSql(tableName, columns, distribution, "comment");
+    }
+
+    public String createTableSql(
+        String tableName, JdbcColumn[] columns, Distribution distribution, 
String comment) {
       return generateCreateTableSql(
           tableName,
           columns,
-          "comment",
+          comment,
           Collections.emptyMap(),
           Transforms.EMPTY_TRANSFORM,
           distribution,
           Indexes.EMPTY_INDEXES);
     }
 
+    public String alterTableSql(String tableName, TableChange... changes) {
+      return generateAlterTableSql("database", tableName, changes);
+    }
+
+    @Override
+    protected JdbcTable getOrCreateTable(
+        String databaseName, String tableName, JdbcTable lazyLoadCreateTable) {
+      return JdbcTable.builder().withName(tableName).build();
+    }
+
     public String createTableSqlWithIndexes(
         String tableName, JdbcColumn[] columns, Distribution distribution, 
Index[] indexes) {
       return generateCreateTableSql(
@@ -148,6 +163,52 @@ public class TestDorisTableOperationsSqlGeneration {
         "Should contain DEFAULT value but was: " + sql);
   }
 
+  @Test
+  public void testEscapeCommentsInGeneratedSql() {
+    TestableDorisTableOperations ops = new TestableDorisTableOperations();
+    TestableDorisTableOperations mockOps = Mockito.spy(ops);
+    Mockito.doAnswer(a -> a.getArgument(0))
+        .when(mockOps)
+        .appendNecessaryProperties(Mockito.anyMap());
+    String tableComment = "owner\"; DROP TABLE marker; --";
+    String columnComment = "owner's comment; DROP TABLE marker; --";
+    JdbcColumn column =
+        JdbcColumn.builder()
+            .withName("col1")
+            .withType(Types.IntegerType.get())
+            .withComment(columnComment)
+            .build();
+    Distribution distribution = Distributions.hash(1, 
NamedReference.field("col1"));
+
+    String createSql =
+        mockOps.createTableSql("test_table", new JdbcColumn[] {column}, 
distribution, tableComment);
+    Assertions.assertTrue(
+        createSql.contains("COMMENT \"owner\"\"; DROP TABLE marker; --\""), 
createSql);
+    Assertions.assertTrue(
+        createSql.contains("COMMENT 'owner''s comment; DROP TABLE marker; 
--'"), createSql);
+
+    String alterSql =
+        mockOps.alterTableSql(
+            "test_table", TableChange.updateColumnComment(new String[] 
{"col1"}, columnComment));
+    Assertions.assertTrue(
+        alterSql.contains("MODIFY COLUMN `col1` COMMENT 'owner''s comment; 
DROP TABLE marker; --'"),
+        alterSql);
+  }
+
+  @Test
+  public void testEscapeAddColumnCommentInGeneratedSql() {
+    TestableDorisTableOperations ops = new TestableDorisTableOperations();
+    String comment = "owner\\'s \"comment\"; --";
+
+    String alterSql =
+        ops.alterTableSql(
+            "test_table",
+            TableChange.addColumn(new String[] {"col2"}, 
Types.IntegerType.get(), comment));
+
+    Assertions.assertTrue(alterSql.contains("ADD COLUMN `col2`"), alterSql);
+    Assertions.assertTrue(alterSql.contains("COMMENT 'owner\\\\''s 
\"comment\"; --'"), alterSql);
+  }
+
   @Test
   public void testCreateTableWithPrimaryKeyIndex() {
     TestableDorisTableOperations ops = new TestableDorisTableOperations();
diff --git 
a/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/utils/TestDorisUtils.java
 
b/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/utils/TestDorisUtils.java
index c26f025beb..4a2b37981e 100644
--- 
a/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/utils/TestDorisUtils.java
+++ 
b/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/utils/TestDorisUtils.java
@@ -54,6 +54,11 @@ public class TestDorisUtils {
     result = DorisUtils.generatePropertiesSql(properties);
     assertEquals(" PROPERTIES (\n\"key\"=\"value\"\n)", result);
 
+    properties = Collections.singletonMap("key\"name", "owner's \"comment\" 
C:\\tmp; --");
+    result = DorisUtils.generatePropertiesSql(properties);
+    assertEquals(
+        " PROPERTIES (\n\"key\"\"name\"=\"owner's \"\"comment\"\" C:\\\\tmp; 
--\"\n)", result);
+
     // Test when properties has multiple entries
     properties = new HashMap<>();
     properties.put("key1", "value1");
@@ -92,6 +97,22 @@ public class TestDorisUtils {
     result = DorisUtils.extractPropertiesFromSql(createTableSql);
     assertEquals("value1", result.get("property1"));
     assertEquals("comment", result.get("comment"));
+
+    // Escaped SHOW CREATE output (doubled quotes and backslashes, as 
generatePropertiesSql
+    // emits) round-trips back to the original key/value text.
+    createTableSql =
+        "CREATE DATABASE `test`\nPROPERTIES (\n"
+            + "\"key\"\"name\" = \"owner's \"\"comment\"\" D:\\\\data; 
--\"\n)";
+    result = DorisUtils.extractPropertiesFromSql(createTableSql);
+    assertEquals("owner's \"comment\" D:\\data; --", result.get("key\"name"));
+
+    // Backslash-style escaping (\" and \\) is unescaped too, and a lone 
backslash before an
+    // ordinary character passes through unchanged.
+    createTableSql =
+        "CREATE DATABASE `test`\nPROPERTIES (\n"
+            + "\"plain\" = \"owner\\\\s \\\"comment\\\" D:\\data; --\"\n)";
+    result = DorisUtils.extractPropertiesFromSql(createTableSql);
+    assertEquals("owner\\s \"comment\" D:\\data; --", result.get("plain"));
   }
 
   @Test
diff --git 
a/catalogs/catalog-jdbc-mysql/src/main/java/org/apache/gravitino/catalog/mysql/operation/MysqlTableOperations.java
 
b/catalogs/catalog-jdbc-mysql/src/main/java/org/apache/gravitino/catalog/mysql/operation/MysqlTableOperations.java
index 0a597ef214..f30ef31f4d 100644
--- 
a/catalogs/catalog-jdbc-mysql/src/main/java/org/apache/gravitino/catalog/mysql/operation/MysqlTableOperations.java
+++ 
b/catalogs/catalog-jdbc-mysql/src/main/java/org/apache/gravitino/catalog/mysql/operation/MysqlTableOperations.java
@@ -18,6 +18,7 @@
  */
 package org.apache.gravitino.catalog.mysql.operation;
 
+import static 
org.apache.gravitino.catalog.jdbc.utils.JdbcConnectorUtils.escapeSqlLiteral;
 import static 
org.apache.gravitino.catalog.mysql.MysqlTablePropertiesMetadata.MYSQL_AUTO_INCREMENT_OFFSET_KEY;
 import static 
org.apache.gravitino.catalog.mysql.MysqlTablePropertiesMetadata.MYSQL_ENGINE_KEY;
 import static org.apache.gravitino.rel.Column.DEFAULT_VALUE_NOT_SET;
@@ -113,7 +114,7 @@ public class MysqlTableOperations extends 
JdbcTableOperations {
 
     // Add table comment if specified
     if (StringUtils.isNotEmpty(comment)) {
-      sqlBuilder.append(" COMMENT='").append(comment).append("'");
+      sqlBuilder.append(" COMMENT='").append(escapeSqlLiteral(comment, 
'\'')).append("'");
     }
 
     // Add table properties if any
@@ -295,7 +296,7 @@ public class MysqlTableOperations extends 
JdbcTableOperations {
           newComment = StringIdentifier.addToComment(identifier, newComment);
         }
       }
-      alterSql.add("COMMENT '" + newComment + "'");
+      alterSql.add("COMMENT '" + escapeSqlLiteral(newComment, '\'') + "'");
     }
 
     if (!setProperties.isEmpty()) {
@@ -460,7 +461,10 @@ public class MysqlTableOperations extends 
JdbcTableOperations {
     }
     // Append comment if available
     if (StringUtils.isNotEmpty(addColumn.getComment())) {
-      columnDefinition.append("COMMENT 
'").append(addColumn.getComment()).append("' ");
+      columnDefinition
+          .append("COMMENT '")
+          .append(escapeSqlLiteral(addColumn.getComment(), '\''))
+          .append("' ");
     }
 
     // Append default value if available
@@ -626,7 +630,7 @@ public class MysqlTableOperations extends 
JdbcTableOperations {
 
     // Add column comment if specified
     if (StringUtils.isNotEmpty(column.comment())) {
-      sqlBuilder.append("COMMENT '").append(column.comment()).append("' ");
+      sqlBuilder.append("COMMENT '").append(escapeSqlLiteral(column.comment(), 
'\'')).append("' ");
     }
     return sqlBuilder;
   }
diff --git 
a/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/operation/TestMysqlTableOperationsSqlGeneration.java
 
b/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/operation/TestMysqlTableOperationsSqlGeneration.java
index 2727feee23..ad8966c2c8 100644
--- 
a/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/operation/TestMysqlTableOperationsSqlGeneration.java
+++ 
b/catalogs/catalog-jdbc-mysql/src/test/java/org/apache/gravitino/catalog/mysql/operation/TestMysqlTableOperationsSqlGeneration.java
@@ -20,9 +20,11 @@ package org.apache.gravitino.catalog.mysql.operation;
 
 import java.util.Collections;
 import org.apache.gravitino.catalog.jdbc.JdbcColumn;
+import org.apache.gravitino.catalog.jdbc.JdbcTable;
 import 
org.apache.gravitino.catalog.jdbc.converter.JdbcColumnDefaultValueConverter;
 import org.apache.gravitino.catalog.jdbc.converter.JdbcExceptionConverter;
 import org.apache.gravitino.catalog.mysql.converter.MysqlTypeConverter;
+import org.apache.gravitino.rel.TableChange;
 import org.apache.gravitino.rel.expressions.distributions.Distributions;
 import org.apache.gravitino.rel.expressions.literals.Literals;
 import org.apache.gravitino.rel.expressions.transforms.Transforms;
@@ -41,15 +43,29 @@ public class TestMysqlTableOperationsSqlGeneration {
     }
 
     public String createTableSql(String tableName, JdbcColumn[] columns) {
+      return createTableSql(tableName, columns, "comment");
+    }
+
+    public String createTableSql(String tableName, JdbcColumn[] columns, 
String comment) {
       return generateCreateTableSql(
           tableName,
           columns,
-          "comment",
+          comment,
           Collections.emptyMap(),
           Transforms.EMPTY_TRANSFORM,
           Distributions.NONE,
           Indexes.EMPTY_INDEXES);
     }
+
+    public String alterTableSql(String tableName, TableChange... changes) {
+      return generateAlterTableSql("database", tableName, changes);
+    }
+
+    @Override
+    protected JdbcTable getOrCreateTable(
+        String databaseName, String tableName, JdbcTable lazyLoadCreateTable) {
+      return JdbcTable.builder().withName(tableName).build();
+    }
   }
 
   @Test
@@ -89,4 +105,46 @@ public class TestMysqlTableOperationsSqlGeneration {
         sql.contains("DEFAULT " + 
converter.fromGravitino(col1.defaultValue())),
         "Should contain DEFAULT value but was: " + sql);
   }
+
+  @Test
+  public void testEscapeCommentsInGeneratedSql() {
+    TestableMysqlTableOperations ops = new TestableMysqlTableOperations();
+    String injectedComment = "owner's comment; DROP TABLE marker; --";
+    JdbcColumn column =
+        JdbcColumn.builder()
+            .withName("col1")
+            .withType(Types.IntegerType.get())
+            .withComment(injectedComment)
+            .build();
+
+    String createSql = ops.createTableSql("test_table", new JdbcColumn[] 
{column}, injectedComment);
+    Assertions.assertTrue(
+        createSql.contains("COMMENT 'owner''s comment; DROP TABLE marker; 
--'"), createSql);
+    Assertions.assertTrue(
+        createSql.contains("COMMENT='owner''s comment; DROP TABLE marker; 
--'"), createSql);
+
+    String commentWithIdentifier =
+        injectedComment + " (From Gravitino, DO NOT EDIT: gravitino.v1.uid1)";
+    String alterSql =
+        ops.alterTableSql("test_table", 
TableChange.updateComment(commentWithIdentifier));
+    Assertions.assertTrue(
+        alterSql.contains(
+            "COMMENT 'owner''s comment; DROP TABLE marker; -- "
+                + "(From Gravitino, DO NOT EDIT: gravitino.v1.uid1)'"),
+        alterSql);
+  }
+
+  @Test
+  public void testEscapeAddColumnCommentInGeneratedSql() {
+    TestableMysqlTableOperations ops = new TestableMysqlTableOperations();
+    String comment = "owner\\'s \"comment\"; --";
+
+    String alterSql =
+        ops.alterTableSql(
+            "test_table",
+            TableChange.addColumn(new String[] {"col2"}, 
Types.IntegerType.get(), comment));
+
+    Assertions.assertTrue(alterSql.contains("ADD COLUMN `col2`"), alterSql);
+    Assertions.assertTrue(alterSql.contains("COMMENT 'owner\\\\''s 
\"comment\"; --'"), alterSql);
+  }
 }
diff --git 
a/catalogs/catalog-jdbc-postgresql/src/main/java/org/apache/gravitino/catalog/postgresql/operation/PostgreSqlSchemaOperations.java
 
b/catalogs/catalog-jdbc-postgresql/src/main/java/org/apache/gravitino/catalog/postgresql/operation/PostgreSqlSchemaOperations.java
index 1df76e914d..3996297b01 100644
--- 
a/catalogs/catalog-jdbc-postgresql/src/main/java/org/apache/gravitino/catalog/postgresql/operation/PostgreSqlSchemaOperations.java
+++ 
b/catalogs/catalog-jdbc-postgresql/src/main/java/org/apache/gravitino/catalog/postgresql/operation/PostgreSqlSchemaOperations.java
@@ -18,6 +18,7 @@
  */
 package org.apache.gravitino.catalog.postgresql.operation;
 
+import static 
org.apache.gravitino.catalog.jdbc.utils.JdbcConnectorUtils.escapeSqlLiteral;
 import static 
org.apache.gravitino.catalog.postgresql.operation.PostgreSqlTableOperations.PG_QUOTE;
 
 import com.google.common.collect.ImmutableSet;
@@ -105,8 +106,8 @@ public class PostgreSqlSchemaOperations extends 
JdbcDatabaseOperations {
           .append(PG_QUOTE)
           .append(schema)
           .append(PG_QUOTE)
-          .append(" IS '")
-          .append(comment)
+          .append(" IS E'")
+          .append(escapeSqlLiteral(comment, '\''))
           .append("'");
     }
     return sqlBuilder.toString();
diff --git 
a/catalogs/catalog-jdbc-postgresql/src/main/java/org/apache/gravitino/catalog/postgresql/operation/PostgreSqlTableOperations.java
 
b/catalogs/catalog-jdbc-postgresql/src/main/java/org/apache/gravitino/catalog/postgresql/operation/PostgreSqlTableOperations.java
index a47e136e05..83be0efcaa 100644
--- 
a/catalogs/catalog-jdbc-postgresql/src/main/java/org/apache/gravitino/catalog/postgresql/operation/PostgreSqlTableOperations.java
+++ 
b/catalogs/catalog-jdbc-postgresql/src/main/java/org/apache/gravitino/catalog/postgresql/operation/PostgreSqlTableOperations.java
@@ -18,6 +18,8 @@
  */
 package org.apache.gravitino.catalog.postgresql.operation;
 
+import static 
org.apache.gravitino.catalog.jdbc.utils.JdbcConnectorUtils.escapeSqlLiteral;
+
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Lists;
@@ -65,7 +67,7 @@ public class PostgreSqlTableOperations extends 
JdbcTableOperations
   public static final String NEW_LINE = "\n";
   public static final String ALTER_TABLE = "ALTER TABLE ";
   public static final String ALTER_COLUMN = "ALTER COLUMN ";
-  public static final String IS = " IS '";
+  public static final String IS = " IS E'";
   public static final String COLUMN_COMMENT = "COMMENT ON COLUMN ";
   public static final String TABLE_COMMENT = "COMMENT ON TABLE ";
 
@@ -204,7 +206,7 @@ public class PostgreSqlTableOperations extends 
JdbcTableOperations
           .append(tableName)
           .append(PG_QUOTE)
           .append(IS)
-          .append(comment)
+          .append(escapeSqlLiteral(comment, '\''))
           .append("';");
     }
     Arrays.stream(columns)
@@ -222,7 +224,7 @@ public class PostgreSqlTableOperations extends 
JdbcTableOperations
                     .append(jdbcColumn.name())
                     .append(PG_QUOTE)
                     .append(IS)
-                    .append(jdbcColumn.comment())
+                    .append(escapeSqlLiteral(jdbcColumn.comment(), '\''))
                     .append("';"));
 
     // Return the generated SQL statement
@@ -510,7 +512,13 @@ public class PostgreSqlTableOperations extends 
JdbcTableOperations
         }
       }
     }
-    return TABLE_COMMENT + PG_QUOTE + jdbcTable.name() + PG_QUOTE + IS + 
newComment + "';";
+    return TABLE_COMMENT
+        + PG_QUOTE
+        + jdbcTable.name()
+        + PG_QUOTE
+        + IS
+        + escapeSqlLiteral(newComment, '\'')
+        + "';";
   }
 
   private String deleteColumnFieldDefinition(
@@ -689,7 +697,7 @@ public class PostgreSqlTableOperations extends 
JdbcTableOperations
               + col
               + PG_QUOTE
               + IS
-              + addColumn.getComment()
+              + escapeSqlLiteral(addColumn.getComment(), '\'')
               + "';");
     }
     return result;
@@ -711,7 +719,7 @@ public class PostgreSqlTableOperations extends 
JdbcTableOperations
         + col
         + PG_QUOTE
         + IS
-        + newComment
+        + escapeSqlLiteral(newComment, '\'')
         + "';";
   }
 
diff --git 
a/catalogs/catalog-jdbc-postgresql/src/test/java/org/apache/gravitino/catalog/postgresql/operation/TestPostgreSqlSchemaOperationsSqlGeneration.java
 
b/catalogs/catalog-jdbc-postgresql/src/test/java/org/apache/gravitino/catalog/postgresql/operation/TestPostgreSqlSchemaOperationsSqlGeneration.java
new file mode 100644
index 0000000000..c961b49537
--- /dev/null
+++ 
b/catalogs/catalog-jdbc-postgresql/src/test/java/org/apache/gravitino/catalog/postgresql/operation/TestPostgreSqlSchemaOperationsSqlGeneration.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.catalog.postgresql.operation;
+
+import java.util.Collections;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestPostgreSqlSchemaOperationsSqlGeneration {
+
+  @Test
+  public void testEscapeCommentInGeneratedSql() {
+    PostgreSqlSchemaOperations operations = new PostgreSqlSchemaOperations();
+
+    String sql =
+        operations.generateCreateDatabaseSql(
+            "test_schema", "owner\\'s comment; DROP SCHEMA marker; --", 
Collections.emptyMap());
+
+    Assertions.assertEquals(
+        "CREATE SCHEMA \"test_schema\";COMMENT ON SCHEMA \"test_schema\" "
+            + "IS E'owner\\\\''s comment; DROP SCHEMA marker; --'",
+        sql);
+  }
+}
diff --git 
a/catalogs/catalog-jdbc-postgresql/src/test/java/org/apache/gravitino/catalog/postgresql/operation/TestPostgreSqlTableOperationsSqlGeneration.java
 
b/catalogs/catalog-jdbc-postgresql/src/test/java/org/apache/gravitino/catalog/postgresql/operation/TestPostgreSqlTableOperationsSqlGeneration.java
index 12f2e1834e..f428271c6a 100644
--- 
a/catalogs/catalog-jdbc-postgresql/src/test/java/org/apache/gravitino/catalog/postgresql/operation/TestPostgreSqlTableOperationsSqlGeneration.java
+++ 
b/catalogs/catalog-jdbc-postgresql/src/test/java/org/apache/gravitino/catalog/postgresql/operation/TestPostgreSqlTableOperationsSqlGeneration.java
@@ -20,9 +20,11 @@ package org.apache.gravitino.catalog.postgresql.operation;
 
 import java.util.Collections;
 import org.apache.gravitino.catalog.jdbc.JdbcColumn;
+import org.apache.gravitino.catalog.jdbc.JdbcTable;
 import 
org.apache.gravitino.catalog.jdbc.converter.JdbcColumnDefaultValueConverter;
 import org.apache.gravitino.catalog.jdbc.converter.JdbcExceptionConverter;
 import 
org.apache.gravitino.catalog.postgresql.converter.PostgreSqlTypeConverter;
+import org.apache.gravitino.rel.TableChange;
 import org.apache.gravitino.rel.expressions.distributions.Distributions;
 import org.apache.gravitino.rel.expressions.literals.Literals;
 import org.apache.gravitino.rel.expressions.transforms.Transforms;
@@ -41,15 +43,29 @@ public class TestPostgreSqlTableOperationsSqlGeneration {
     }
 
     public String createTableSql(String tableName, JdbcColumn[] columns) {
+      return createTableSql(tableName, columns, "comment");
+    }
+
+    public String createTableSql(String tableName, JdbcColumn[] columns, 
String comment) {
       return generateCreateTableSql(
           tableName,
           columns,
-          "comment",
+          comment,
           Collections.emptyMap(),
           Transforms.EMPTY_TRANSFORM,
           Distributions.NONE,
           Indexes.EMPTY_INDEXES);
     }
+
+    public String alterTableSql(String tableName, TableChange... changes) {
+      return generateAlterTableSql("database", tableName, changes);
+    }
+
+    @Override
+    protected JdbcTable getOrCreateTable(
+        String databaseName, String tableName, JdbcTable lazyLoadCreateTable) {
+      return JdbcTable.builder().withName(tableName).build();
+    }
   }
 
   @Test
@@ -89,4 +105,45 @@ public class TestPostgreSqlTableOperationsSqlGeneration {
         sql.contains("DEFAULT " + 
converter.fromGravitino(col1.defaultValue())),
         "Should contain DEFAULT value but was: " + sql);
   }
+
+  @Test
+  public void testEscapeCommentsInGeneratedSql() {
+    TestablePostgreSqlTableOperations ops = new 
TestablePostgreSqlTableOperations();
+    String injectedComment = "owner\\'s comment; DROP TABLE marker; --";
+    JdbcColumn column =
+        JdbcColumn.builder()
+            .withName("col1")
+            .withType(Types.IntegerType.get())
+            .withComment(injectedComment)
+            .build();
+
+    String createSql = ops.createTableSql("test_table", new JdbcColumn[] 
{column}, injectedComment);
+    Assertions.assertTrue(
+        createSql.contains("IS E'owner\\\\''s comment; DROP TABLE marker; 
--';"), createSql);
+
+    String alterSql =
+        ops.alterTableSql(
+            "test_table", TableChange.updateColumnComment(new String[] 
{"col1"}, injectedComment));
+    Assertions.assertEquals(
+        "COMMENT ON COLUMN \"test_table\".\"col1\" "
+            + "IS E'owner\\\\''s comment; DROP TABLE marker; --';",
+        alterSql);
+  }
+
+  @Test
+  public void testEscapeAddColumnCommentInGeneratedSql() {
+    TestablePostgreSqlTableOperations ops = new 
TestablePostgreSqlTableOperations();
+    String comment = "owner\\'s \"comment\"; --";
+
+    String alterSql =
+        ops.alterTableSql(
+            "test_table",
+            TableChange.addColumn(new String[] {"col2"}, 
Types.IntegerType.get(), comment));
+
+    Assertions.assertTrue(alterSql.contains("ADD COLUMN \"col2\""), alterSql);
+    Assertions.assertTrue(
+        alterSql.contains(
+            "COMMENT ON COLUMN \"test_table\".\"col2\" IS E'owner\\\\''s 
\"comment\"; --';"),
+        alterSql);
+  }
 }
diff --git 
a/catalogs/catalog-jdbc-starrocks/src/main/java/org/apache/gravitino/catalog/starrocks/operations/StarRocksTableOperations.java
 
b/catalogs/catalog-jdbc-starrocks/src/main/java/org/apache/gravitino/catalog/starrocks/operations/StarRocksTableOperations.java
index f4dc067ba3..e123be6103 100644
--- 
a/catalogs/catalog-jdbc-starrocks/src/main/java/org/apache/gravitino/catalog/starrocks/operations/StarRocksTableOperations.java
+++ 
b/catalogs/catalog-jdbc-starrocks/src/main/java/org/apache/gravitino/catalog/starrocks/operations/StarRocksTableOperations.java
@@ -18,6 +18,7 @@
  */
 package org.apache.gravitino.catalog.starrocks.operations;
 
+import static 
org.apache.gravitino.catalog.jdbc.utils.JdbcConnectorUtils.escapeSqlLiteral;
 import static org.apache.gravitino.rel.Column.DEFAULT_VALUE_NOT_SET;
 
 import com.google.common.base.Preconditions;
@@ -118,7 +119,7 @@ public class StarRocksTableOperations extends 
JdbcTableOperations {
     sqlBuilder.append(")\n");
     if (StringUtils.isNotEmpty(comment)) {
       comment = StringIdentifier.addToComment(StringIdentifier.DUMMY_ID, 
comment);
-      sqlBuilder.append(" COMMENT \"").append(comment).append("\"");
+      sqlBuilder.append(" COMMENT \"").append(escapeSqlLiteral(comment, 
'"')).append("\"");
     }
 
     appendPartitionSql(partitioning, columns, sqlBuilder);
@@ -170,7 +171,17 @@ public class StarRocksTableOperations extends 
JdbcTableOperations {
       } else if (change instanceof TableChange.UpdateComment) {
         TableChange.UpdateComment updateComment = (TableChange.UpdateComment) 
change;
         String newComment = updateComment.getNewComment();
-        alterSql.add("MODIFY COMMENT \"" + newComment + "\"");
+        if (StringIdentifier.fromComment(newComment) == null) {
+          lazyLoadTable = getOrCreateTable(databaseName, tableName, 
lazyLoadTable);
+          StringIdentifier identifier = 
StringIdentifier.fromComment(lazyLoadTable.comment());
+          if (identifier != null) {
+            newComment = StringIdentifier.addToComment(identifier, newComment);
+          }
+        }
+        if (StringUtils.isNotEmpty(newComment)) {
+          newComment = 
StringIdentifier.addToComment(StringIdentifier.DUMMY_ID, newComment);
+        }
+        alterSql.add("COMMENT = \"" + escapeSqlLiteral(newComment, '"') + 
"\"");
       } else if (change instanceof TableChange.SetProperty) {
         if (hasSetPropertyChange) {
           throw new IllegalArgumentException(
@@ -333,7 +344,7 @@ public class StarRocksTableOperations extends 
JdbcTableOperations {
 
     // Add column comment if specified
     if (StringUtils.isNotEmpty(column.comment())) {
-      sqlBuilder.append("COMMENT '").append(column.comment()).append("' ");
+      sqlBuilder.append("COMMENT '").append(escapeSqlLiteral(column.comment(), 
'\'')).append("' ");
     }
     return sqlBuilder;
   }
@@ -481,7 +492,10 @@ public class StarRocksTableOperations extends 
JdbcTableOperations {
 
     // Append comment if available
     if (StringUtils.isNotEmpty(addColumn.getComment())) {
-      columnDefinition.append("COMMENT 
'").append(addColumn.getComment()).append("' ");
+      columnDefinition
+          .append("COMMENT '")
+          .append(escapeSqlLiteral(addColumn.getComment(), '\''))
+          .append("' ");
     }
 
     // Append position if available
diff --git 
a/catalogs/catalog-jdbc-starrocks/src/main/java/org/apache/gravitino/catalog/starrocks/utils/StarRocksUtils.java
 
b/catalogs/catalog-jdbc-starrocks/src/main/java/org/apache/gravitino/catalog/starrocks/utils/StarRocksUtils.java
index f97e2e316f..a918b26dc0 100644
--- 
a/catalogs/catalog-jdbc-starrocks/src/main/java/org/apache/gravitino/catalog/starrocks/utils/StarRocksUtils.java
+++ 
b/catalogs/catalog-jdbc-starrocks/src/main/java/org/apache/gravitino/catalog/starrocks/utils/StarRocksUtils.java
@@ -30,6 +30,7 @@ import java.util.Optional;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
+import org.apache.gravitino.catalog.jdbc.utils.JdbcConnectorUtils;
 import org.apache.gravitino.rel.expressions.NamedReference;
 import org.apache.gravitino.rel.expressions.distributions.Distribution;
 import org.apache.gravitino.rel.expressions.distributions.Distributions;
@@ -67,7 +68,9 @@ public class StarRocksUtils {
           "(?:^|\\s|\\))DISTRIBUTED\\s+BY\\s+(?:RANDOM\\b|\\w+\\s*\\()", 
Pattern.CASE_INSENSITIVE);
 
   private static final Pattern TABLE_COMMENT_PATTERN =
-      Pattern.compile("COMMENT\\s*\"([^\\(]+?)\\s*\\(From Gravitino,.*\\)\"");
+      Pattern.compile(
+          "COMMENT\\s*\"((?:\\\\.|\"\"|[^\"\\\\])*)\\s+"
+              + "\\(From Gravitino, DO NOT EDIT: 
gravitino\\.v\\d+\\.uid-?\\d+\\)\"");
 
   private static final String PARTITION_TYPE_VALUE_PATTERN_STRING =
       "types: \\[([^\\]]+)\\]; keys: \\[([^\\]]+)\\];";
@@ -196,7 +199,7 @@ public class StarRocksUtils {
   public static String extractTableCommentFromSql(String createTableSql) {
     Matcher matcher = TABLE_COMMENT_PATTERN.matcher(createTableSql.trim());
     if (matcher.find()) {
-      return matcher.group(1);
+      return JdbcConnectorUtils.unescapeSqlLiteral(matcher.group(1), '"');
     }
     return "";
   }
diff --git 
a/catalogs/catalog-jdbc-starrocks/src/test/java/org/apache/gravitino/catalog/starrocks/operation/TestStarRocksTableOperations.java
 
b/catalogs/catalog-jdbc-starrocks/src/test/java/org/apache/gravitino/catalog/starrocks/operation/TestStarRocksTableOperations.java
index b7726b96fd..5f531e7d55 100644
--- 
a/catalogs/catalog-jdbc-starrocks/src/test/java/org/apache/gravitino/catalog/starrocks/operation/TestStarRocksTableOperations.java
+++ 
b/catalogs/catalog-jdbc-starrocks/src/test/java/org/apache/gravitino/catalog/starrocks/operation/TestStarRocksTableOperations.java
@@ -31,6 +31,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
+import org.apache.gravitino.StringIdentifier;
 import org.apache.gravitino.catalog.jdbc.JdbcColumn;
 import org.apache.gravitino.catalog.jdbc.JdbcTable;
 import org.apache.gravitino.catalog.jdbc.converter.JdbcTypeConverter;
@@ -195,6 +196,55 @@ public class TestStarRocksTableOperations extends 
TestStarRocks {
         TABLE_OPERATIONS.drop(databaseName, newName), "table should be 
non-existent");
   }
 
+  @Test
+  public void testTableCommentWithSqlLiteralCharacters() {
+    String tableName = 
GravitinoITUtils.genRandomName("starrocks_comment_test_table");
+    String createComment = "owner's \"comment\" (created) C:\\tmp; --";
+    String updatedComment = "reviewer's \"comment\" (updated) D:\\data; --";
+    StringIdentifier identifier = StringIdentifier.fromId(42);
+    JdbcColumn column =
+        
JdbcColumn.builder().withName("col_1").withType(INT).withComment("id").build();
+
+    try {
+      TABLE_OPERATIONS.create(
+          databaseName,
+          tableName,
+          new JdbcColumn[] {column},
+          StringIdentifier.addToComment(identifier, createComment),
+          createProperties(),
+          null,
+          Distributions.hash(DEFAULT_BUCKET_SIZE, 
NamedReference.field("col_1")),
+          Indexes.EMPTY_INDEXES);
+
+      Assertions.assertEquals(
+          StringIdentifier.addToComment(identifier, createComment),
+          TABLE_OPERATIONS.load(databaseName, tableName).comment());
+
+      TABLE_OPERATIONS.alterTable(
+          databaseName, tableName, TableChange.updateComment(updatedComment));
+      Awaitility.await()
+          .atMost(MAX_WAIT_IN_SECONDS, TimeUnit.SECONDS)
+          .pollInterval(WAIT_INTERVAL_IN_SECONDS, TimeUnit.SECONDS)
+          .untilAsserted(
+              () ->
+                  Assertions.assertEquals(
+                      StringIdentifier.addToComment(identifier, 
updatedComment),
+                      TABLE_OPERATIONS.load(databaseName, 
tableName).comment()));
+
+      TABLE_OPERATIONS.alterTable(databaseName, tableName, 
TableChange.updateComment(""));
+      Awaitility.await()
+          .atMost(MAX_WAIT_IN_SECONDS, TimeUnit.SECONDS)
+          .pollInterval(WAIT_INTERVAL_IN_SECONDS, TimeUnit.SECONDS)
+          .untilAsserted(
+              () ->
+                  Assertions.assertEquals(
+                      StringIdentifier.addToComment(identifier, ""),
+                      TABLE_OPERATIONS.load(databaseName, 
tableName).comment()));
+    } finally {
+      TABLE_OPERATIONS.drop(databaseName, tableName);
+    }
+  }
+
   @Test
   public void testAlterTable() {
     String tableName = 
GravitinoITUtils.genRandomName("starrocks_alter_test_table");
diff --git 
a/catalogs/catalog-jdbc-starrocks/src/test/java/org/apache/gravitino/catalog/starrocks/operation/TestStarRocksTableOperationsSqlGeneration.java
 
b/catalogs/catalog-jdbc-starrocks/src/test/java/org/apache/gravitino/catalog/starrocks/operation/TestStarRocksTableOperationsSqlGeneration.java
index f8eab07a93..12195a08cb 100644
--- 
a/catalogs/catalog-jdbc-starrocks/src/test/java/org/apache/gravitino/catalog/starrocks/operation/TestStarRocksTableOperationsSqlGeneration.java
+++ 
b/catalogs/catalog-jdbc-starrocks/src/test/java/org/apache/gravitino/catalog/starrocks/operation/TestStarRocksTableOperationsSqlGeneration.java
@@ -19,11 +19,14 @@
 package org.apache.gravitino.catalog.starrocks.operation;
 
 import java.util.Collections;
+import org.apache.gravitino.StringIdentifier;
 import org.apache.gravitino.catalog.jdbc.JdbcColumn;
+import org.apache.gravitino.catalog.jdbc.JdbcTable;
 import 
org.apache.gravitino.catalog.jdbc.converter.JdbcColumnDefaultValueConverter;
 import org.apache.gravitino.catalog.jdbc.converter.JdbcExceptionConverter;
 import org.apache.gravitino.catalog.starrocks.converter.StarRocksTypeConverter;
 import 
org.apache.gravitino.catalog.starrocks.operations.StarRocksTableOperations;
+import org.apache.gravitino.rel.TableChange;
 import org.apache.gravitino.rel.expressions.NamedReference;
 import org.apache.gravitino.rel.expressions.distributions.Distribution;
 import org.apache.gravitino.rel.expressions.distributions.Distributions;
@@ -46,15 +49,34 @@ public class TestStarRocksTableOperationsSqlGeneration {
 
     public String createTableSql(
         String tableName, JdbcColumn[] columns, Distribution distribution) {
+      return createTableSql(tableName, columns, distribution, "comment");
+    }
+
+    public String createTableSql(
+        String tableName, JdbcColumn[] columns, Distribution distribution, 
String comment) {
       return generateCreateTableSql(
           tableName,
           columns,
-          "comment",
+          comment,
           Collections.emptyMap(),
           Transforms.EMPTY_TRANSFORM,
           distribution,
           Indexes.EMPTY_INDEXES);
     }
+
+    public String alterTableSql(String tableName, TableChange... changes) {
+      return generateAlterTableSql("database", tableName, changes);
+    }
+
+    @Override
+    protected JdbcTable getOrCreateTable(
+        String databaseName, String tableName, JdbcTable lazyLoadCreateTable) {
+      return JdbcTable.builder()
+          .withName(tableName)
+          .withComment(
+              StringIdentifier.addToComment(StringIdentifier.fromId(42), 
"existing comment"))
+          .build();
+    }
   }
 
   @Test
@@ -120,4 +142,57 @@ public class TestStarRocksTableOperationsSqlGeneration {
         sql.contains("DEFAULT " + 
converter.fromGravitino(col1.defaultValue())),
         "Should contain DEFAULT '   ' but was: " + sql);
   }
+
+  @Test
+  public void testEscapeCommentsInGeneratedSql() {
+    TestableStarRocksTableOperations ops = new 
TestableStarRocksTableOperations();
+    String tableComment = "owner\"; DROP TABLE marker; --";
+    String columnComment = "owner's comment; DROP TABLE marker; --";
+    JdbcColumn column =
+        JdbcColumn.builder()
+            .withName("col1")
+            .withType(Types.IntegerType.get())
+            .withComment(columnComment)
+            .build();
+    Distribution distribution = Distributions.hash(1, 
NamedReference.field("col1"));
+
+    String createSql =
+        ops.createTableSql("test_table", new JdbcColumn[] {column}, 
distribution, tableComment);
+    Assertions.assertTrue(
+        createSql.contains("COMMENT \"owner\"\"; DROP TABLE marker; -- "), 
createSql);
+    Assertions.assertTrue(
+        createSql.contains("COMMENT 'owner''s comment; DROP TABLE marker; 
--'"), createSql);
+
+    // The existing table's identifier (uid42) is preserved, and a sacrificial 
DUMMY_ID (uid-1)
+    // marker is appended on top: the read path (extractTableCommentFromSql) 
strips exactly one
+    // (outermost) marker, so the real identifier survives the round-trip.
+    String alterSql = ops.alterTableSql("test_table", 
TableChange.updateComment(tableComment));
+    Assertions.assertTrue(
+        alterSql.contains(
+            "COMMENT = \"owner\"\"; DROP TABLE marker; -- "
+                + "(From Gravitino, DO NOT EDIT: gravitino.v1.uid42) "
+                + "(From Gravitino, DO NOT EDIT: gravitino.v1.uid-1)\""),
+        alterSql);
+
+    String clearCommentSql = ops.alterTableSql("test_table", 
TableChange.updateComment(""));
+    Assertions.assertTrue(
+        clearCommentSql.contains(
+            "COMMENT = \"(From Gravitino, DO NOT EDIT: gravitino.v1.uid42) "
+                + "(From Gravitino, DO NOT EDIT: gravitino.v1.uid-1)\""),
+        clearCommentSql);
+  }
+
+  @Test
+  public void testEscapeAddColumnCommentInGeneratedSql() {
+    TestableStarRocksTableOperations ops = new 
TestableStarRocksTableOperations();
+    String comment = "owner\\'s \"comment\"; --";
+
+    String alterSql =
+        ops.alterTableSql(
+            "test_table",
+            TableChange.addColumn(new String[] {"col2"}, 
Types.IntegerType.get(), comment));
+
+    Assertions.assertTrue(alterSql.contains("ADD COLUMN `col2`"), alterSql);
+    Assertions.assertTrue(alterSql.contains("COMMENT 'owner\\\\''s 
\"comment\"; --'"), alterSql);
+  }
 }
diff --git 
a/catalogs/catalog-jdbc-starrocks/src/test/java/org/apache/gravitino/catalog/starrocks/utils/TestStarRocksUtils.java
 
b/catalogs/catalog-jdbc-starrocks/src/test/java/org/apache/gravitino/catalog/starrocks/utils/TestStarRocksUtils.java
index 9f77b51748..3eb449327e 100644
--- 
a/catalogs/catalog-jdbc-starrocks/src/test/java/org/apache/gravitino/catalog/starrocks/utils/TestStarRocksUtils.java
+++ 
b/catalogs/catalog-jdbc-starrocks/src/test/java/org/apache/gravitino/catalog/starrocks/utils/TestStarRocksUtils.java
@@ -97,6 +97,31 @@ public class TestStarRocksUtils {
     assertEquals("comment", result.get("comment"));
   }
 
+  @Test
+  public void testExtractEscapedTableCommentFromSql() {
+    String createTableSql =
+        "CREATE TABLE `testTable` (\n"
+            + "`col1` INT COMMENT \"column comment\"\n"
+            + ")\n"
+            + "COMMENT \"owner's \\\"comment\\\" (nested) C:\\\\tmp; -- "
+            + "(From Gravitino, DO NOT EDIT: gravitino.v1.uid-1)\"";
+
+    assertEquals(
+        "owner's \"comment\" (nested) C:\\tmp; --",
+        StarRocksUtils.extractTableCommentFromSql(createTableSql));
+
+    createTableSql =
+        "CREATE TABLE `testTable` (\n"
+            + "`col1` INT\n"
+            + ")\n"
+            + "COMMENT \"owner's \"\"comment\"\" D:\\\\data; -- "
+            + "(From Gravitino, DO NOT EDIT: gravitino.v1.uid-1)\"";
+
+    assertEquals(
+        "owner's \"comment\" D:\\data; --",
+        StarRocksUtils.extractTableCommentFromSql(createTableSql));
+  }
+
   @Test
   public void testExtractPartitionInfoFromSql() {
     // test range partition

Reply via email to