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]