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]

Reply via email to