diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java index a2ca9986f4296e..4f715e14d4f094 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java @@ -5537,13 +5537,8 @@ public List visitVariantSubColTypeList(VariantSubColTypeListContex @Override public VariantField visitVariantSubColType(VariantSubColTypeContext ctx) { - String comment; - if (ctx.commentSpec() != null) { - comment = ctx.commentSpec().STRING_LITERAL().getText(); - comment = LogicalPlanBuilderAssistant.escapeBackSlash(comment.substring(1, comment.length() - 1)); - } else { - comment = ""; - } + String comment = ctx.commentSpec() == null ? "" + : SqlLiteralUtils.parseStringLiteral(ctx.commentSpec().STRING_LITERAL().getText()); String pattern = ctx.STRING_LITERAL().getText(); pattern = pattern.substring(1, pattern.length() - 1); if (ctx.variantSubColMatchType() != null) { diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/types/VariantField.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/types/VariantField.java index 7e5b87878c4004..8253f620a8e281 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/types/VariantField.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/types/VariantField.java @@ -19,6 +19,7 @@ import org.apache.doris.catalog.PatternType; import org.apache.doris.common.GlobRegexUtil; +import org.apache.doris.nereids.util.SqlLiteralUtils; import org.apache.doris.nereids.util.Utils; import com.google.re2j.Pattern; @@ -118,7 +119,7 @@ public String toSql() { sb.append("'").append(pattern).append("'"); sb.append(":").append(dataType.toSql()); if (!comment.isEmpty()) { - sb.append(" COMMENT '").append(comment).append("'"); + sb.append(" COMMENT ").append(SqlLiteralUtils.quoteStringLiteral(comment)); } return sb.toString(); } diff --git a/fe/fe-core/src/test/java/org/apache/doris/catalog/TypeTest.java b/fe/fe-core/src/test/java/org/apache/doris/catalog/TypeTest.java index 2a8f3f299eb0fc..d7a985c5b320a7 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/catalog/TypeTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/catalog/TypeTest.java @@ -17,6 +17,8 @@ package org.apache.doris.catalog; +import org.apache.doris.nereids.parser.NereidsParser; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -162,6 +164,21 @@ public void testVariantToSqlSerializesNestedGroupProperty() { Assertions.assertTrue(variantType.toSql().contains("\"variant_enable_nested_group\" = \"true\"")); } + @Test + public void testVariantFieldCommentToSqlRoundTrip() { + String comment = "O'Reilly \"quoted\" a\\b"; + ArrayList fields = new ArrayList<>(); + fields.add(new VariantField("price", Type.INT, comment)); + String sql = new VariantType(fields, 0, false, 10000, 1, false, 0L, 64, false).toSql(); + + org.apache.doris.nereids.types.VariantType parsed = (org.apache.doris.nereids.types.VariantType) + new NereidsParser().parseDataType(sql); + Assertions.assertEquals(comment, parsed.getPredefinedFields().get(0).getComment()); + org.apache.doris.nereids.types.VariantType reparsed = (org.apache.doris.nereids.types.VariantType) + new NereidsParser().parseDataType("variant<" + parsed.getPredefinedFields().get(0).toSql() + ">"); + Assertions.assertEquals(comment, reparsed.getPredefinedFields().get(0).getComment()); + } + @Test public void testVariantToThriftUsesV2() { Assertions.assertTrue(new VariantType().toThrift().types.get(0).scalar_type.variant_is_v2); diff --git a/fe/fe-type/src/main/java/org/apache/doris/catalog/VariantField.java b/fe/fe-type/src/main/java/org/apache/doris/catalog/VariantField.java index 5ccff65291b37e..dafa1736464e73 100644 --- a/fe/fe-type/src/main/java/org/apache/doris/catalog/VariantField.java +++ b/fe/fe-type/src/main/java/org/apache/doris/catalog/VariantField.java @@ -17,6 +17,7 @@ package org.apache.doris.catalog; +import org.apache.doris.common.util.SqlUtils; import org.apache.doris.thrift.TTypeDesc; import org.apache.doris.thrift.TTypeNode; @@ -74,7 +75,8 @@ public String toSql(int depth) { sb.append("'").append(pattern).append("'"); sb.append(":").append(type.toSql(depth + 1)); if (!comment.isEmpty()) { - sb.append(" COMMENT '").append(comment).append("'"); + // Quote the comment so SHOW CREATE TABLE output can be replayed when it holds quotes or backslashes. + sb.append(" COMMENT ").append(SqlUtils.quoteStringLiteral(comment, false)); } return sb.toString(); } diff --git a/regression-test/data/variant_p0/predefine/test_variant_predefine_comment_escape.out b/regression-test/data/variant_p0/predefine/test_variant_predefine_comment_escape.out new file mode 100644 index 00000000000000..8c0faa26a5ce5a --- /dev/null +++ b/regression-test/data/variant_p0/predefine/test_variant_predefine_comment_escape.out @@ -0,0 +1,10 @@ +-- This file is automatically generated. You should know what you did if you want to edit this +-- !show_create -- +test_variant_predefine_comment_escape CREATE TABLE `test_variant_predefine_comment_escape` (\n `id` int NULL,\n `v` variant<'price':int COMMENT "O'Reilly",'name':text COMMENT "say 'hi' and ""bye""",'path':text COMMENT "C:\\\\tmp",PROPERTIES ("variant_max_subcolumns_count" = "10","variant_enable_typed_paths_to_sparse" = "false","variant_max_sparse_column_statistics_size" = "10000","variant_sparse_hash_shard_count" = "1")> NULL\n) ENGINE=OLAP\nDUPLICATE KEY(`id`)\nDISTRIBUTED BY HASH(`id`) BUCKETS 1\nPROPERTIES (\n"replication_allocation" = "tag.location.default: 1",\n"min_load_replica_num" = "-1",\n"is_being_synced" = "false",\n"storage_medium" = "hdd",\n"storage_format" = "V2",\n"inverted_index_storage_format" = "V3",\n"light_schema_change" = "true",\n"disable_auto_compaction" = "false",\n"group_commit_interval_ms" = "10000",\n"group_commit_data_bytes" = "134217728"\n); + +-- !show_create_copy -- +test_variant_predefine_comment_escape_copy CREATE TABLE `test_variant_predefine_comment_escape_copy` (\n `id` int NULL,\n `v` variant<'price':int COMMENT "O'Reilly",'name':text COMMENT "say 'hi' and ""bye""",'path':text COMMENT "C:\\\\tmp",PROPERTIES ("variant_max_subcolumns_count" = "10","variant_enable_typed_paths_to_sparse" = "false","variant_max_sparse_column_statistics_size" = "10000","variant_sparse_hash_shard_count" = "1")> NULL\n) ENGINE=OLAP\nDUPLICATE KEY(`id`)\nDISTRIBUTED BY HASH(`id`) BUCKETS 1\nPROPERTIES (\n"replication_allocation" = "tag.location.default: 1",\n"min_load_replica_num" = "-1",\n"is_being_synced" = "false",\n"storage_medium" = "hdd",\n"storage_format" = "V2",\n"inverted_index_storage_format" = "V3",\n"light_schema_change" = "true",\n"disable_auto_compaction" = "false",\n"group_commit_interval_ms" = "10000",\n"group_commit_data_bytes" = "134217728"\n); + +-- !select_copy -- +1 10 n p + diff --git a/regression-test/suites/variant_p0/predefine/test_variant_predefine_comment_escape.groovy b/regression-test/suites/variant_p0/predefine/test_variant_predefine_comment_escape.groovy new file mode 100644 index 00000000000000..5de0a40205003a --- /dev/null +++ b/regression-test/suites/variant_p0/predefine/test_variant_predefine_comment_escape.groovy @@ -0,0 +1,44 @@ +// 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. + +// SHOW CREATE TABLE must print predefined field comments as SQL literals that can be replayed. +suite("test_variant_predefine_comment_escape") { + sql "DROP TABLE IF EXISTS test_variant_predefine_comment_escape" + sql "DROP TABLE IF EXISTS test_variant_predefine_comment_escape_copy" + sql """ + CREATE TABLE test_variant_predefine_comment_escape ( + id INT, + v VARIANT< + 'price':INT COMMENT "O'Reilly", + 'name':STRING COMMENT 'say ''hi'' and "bye"', + 'path':STRING COMMENT 'C:\\\\tmp', + PROPERTIES ("variant_max_subcolumns_count" = "10") + > + ) DUPLICATE KEY(id) DISTRIBUTED BY HASH(id) BUCKETS 1 + PROPERTIES ("replication_num" = "1") + """ + qt_show_create "SHOW CREATE TABLE test_variant_predefine_comment_escape" + + // Replay the printed DDL under a new name; the copy must print the same comments again. + def ddl = sql("SHOW CREATE TABLE test_variant_predefine_comment_escape")[0][1] + sql ddl.replace("`test_variant_predefine_comment_escape`", "`test_variant_predefine_comment_escape_copy`") + qt_show_create_copy "SHOW CREATE TABLE test_variant_predefine_comment_escape_copy" + + sql """INSERT INTO test_variant_predefine_comment_escape_copy + SELECT 1, parse_to_variant('{"price": 10, "name": "n", "path": "p"}')""" + order_qt_select_copy "SELECT id, v['price'], v['name'], v['path'] FROM test_variant_predefine_comment_escape_copy" +}