This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.2 by this push:
new 5d4be8e872d branch-4.2: [fix](parser) Reject unquoted suffixes
attached to quoted table names #69541 (#70061)
5d4be8e872d is described below
commit 5d4be8e872db83cfeef24810c18dacdbedb9fa59
Author: morrySnow <[email protected]>
AuthorDate: Wed Nov 4 08:18:09 2026 +0800
branch-4.2: [fix](parser) Reject unquoted suffixes attached to quoted table
names #69541 (#70061)
picked from master #69541
Co-authored-by: Gabriel <[email protected]>
---
.../antlr4/org/apache/doris/nereids/DorisParser.g4 | 18 ++++-
.../nereids/parser/QuotedTableSuffixTest.java | 94 ++++++++++++++++++++++
.../nereids_p0/test_quoted_table_suffix.groovy | 33 ++++++++
3 files changed, 144 insertions(+), 1 deletion(-)
diff --git a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
index 79cbee6c2ff..656f37e5223 100644
--- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
+++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
@@ -23,6 +23,21 @@ options { tokenVocab = DorisLexer; }
@members {
public boolean ansiSQLSyntax = false;
+
+ private void validateQuotedTableSuffix(Token tableEnd, TableAliasContext
alias) {
+ Token aliasStart = alias.getStart();
+ // A suffix attached to a quoted table name otherwise becomes a
base-table scan alias.
+ // Keep separated or explicit dollar-prefixed aliases valid.
+ if (tableEnd.getType() == BACKQUOTED_IDENTIFIER
+ && aliasStart.getType() == IDENTIFIER
+ && aliasStart.getText().startsWith("$")
+ && tableEnd.getStopIndex() + 1 == aliasStart.getStartIndex()) {
+ throw new org.apache.doris.nereids.exceptions.ParseException(
+ "A '$' suffix cannot be attached outside a quoted table
name. "
+ + "Quote the full table name or separate an alias with AS
or whitespace.", alias);
+ }
+ }
+
}
multiStatements
@@ -1496,7 +1511,8 @@ optScanParams
relationPrimary
: multipartIdentifier optScanParams? materializedViewName? tableSnapshot?
specifiedPartition?
- tabletList? tableAlias sample? relationHint? lateralView*
#tableName
+ tabletList? tableAlias
{validateQuotedTableSuffix($multipartIdentifier.stop, $tableAlias.ctx);}
+ sample? relationHint? lateralView*
#tableName
| LEFT_PAREN query RIGHT_PAREN tableAlias lateralView*
#aliasedQuery
| tvfName=identifier LEFT_PAREN
(properties=propertyItemList)?
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/QuotedTableSuffixTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/QuotedTableSuffixTest.java
new file mode 100644
index 00000000000..65b4abc64db
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/QuotedTableSuffixTest.java
@@ -0,0 +1,94 @@
+// 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.doris.nereids.parser;
+
+import org.apache.doris.nereids.DorisLexer;
+import org.apache.doris.nereids.DorisParser;
+import org.apache.doris.nereids.DorisParserBaseVisitor;
+import org.apache.doris.nereids.exceptions.ParseException;
+
+import com.google.common.collect.ImmutableList;
+import org.antlr.v4.runtime.CharStreams;
+import org.antlr.v4.runtime.CommonTokenStream;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class QuotedTableSuffixTest {
+
+ @Test
+ void rejectsUnquotedSuffixAttachedToQuotedTable() {
+ for (String table : ImmutableList.of("`catalog`.`db`.`t`",
"catalog.db.`t`", "`t`", "`t``name`", "`่กจ`")) {
+ for (String suffix : ImmutableList.of("$snapshots", "$history",
"$partitions", "$custom")) {
+ String sql = "SELECT * FROM " + table + suffix;
+ Assertions.assertThrows(ParseException.class, () ->
NereidsParser.toAst(sql, DorisParser::singleStatement), sql);
+ }
+ }
+ }
+
+ @Test
+ void rejectsAttachedSuffixWithoutBuildingParseTree() {
+ DorisParser generated = new DorisParser(new CommonTokenStream(
+ new DorisLexer(new
CaseInsensitiveStream(CharStreams.fromString("SELECT * FROM `t`$snapshots")))));
+ generated.setBuildParseTree(false);
+ Assertions.assertThrows(ParseException.class,
generated::singleStatement);
+ }
+
+ @Test
+ void preservesCompleteSystemTableIdentifiers() {
+ for (String table : ImmutableList.of("catalog.db.t$snapshots",
"`catalog`.`db`.`t$snapshots`")) {
+ DorisParser.TableNameContext relation = parseTable("SELECT * FROM
" + table);
+ Assertions.assertEquals("catalog.db.t$snapshots",
relation.multipartIdentifier().getText());
+ Assertions.assertNull(relation.tableAlias().strictIdentifier());
+ }
+ }
+
+ @Test
+ void preservesExplicitAndSeparatedDollarAliases() {
+ for (String separator : ImmutableList.of(" ", "\t", "\n", "/**/", "--
alias\n", " AS ", "AS ")) {
+ String sql = "SELECT $snapshots.id FROM `t`" + separator +
"$snapshots";
+ DorisParser.TableNameContext relation = parseTable(sql);
+ Assertions.assertEquals("t",
relation.multipartIdentifier().getText());
+ Assertions.assertEquals("$snapshots",
relation.tableAlias().strictIdentifier().getText());
+ }
+ }
+
+ @Test
+ void preservesQuotedAndOrdinaryAliases() {
+ for (String alias : ImmutableList.of(" `$snapshots`", "ordinary", "
`ordinary`")) {
+ DorisParser.TableNameContext relation = parseTable("SELECT * FROM
`t`" + alias);
+ Assertions.assertEquals("t",
relation.multipartIdentifier().getText());
+ Assertions.assertEquals(alias.trim().replace("`", ""),
relation.tableAlias().strictIdentifier().getText());
+ }
+ }
+
+ private DorisParser.TableNameContext parseTable(String sql) {
+ List<DorisParser.TableNameContext> relations = new ArrayList<>();
+ new DorisParserBaseVisitor<Void>() {
+ @Override
+ public Void visitTableName(DorisParser.TableNameContext ctx) {
+ relations.add(ctx);
+ return super.visitTableName(ctx);
+ }
+ }.visit(NereidsParser.toAst(sql, DorisParser::singleStatement));
+ Assertions.assertEquals(1, relations.size());
+ return relations.get(0);
+ }
+}
diff --git a/regression-test/suites/nereids_p0/test_quoted_table_suffix.groovy
b/regression-test/suites/nereids_p0/test_quoted_table_suffix.groovy
new file mode 100644
index 00000000000..ebbf3fa8f6d
--- /dev/null
+++ b/regression-test/suites/nereids_p0/test_quoted_table_suffix.groovy
@@ -0,0 +1,33 @@
+// 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.
+
+suite("test_quoted_table_suffix") {
+ // Reject the ambiguous suffix before catalog lookup so it cannot silently
scan the base table.
+ [
+ 'SELECT * FROM `catalog`.`db`.`t`$snapshots',
+ 'SELECT * FROM catalog.db.`t`$snapshots',
+ 'SELECT * FROM `t`$history',
+ 'SELECT * FROM `t``name`$partitions',
+ 'SELECT * FROM (SELECT * FROM `t`$snapshots) nested',
+ 'SELECT * FROM base JOIN `t`$snapshots ON base.id = $snapshots.id'
+ ].each { query ->
+ test {
+ sql query
+ exception "A '\$' suffix cannot be attached outside a quoted table
name"
+ }
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]