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

Gabriel39 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 06af439e3c8 [fix](parser) Reject unquoted suffixes attached to quoted 
table names (#69541)
06af439e3c8 is described below

commit 06af439e3c8bb51a22c7cec17a825cf5c2086db4
Author: Gabriel <[email protected]>
AuthorDate: Sun Oct 25 22:34:41 2026 +0800

    [fix](parser) Reject unquoted suffixes attached to quoted table names 
(#69541)
    
    ### What problem does this PR solve?
    
    `` SELECT * FROM `t`$snapshots `` currently parses `$snapshots` as an
    implicit table alias and silently returns rows from `t`. The same issue
    affects qualified names such as `` `catalog`.`db`.`t`$snapshots ``.
    
    Reject an unquoted dollar-prefixed suffix immediately adjacent to a
    quoted table name during parsing. The error explains how to quote the
    complete table name or separate an intended alias. Supported forms such
    as `t$snapshots`, `` `t$snapshots` ``, and `` `t` AS $snapshots ``
    remain valid, as do aliases separated by whitespace or comments.
    
    Add parser coverage for qualified names, escaped backticks, non-ASCII
    names, explicit and separated aliases, and parsing without building a
    tree. Add a p0 regression suite with six syntax-error checks that
    require no external catalog.
    
    ### Release note
    
    Queries that attach an unquoted dollar suffix directly to a quoted table
    name now fail explicitly instead of silently scanning the base table.
    Quote the entire system table name, for example `` `t$snapshots` ``.
    
    ### Check List (For Author)
    
    - Test
    - [x] Unit Test: reproduced the failure before the fix; all 255
    `fe-sql-parser` tests pass after the fix.
    - [x] Manual test: all six SQL statements in the new regression suite
    produce the expected diagnostic with the standalone parser CLI; Groovy
    syntax validation passes.
    - [x] Regression test: `test_quoted_table_suffix` passed on a locally
    built FE/BE cluster; all six syntax-error checks passed.
        - FE Checkstyle: zero violations in the parent and parser modules.
    - Behavior changed:
    - [x] Yes. Ambiguous adjacent suffixes produce a parse error; explicit
    and separated dollar-prefixed aliases retain their existing behavior.
    - Does this need documentation?
        - [x] No.
    
    ### Check List (For Reviewer who merge this PR)
    
    - [ ] Confirm the release note
    - [ ] Confirm test cases
    - [ ] Confirm document
    - [ ] Add branch pick label
---
 .../antlr4/org/apache/doris/nereids/DorisParser.g4 | 17 +++-
 .../doris/sqlparser/QuotedTableSuffixTest.java     | 90 ++++++++++++++++++++++
 .../nereids_p0/test_quoted_table_suffix.groovy     | 33 ++++++++
 3 files changed, 139 insertions(+), 1 deletion(-)

diff --git 
a/fe/fe-sql-parser/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 
b/fe/fe-sql-parser/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
index e73ef5d7b20..33d0115ebfc 100644
--- a/fe/fe-sql-parser/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
+++ b/fe/fe-sql-parser/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
@@ -49,6 +49,20 @@ options { tokenVocab = DorisLexer; }
                 ctx.getParent().getText(), ctx);
     }
 
+    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);
+        }
+    }
+
     private boolean isQueryOrganizationStart() {
         int tokenType = _input.LA(1);
         return tokenType == ORDER || tokenType == LIMIT;
@@ -1721,7 +1735,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-sql-parser/src/test/java/org/apache/doris/sqlparser/QuotedTableSuffixTest.java
 
b/fe/fe-sql-parser/src/test/java/org/apache/doris/sqlparser/QuotedTableSuffixTest.java
new file mode 100644
index 00000000000..15acd3cfaed
--- /dev/null
+++ 
b/fe/fe-sql-parser/src/test/java/org/apache/doris/sqlparser/QuotedTableSuffixTest.java
@@ -0,0 +1,90 @@
+// 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.sqlparser;
+
+import org.apache.doris.nereids.DorisParser;
+import org.apache.doris.nereids.DorisParserBaseVisitor;
+import org.apache.doris.nereids.exceptions.ParseException;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class QuotedTableSuffixTest {
+    private final DorisSqlParser parser = new DorisSqlParser();
+
+    @Test
+    void rejectsUnquotedSuffixAttachedToQuotedTable() {
+        for (String table : List.of("`catalog`.`db`.`t`", "catalog.db.`t`", 
"`t`", "`t``name`", "`่กจ`")) {
+            for (String suffix : List.of("$snapshots", "$history", 
"$partitions", "$custom")) {
+                String sql = "SELECT * FROM " + table + suffix;
+                Assertions.assertThrows(ParseException.class, () -> 
parser.parseStatement(sql), sql);
+            }
+        }
+    }
+
+    @Test
+    void rejectsAttachedSuffixWithoutBuildingParseTree() {
+        DorisParser generated = parser.newParser(parser.newLexer("SELECT * 
FROM `t`$snapshots"));
+        generated.setBuildParseTree(false);
+        Assertions.assertThrows(ParseException.class, 
generated::singleStatement);
+    }
+
+    @Test
+    void preservesCompleteSystemTableIdentifiers() {
+        for (String table : List.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 : List.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 : List.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(parser.parseStatement(sql));
+        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