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

morrySnow 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 cdb05784a44 [improvement](parser) Localize identifier post-processing 
(#67457)
cdb05784a44 is described below

commit cdb05784a4410fb73ef447b93e6760ae7bef4803
Author: morrySnow <[email protected]>
AuthorDate: Thu Sep 3 15:56:40 2026 +0800

    [improvement](parser) Localize identifier post-processing (#67457)
    
    ### What problem does this PR solve?
    
    Problem Summary:
    
    `PostProcessor` was installed as a global ANTLR parse listener, so every
    rule exit paid listener-dispatch cost even though only three local
    actions were needed: normalizing non-reserved identifiers, normalizing
    quoted identifiers, and reporting malformed unquoted identifiers.
    
    This PR moves those actions into the corresponding grammar rules and
    removes the listener from both `DorisSqlParser` and `NereidsParser`. It
    preserves identifier token text/type/positions, double-backtick
    unescaping, malformed-identifier errors, and parsing with
    `buildParseTree=false`.
    
    ### Benchmark
    
    Environment:
    
    - Baseline: `760b14ee62c` (benchmark-only commit on `f054492cbb9`)
    - Candidate: `5b45223bc61`
    - JDK 17.0.20.1, JMH 1.37, macOS arm64
    - 1 thread, 3 forks, `-Xms1g -Xmx1g`, 4 x 300 ms warmup, 7 x 400 ms
    measurement, `-prof gc`
    - Baseline is the average of two complete runs (B1/B2). Candidate error
    is JMH's 99.9% confidence interval.
    - A preliminary candidate run affected by host interference was
    discarded (`typical` parser-only was 88.8 us/op instead of the stable
    7-9 us/op range); the final artifact was rebuilt and measured in a clean
    run.
    
    Build and run:
    
    ```shell
    LC_ALL=en_US.UTF-8 mvn -Pbenchmark -pl 
fe-sql-parser,fe-sql-parser-benchmark -am \
        -Dmaven.build.cache.enabled=false package
    java -jar 
fe/fe-sql-parser-benchmark/target/fe-sql-parser-benchmark-*-benchmark.jar \
        'IdentifierPostProcessorBenchmark.*' -prof gc -rf json -rff result.json
    ```
    
    Time is `us/op`; positive change means faster. The workloads cover a
    control query, a typical aggregate query, a 64-column query, a
    non-reserved-keyword-heavy query, and a quoted-identifier-heavy query.
    
    | Path / Workload           | Baseline (B1 / B2)     | Candidate       | 
Latency Improvement | Allocation (B/op): Baseline → Candidate |
    
|:--------------------------|-----------------------:|----------------:|--------------------:|----------------------------------------:|
    | end-to-end / control      | 2.817 / 2.694          | 2.191 ± 0.041   | 
**25.8% faster**    | 4,933.4 → 4,848.0 (-1.73%)              |
    | end-to-end / typical      | 17.143 / 18.288        | 17.173 ± 1.742  | 
**3.2% faster**     | 18,754.0 → 18,658.9 (-0.51%)            |
    | end-to-end / wide         | 66.555 / 72.165        | 60.816 ± 0.731  | 
**14.0% faster**    | 141,501.2 → 141,432.9 (-0.05%)          |
    | end-to-end / nonReserved  | 12.278 / 14.034        | 11.044 ± 0.276  | 
**19.1% faster**    | 22,844.4 → 22,530.9 (-1.37%)            |
    | end-to-end / quoted       | 13.154 / 11.128        | 8.089 ± 1.121   | 
**50.1% faster**    | 16,081.5 → 15,984.1 (-0.61%)            |
    | parser-only / control     | 1.703 / 1.841          | 1.550 ± 0.016   | 
**14.3% faster**    | 3,824.0 → 3,744.0 (-2.09%)              |
    | parser-only / typical     | 8.655 / 8.565          | 7.868 ± 0.096   | 
**9.4% faster**     | 13,872.1 → 13,792.1 (-0.58%)            |
    | parser-only / wide        | 60.700 / 59.489        | 55.784 ± 0.624  | 
**7.7% faster**     | 115,632.9 → 115,552.8 (-0.07%)          |
    | parser-only / nonReserved | 10.127 / 10.148        | 9.863 ± 0.756   | 
**2.8% faster**     | 19,261.5 → 19,189.5 (-0.37%)            |
    | parser-only / quoted      | 6.985 / 7.116          | 6.658 ± 0.186   | 
**5.9% faster**     | 13,440.1 → 13,346.8 (-0.69%)            |
    
    The direct parser path improves by 2.8%-14.3% and allocates less in
    every workload. End-to-end means also improve in every workload; the
    larger quoted/control figures have more host-level variance and are not
    used as the primary conclusion. The gain comes from removing global
    rule-exit listener dispatch; the remaining allocation reduction comes
    from avoiding listener bookkeeping.
    
    ### Semantic verification
    
    - Parsed all 4,610 tracked `.sql` files in default and ANSI modes.
    Acceptance/error class/error position/statement count matched the frozen
    baseline in all 9,220 cases.
    - Tracked SQL path-list SHA-256:
    `c1fdf48a22d311f7164516b512e850bef373126be1d3079148c109e5f80f80c1`.
    - Detailed CST/token/error snapshots cover ordinary, non-reserved,
    quoted, doubled-backtick and multipart identifiers, expressions, DDL,
    both parser entry paths, and `test-table` / `test-tbl` errors. Every
    rule node and token field matched the baseline.
    - Verified the advanced parser path with `buildParseTree=false`.
---
 .../apache/doris/nereids/parser/NereidsParser.java |  2 -
 .../generator/PatternDescribableProcessor.java     |  4 -
 .../doris/nereids/parser/NereidsParserTest.java    |  2 +-
 ....java => IdentifierPostProcessorBenchmark.java} | 60 +++++++--------
 .../benchmark/PrimaryExpressionBenchmark.java      |  3 -
 .../benchmark/QueryOrDmlCommonPrefixBenchmark.java |  3 -
 fe/fe-sql-parser/README.md                         |  6 +-
 .../antlr4/org/apache/doris/nereids/DorisParser.g4 | 32 ++++++++
 .../apache/doris/nereids/parser/PostProcessor.java | 71 ------------------
 .../org/apache/doris/sqlparser/DorisSqlParser.java |  3 -
 .../apache/doris/sqlparser/DorisSqlParserTest.java | 87 ++++++++++++++++++++++
 11 files changed, 150 insertions(+), 123 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/NereidsParser.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/NereidsParser.java
index 2c3775dc626..5e532350df4 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/NereidsParser.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/NereidsParser.java
@@ -73,7 +73,6 @@ import javax.annotation.Nullable;
 public class NereidsParser {
     public static final Logger LOG = LogManager.getLogger(NereidsParser.class);
     private static final ParseErrorListener PARSE_ERROR_LISTENER = new 
ParseErrorListener();
-    private static final PostProcessor POST_PROCESSOR = new PostProcessor();
 
     private static final BitSet EXPLAIN_TOKENS = new BitSet();
 
@@ -410,7 +409,6 @@ public class NereidsParser {
             CommonTokenStream tokenStream, Function<DorisParser, 
ParserRuleContext> parseFunction) {
         DorisParser parser = new DorisParser(tokenStream);
         parser.ansiSQLSyntax = 
GlobalVariable.enable_ansi_query_organization_behavior;
-        parser.addParseListener(POST_PROCESSOR);
         parser.removeErrorListeners();
         parser.addErrorListener(PARSE_ERROR_LISTENER);
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/pattern/generator/PatternDescribableProcessor.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/pattern/generator/PatternDescribableProcessor.java
index e12ccd44189..1e927040350 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/pattern/generator/PatternDescribableProcessor.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/pattern/generator/PatternDescribableProcessor.java
@@ -152,10 +152,6 @@ public class PatternDescribableProcessor extends 
AbstractProcessor {
             }
         });
 
-        // parser.addParseListener(PostProcessor)
-        // parser.removeErrorListeners()
-        // parser.addErrorListener(ParseErrorListener)
-
         ParserRuleContext tree;
         try {
             // first, try parsing with potentially faster SLL mode
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/NereidsParserTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/NereidsParserTest.java
index c6e783e8b7c..e92dc04f10b 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/NereidsParserTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/NereidsParserTest.java
@@ -300,7 +300,7 @@ public class NereidsParserTest extends ParserTestBase {
     }
 
     @Test
-    public void testPostProcessor() {
+    public void testQuotedIdentifierNormalization() {
         parsePlan("select `AD``D` from t1 where a = 1")
                 .matches(
                         logicalProject().when(p -> 
"AD`D".equals(p.getProjects().get(0).getName()))
diff --git 
a/fe/fe-sql-parser-benchmark/src/main/java/org/apache/doris/sqlparser/benchmark/PrimaryExpressionBenchmark.java
 
b/fe/fe-sql-parser-benchmark/src/main/java/org/apache/doris/sqlparser/benchmark/IdentifierPostProcessorBenchmark.java
similarity index 71%
copy from 
fe/fe-sql-parser-benchmark/src/main/java/org/apache/doris/sqlparser/benchmark/PrimaryExpressionBenchmark.java
copy to 
fe/fe-sql-parser-benchmark/src/main/java/org/apache/doris/sqlparser/benchmark/IdentifierPostProcessorBenchmark.java
index fca29e73bc4..27f6f9ad87d 100644
--- 
a/fe/fe-sql-parser-benchmark/src/main/java/org/apache/doris/sqlparser/benchmark/PrimaryExpressionBenchmark.java
+++ 
b/fe/fe-sql-parser-benchmark/src/main/java/org/apache/doris/sqlparser/benchmark/IdentifierPostProcessorBenchmark.java
@@ -19,7 +19,6 @@ package org.apache.doris.sqlparser.benchmark;
 
 import org.apache.doris.nereids.DorisParser;
 import org.apache.doris.nereids.parser.ParseErrorListener;
-import org.apache.doris.nereids.parser.PostProcessor;
 import org.apache.doris.sqlparser.DorisSqlParser;
 
 import org.antlr.v4.runtime.CommonTokenStream;
@@ -44,19 +43,18 @@ import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 
-/** Measures the grammar change both in isolation and through the public 
parser facade. */
+/** Measures identifier post-processing through the public facade and with 
pre-tokenized input. */
 @BenchmarkMode(Mode.AverageTime)
 @OutputTimeUnit(TimeUnit.MICROSECONDS)
 @Fork(value = 3, jvmArgsAppend = {"-Xms1g", "-Xmx1g"})
 @Warmup(iterations = 4, time = 300, timeUnit = TimeUnit.MILLISECONDS)
 @Measurement(iterations = 7, time = 400, timeUnit = TimeUnit.MILLISECONDS)
 @State(Scope.Thread)
-public class PrimaryExpressionBenchmark {
-    @Param({"control", "typical", "specialForms", "postfixChain", 
"wideProjection"})
+public class IdentifierPostProcessorBenchmark {
+    @Param({"control", "typical", "wide", "nonReserved", "quoted"})
     public String workload;
 
     private final DorisSqlParser facade = new DorisSqlParser();
-    private final PostProcessor postProcessor = new PostProcessor();
     private final ParseErrorListener errorListener = new ParseErrorListener();
 
     private String sql;
@@ -64,33 +62,7 @@ public class PrimaryExpressionBenchmark {
 
     @Setup(Level.Trial)
     public void setUp() {
-        switch (workload) {
-            case "control":
-                sql = "SELECT 1";
-                break;
-            case "typical":
-                sql = "SELECT a + b * c FROM t "
-                        + "WHERE (d = 1 OR e[2].f > 3) AND g IS NOT NULL";
-                break;
-            case "specialForms":
-                sql = "SELECT CASE a WHEN 1 THEN CONVERT(b USING utf8) "
-                        + "WHEN 2 THEN CAST(c AS BIGINT) ELSE d + 1 END FROM t 
"
-                        + "WHERE CASE WHEN e > 0 THEN TRUE ELSE FALSE END";
-                break;
-            case "postfixChain":
-                sql = "SELECT fn(a)[1:2][3].field[4].nested[5:6].leaf "
-                        + "COLLATE utf8_general_ci FROM t";
-                break;
-            case "wideProjection":
-                sql = "SELECT " + IntStream.range(0, 64)
-                        .mapToObj(i -> "c" + i + " + " + i)
-                        .collect(Collectors.joining(", "))
-                        + " FROM t WHERE key_col[1].field > 0";
-                break;
-            default:
-                throw new IllegalArgumentException("Unknown workload: " + 
workload);
-        }
-
+        sql = statement(workload);
         CommonTokenStream stream = new CommonTokenStream(facade.newLexer(sql));
         stream.fill();
         tokens = List.copyOf(stream.getTokens());
@@ -105,10 +77,32 @@ public class PrimaryExpressionBenchmark {
     public Object parsePreTokenized() {
         CommonTokenStream stream = new CommonTokenStream(new 
ListTokenSource(tokens));
         DorisParser parser = new DorisParser(stream);
-        parser.addParseListener(postProcessor);
         parser.removeErrorListeners();
         parser.addErrorListener(errorListener);
         parser.getInterpreter().setPredictionMode(PredictionMode.SLL);
         return parser.singleStatement();
     }
+
+    private static String statement(String workload) {
+        switch (workload) {
+            case "control":
+                return "SELECT 1";
+            case "typical":
+                return "SELECT customer_id, sum(amount) AS total FROM sales AS 
s "
+                        + "WHERE region = 'east' GROUP BY customer_id ORDER BY 
total DESC LIMIT 10";
+            case "wide":
+                return "SELECT " + IntStream.range(0, 64)
+                        .mapToObj(index -> "c" + index + " AS alias" + index)
+                        .collect(Collectors.joining(", "))
+                        + " FROM catalog.db.fact_table";
+            case "nonReserved":
+                return "SELECT action, branch, cache, catalog, connection, 
engine, format, global, name "
+                        + "FROM aggregate AS alias WHERE action = 1";
+            case "quoted":
+                return "SELECT `AD``D`, `Mixed Name`, `select` FROM 
`db``name`.`table name` AS `t``1` "
+                        + "WHERE `t``1`.`AD``D` > 0";
+            default:
+                throw new IllegalArgumentException("Unknown workload: " + 
workload);
+        }
+    }
 }
diff --git 
a/fe/fe-sql-parser-benchmark/src/main/java/org/apache/doris/sqlparser/benchmark/PrimaryExpressionBenchmark.java
 
b/fe/fe-sql-parser-benchmark/src/main/java/org/apache/doris/sqlparser/benchmark/PrimaryExpressionBenchmark.java
index fca29e73bc4..0938338b1c4 100644
--- 
a/fe/fe-sql-parser-benchmark/src/main/java/org/apache/doris/sqlparser/benchmark/PrimaryExpressionBenchmark.java
+++ 
b/fe/fe-sql-parser-benchmark/src/main/java/org/apache/doris/sqlparser/benchmark/PrimaryExpressionBenchmark.java
@@ -19,7 +19,6 @@ package org.apache.doris.sqlparser.benchmark;
 
 import org.apache.doris.nereids.DorisParser;
 import org.apache.doris.nereids.parser.ParseErrorListener;
-import org.apache.doris.nereids.parser.PostProcessor;
 import org.apache.doris.sqlparser.DorisSqlParser;
 
 import org.antlr.v4.runtime.CommonTokenStream;
@@ -56,7 +55,6 @@ public class PrimaryExpressionBenchmark {
     public String workload;
 
     private final DorisSqlParser facade = new DorisSqlParser();
-    private final PostProcessor postProcessor = new PostProcessor();
     private final ParseErrorListener errorListener = new ParseErrorListener();
 
     private String sql;
@@ -105,7 +103,6 @@ public class PrimaryExpressionBenchmark {
     public Object parsePreTokenized() {
         CommonTokenStream stream = new CommonTokenStream(new 
ListTokenSource(tokens));
         DorisParser parser = new DorisParser(stream);
-        parser.addParseListener(postProcessor);
         parser.removeErrorListeners();
         parser.addErrorListener(errorListener);
         parser.getInterpreter().setPredictionMode(PredictionMode.SLL);
diff --git 
a/fe/fe-sql-parser-benchmark/src/main/java/org/apache/doris/sqlparser/benchmark/QueryOrDmlCommonPrefixBenchmark.java
 
b/fe/fe-sql-parser-benchmark/src/main/java/org/apache/doris/sqlparser/benchmark/QueryOrDmlCommonPrefixBenchmark.java
index b2700dce7f8..9efd78abbb9 100644
--- 
a/fe/fe-sql-parser-benchmark/src/main/java/org/apache/doris/sqlparser/benchmark/QueryOrDmlCommonPrefixBenchmark.java
+++ 
b/fe/fe-sql-parser-benchmark/src/main/java/org/apache/doris/sqlparser/benchmark/QueryOrDmlCommonPrefixBenchmark.java
@@ -19,7 +19,6 @@ package org.apache.doris.sqlparser.benchmark;
 
 import org.apache.doris.nereids.DorisParser;
 import org.apache.doris.nereids.parser.ParseErrorListener;
-import org.apache.doris.nereids.parser.PostProcessor;
 import org.apache.doris.sqlparser.DorisSqlParser;
 
 import org.antlr.v4.runtime.CommonTokenStream;
@@ -56,7 +55,6 @@ public class QueryOrDmlCommonPrefixBenchmark {
     public String workload;
 
     private final DorisSqlParser facade = new DorisSqlParser();
-    private final PostProcessor postProcessor = new PostProcessor();
     private final ParseErrorListener errorListener = new ParseErrorListener();
 
     private String sql;
@@ -99,7 +97,6 @@ public class QueryOrDmlCommonPrefixBenchmark {
     public Object parsePreTokenized() {
         CommonTokenStream stream = new CommonTokenStream(new 
ListTokenSource(tokens));
         DorisParser parser = new DorisParser(stream);
-        parser.addParseListener(postProcessor);
         parser.removeErrorListeners();
         parser.addErrorListener(errorListener);
         parser.getInterpreter().setPredictionMode(PredictionMode.SLL);
diff --git a/fe/fe-sql-parser/README.md b/fe/fe-sql-parser/README.md
index 73a5662d3b9..afc3dd8fa4e 100644
--- a/fe/fe-sql-parser/README.md
+++ b/fe/fe-sql-parser/README.md
@@ -35,7 +35,7 @@ fe-sql-parser/
     ├── org/apache/doris/nereids/
     │   ├── parser/                 # Parser support: CaseInsensitiveStream,
     │   │                           # Origin, OriginAware, ParserUtils,
-    │   │                           # ParseErrorListener, PostProcessor
+    │   │                           # ParseErrorListener
     │   ├── exceptions/             # ParseException, SyntaxParseException
     │   └── errors/QueryParsingErrors.java
     └── org/apache/doris/sqlparser/
@@ -423,7 +423,7 @@ public class AuditListener extends DorisParserBaseListener {
 
 ### Example 3: Live `ParseTreeListener` — fire during parsing
 
-Most cases are covered by Examples 1 and 2. If you need to intervene **while 
the parser is building each node** (mutating tokens, injecting metadata, 
streaming work), attach a listener with `parser.addParseListener(...)`. This is 
exactly how `fe-sql-parser`'s internal `PostProcessor` rewrites identifier case 
at parse time.
+Most cases are covered by Examples 1 and 2. If you need to intervene **while 
the parser is building each node** (mutating tokens, injecting metadata, 
streaming work), attach a listener with `parser.addParseListener(...)`.
 
 `DorisSqlParser.parseStatement` does not expose the parser instance; use 
`newLexer` + `newParser` to take ownership:
 
@@ -453,7 +453,7 @@ DorisParser.SingleStatementContext tree = 
parser.singleStatement();
 System.out.println(hintListener.hints);
 ```
 
-`newParser` already attaches `PostProcessor` and `ParseErrorListener`; your 
listener is added on top.
+`newParser` already attaches `ParseErrorListener`; your listener is added on 
top. Identifier normalization is handled locally by the grammar.
 
 ### Example 4: Wrap the facade — metrics, caching, rewriting
 
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 b03f6835717..3ec5d2b6dc0 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
@@ -24,6 +24,31 @@ options { tokenVocab = DorisLexer; }
 @members {
     public boolean ansiSQLSyntax = false;
 
+    private void replaceTokenByIdentifier(ParserRuleContext ctx, int 
stripMargins,
+            boolean unescapeBackticks) {
+        if (!getBuildParseTree()) {
+            return;
+        }
+        ParserRuleContext parent = ctx.getParent();
+        parent.removeLastChild();
+        Token token = (Token) ctx.getChild(0).getPayload();
+        CommonToken identifier = new CommonToken(
+                new Pair<>(token.getTokenSource(), token.getInputStream()),
+                IDENTIFIER,
+                token.getChannel(),
+                token.getStartIndex() + stripMargins,
+                token.getStopIndex() - stripMargins);
+        if (unescapeBackticks) {
+            identifier.setText(identifier.getText().replace("``", "`"));
+        }
+        parent.addChild(new TerminalNodeImpl(identifier));
+    }
+
+    private void reportUnquotedIdentifier(ErrorIdentContext ctx) {
+        throw 
org.apache.doris.nereids.errors.QueryParsingErrors.unquotedIdentifierError(
+                ctx.getParent().getText(), ctx);
+    }
+
     private boolean isTupleLambdaBody() {
         if (_input.LA(1) != LEFT_PAREN) {
             return false;
@@ -2171,6 +2196,11 @@ errorCapturingIdentifierExtra
     : (SUBTRACT identifier)+ #errorIdent
     |                        #realIdent
     ;
+finally {
+    if ($ctx instanceof ErrorIdentContext) {
+        reportUnquotedIdentifier((ErrorIdentContext) $ctx);
+    }
+}
 
 identifier
     : strictIdentifier
@@ -2183,6 +2213,7 @@ strictIdentifier
     ;
 
 quotedIdentifier
+@after { replaceTokenByIdentifier($ctx, 1, true); }
     : BACKQUOTED_IDENTIFIER
     ;
 
@@ -2201,6 +2232,7 @@ dollarQuotedString
 // The non-reserved keywords are listed in `nonReserved`.
 // TODO: need to stay consistent with the legacy
 nonReserved
+@after { replaceTokenByIdentifier($ctx, 0, false); }
 //--DEFAULT-NON-RESERVED-START
     : ACTIONS
     | AFTER
diff --git 
a/fe/fe-sql-parser/src/main/java/org/apache/doris/nereids/parser/PostProcessor.java
 
b/fe/fe-sql-parser/src/main/java/org/apache/doris/nereids/parser/PostProcessor.java
deleted file mode 100644
index d94fb545081..00000000000
--- 
a/fe/fe-sql-parser/src/main/java/org/apache/doris/nereids/parser/PostProcessor.java
+++ /dev/null
@@ -1,71 +0,0 @@
-// 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.DorisParser;
-import org.apache.doris.nereids.DorisParser.ErrorIdentContext;
-import org.apache.doris.nereids.DorisParser.NonReservedContext;
-import org.apache.doris.nereids.DorisParser.QuotedIdentifierContext;
-import org.apache.doris.nereids.DorisParserBaseListener;
-import org.apache.doris.nereids.errors.QueryParsingErrors;
-
-import org.antlr.v4.runtime.CommonToken;
-import org.antlr.v4.runtime.ParserRuleContext;
-import org.antlr.v4.runtime.Token;
-import org.antlr.v4.runtime.tree.TerminalNodeImpl;
-
-import java.util.function.Function;
-
-/**
- * Do some post processor after parse to facilitate subsequent analysis.
- */
-public class PostProcessor extends DorisParserBaseListener {
-    @Override
-    public void exitErrorIdent(ErrorIdentContext ctx) {
-        String ident = ctx.getParent().getText();
-        throw QueryParsingErrors.unquotedIdentifierError(ident, ctx);
-    }
-
-    @Override
-    public void exitQuotedIdentifier(QuotedIdentifierContext ctx) {
-        replaceTokenByIdentifier(ctx, 1, token -> {
-            // Remove the double back ticks in the string.
-            token.setText(token.getText().replace("``", "`"));
-            return token;
-        });
-    }
-
-    @Override
-    public void exitNonReserved(NonReservedContext ctx) {
-        replaceTokenByIdentifier(ctx, 0, i -> i);
-    }
-
-    private void replaceTokenByIdentifier(ParserRuleContext ctx, int 
stripMargins,
-            Function<CommonToken, CommonToken> f) {
-        ParserRuleContext parent = ctx.getParent();
-        parent.removeLastChild();
-        Token token = (Token) (ctx.getChild(0).getPayload());
-        CommonToken newToken = new CommonToken(
-                new org.antlr.v4.runtime.misc.Pair<>(token.getTokenSource(), 
token.getInputStream()),
-                DorisParser.IDENTIFIER,
-                token.getChannel(),
-                token.getStartIndex() + stripMargins,
-                token.getStopIndex() - stripMargins);
-        parent.addChild(new TerminalNodeImpl(f.apply(newToken)));
-    }
-}
diff --git 
a/fe/fe-sql-parser/src/main/java/org/apache/doris/sqlparser/DorisSqlParser.java 
b/fe/fe-sql-parser/src/main/java/org/apache/doris/sqlparser/DorisSqlParser.java
index e006228cf3f..6d62a273448 100644
--- 
a/fe/fe-sql-parser/src/main/java/org/apache/doris/sqlparser/DorisSqlParser.java
+++ 
b/fe/fe-sql-parser/src/main/java/org/apache/doris/sqlparser/DorisSqlParser.java
@@ -25,7 +25,6 @@ import 
org.apache.doris.nereids.DorisParser.MultiStatementsContext;
 import org.apache.doris.nereids.DorisParser.SingleStatementContext;
 import org.apache.doris.nereids.parser.CaseInsensitiveStream;
 import org.apache.doris.nereids.parser.ParseErrorListener;
-import org.apache.doris.nereids.parser.PostProcessor;
 
 import org.antlr.v4.runtime.CharStreams;
 import org.antlr.v4.runtime.CommonTokenStream;
@@ -41,7 +40,6 @@ import java.util.function.Function;
  */
 public final class DorisSqlParser {
     private static final ParseErrorListener PARSE_ERROR_LISTENER = new 
ParseErrorListener();
-    private static final PostProcessor POST_PROCESSOR = new PostProcessor();
 
     private final boolean noBackslashEscapes;
     private final boolean ansiSqlSyntax;
@@ -116,7 +114,6 @@ public final class DorisSqlParser {
 
     private DorisParser configure(DorisParser parser) {
         parser.ansiSQLSyntax = ansiSqlSyntax;
-        parser.addParseListener(POST_PROCESSOR);
         parser.removeErrorListeners();
         parser.addErrorListener(PARSE_ERROR_LISTENER);
         return parser;
diff --git 
a/fe/fe-sql-parser/src/test/java/org/apache/doris/sqlparser/DorisSqlParserTest.java
 
b/fe/fe-sql-parser/src/test/java/org/apache/doris/sqlparser/DorisSqlParserTest.java
index 928160e85c4..362c0d90404 100644
--- 
a/fe/fe-sql-parser/src/test/java/org/apache/doris/sqlparser/DorisSqlParserTest.java
+++ 
b/fe/fe-sql-parser/src/test/java/org/apache/doris/sqlparser/DorisSqlParserTest.java
@@ -17,14 +17,21 @@
 
 package org.apache.doris.sqlparser;
 
+import org.apache.doris.nereids.DorisParser;
 import org.apache.doris.nereids.DorisParser.ExpressionContext;
 import org.apache.doris.nereids.DorisParser.MultiStatementsContext;
 import org.apache.doris.nereids.DorisParser.SingleStatementContext;
 import org.apache.doris.nereids.exceptions.ParseException;
 
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.tree.ParseTree;
+import org.antlr.v4.runtime.tree.TerminalNode;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
+import java.util.ArrayList;
+import java.util.List;
+
 class DorisSqlParserTest {
     private final DorisSqlParser parser = new DorisSqlParser();
 
@@ -72,4 +79,84 @@ class DorisSqlParserTest {
     void rejectsTrailingGarbageInExpression() {
         Assertions.assertThrows(ParseException.class, () -> 
parser.parseExpression("1 + 2 BAD GARBAGE"));
     }
+
+    @Test
+    void normalizesIdentifiersInGrammarRules() {
+        String sql = "SELECT ordinary, actions, `AD``D` FROM 
catalog.`db``name`.`table name`";
+        List<String> expected = List.of("ordinary", "actions", "AD`D", 
"catalog", "db`name", "table name");
+
+        SingleStatementContext facadeTree = parser.parseStatement(sql);
+        Assertions.assertEquals(expected, identifierTexts(facadeTree));
+
+        DorisParser generatedParser = parser.newParser(parser.newLexer(sql));
+        Assertions.assertTrue(generatedParser.getParseListeners().isEmpty());
+        SingleStatementContext generatedTree = 
generatedParser.singleStatement();
+        Assertions.assertEquals(expected, identifierTexts(generatedTree));
+
+        List<Token> identifiers = identifierTokens(facadeTree);
+        Token nonReserved = identifiers.get(1);
+        Assertions.assertEquals(DorisParser.IDENTIFIER, nonReserved.getType());
+        Assertions.assertEquals(sql.indexOf("actions"), 
nonReserved.getStartIndex());
+        Assertions.assertEquals(sql.indexOf("actions") + "actions".length() - 
1, nonReserved.getStopIndex());
+
+        Token quoted = identifiers.get(2);
+        Assertions.assertEquals(DorisParser.IDENTIFIER, quoted.getType());
+        Assertions.assertEquals(sql.indexOf("`AD``D`") + 1, 
quoted.getStartIndex());
+        Assertions.assertEquals(sql.indexOf("`AD``D`") + "`AD``D`".length() - 
2, quoted.getStopIndex());
+    }
+
+    @Test
+    void rejectsUnquotedIdentifiersInGrammarRule() {
+        ParseException reservedSuffixException = Assertions.assertThrows(
+                ParseException.class, () -> parser.parseStatement("SELECT * 
FROM test-table"));
+        Assertions.assertTrue(reservedSuffixException.getMessage().contains(
+                "Possibly unquoted identifier test- detected"));
+
+        String sql = "SELECT * FROM test-tbl";
+        ParseException facadeException = Assertions.assertThrows(
+                ParseException.class, () -> parser.parseStatement(sql));
+
+        DorisParser generatedParser = parser.newParser(parser.newLexer(sql));
+        ParseException generatedException = Assertions.assertThrows(
+                ParseException.class, generatedParser::singleStatement);
+
+        Assertions.assertEquals(facadeException.getMessage(), 
generatedException.getMessage());
+        Assertions.assertTrue(facadeException.getMessage().contains(
+                "Possibly unquoted identifier test-tbl detected"));
+    }
+
+    @Test
+    void parsesWithoutBuildingParseTree() {
+        DorisParser generatedParser = parser.newParser(
+                parser.newLexer("SELECT actions, `AD``D` FROM t"));
+        generatedParser.setBuildParseTree(false);
+        Assertions.assertDoesNotThrow(generatedParser::singleStatement);
+    }
+
+    private static List<String> identifierTexts(ParseTree tree) {
+        List<String> texts = new ArrayList<>();
+        for (Token token : identifierTokens(tree)) {
+            texts.add(token.getText());
+        }
+        return texts;
+    }
+
+    private static List<Token> identifierTokens(ParseTree tree) {
+        List<Token> tokens = new ArrayList<>();
+        collectIdentifierTokens(tree, tokens);
+        return tokens;
+    }
+
+    private static void collectIdentifierTokens(ParseTree tree, List<Token> 
tokens) {
+        if (tree instanceof TerminalNode) {
+            Token token = ((TerminalNode) tree).getSymbol();
+            if (token.getType() == DorisParser.IDENTIFIER) {
+                tokens.add(token);
+            }
+            return;
+        }
+        for (int index = 0; index < tree.getChildCount(); index++) {
+            collectIdentifierTokens(tree.getChild(index), tokens);
+        }
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to