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 fb0b0c5358e [refactor](parser) Dispatch parser statements by first 
token (#66944)
fb0b0c5358e is described below

commit fb0b0c5358e341286c30d7af67a6b996a9d50b1d
Author: morrySnow <[email protected]>
AuthorDate: Mon Aug 31 23:53:30 2026 +0800

    [refactor](parser) Dispatch parser statements by first token (#66944)
    
    ### What problem does this PR solve?
    
    Problem Summary: `statementBase` previously grouped unrelated statement
    families into one ANTLR adaptive prediction decision. This PR splits
    cross-prefix families and dispatches them through first-token-specific
    rules, changing generated `statementBase()` from one `adaptivePredict`
    call to a direct `LA(1)` switch. It also removes the redundant
    `supported` prefix from statement grammar rules and generated
    Context/visitor names; dispatcher rules use the `*StatementDispatch`
    suffix where the natural statement name is already occupied. All
    concrete grammar alternatives and FE behavior are preserved. Parent CST
    Context compatibility is intentionally out of scope because the
    standalone parser CST is not a public API.
    
    Shared-host JMH rounds were rejected because they did not pass the
    predeclared noise gate, so this PR does not claim an unverified
    wall-clock improvement.
---
 .../doris/nereids/parser/LogicalPlanBuilder.java   |  10 +-
 .../parser/LogicalPlanBuilderForEncryption.java    |   8 +-
 .../parser/ParseInsertPartitionSpecTest.java       |  29 +-
 fe/fe-sql-parser/README.md                         |   8 +-
 .../antlr4/org/apache/doris/nereids/DorisParser.g4 | 300 +++++++++++++++------
 .../doris/sqlparser/StatementBaseDispatchTest.java | 160 +++++++++++
 6 files changed, 397 insertions(+), 118 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
index b27aa93a45f..8b7126d730a 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
@@ -465,7 +465,6 @@ import 
org.apache.doris.nereids.DorisParser.StructLiteralContext;
 import org.apache.doris.nereids.DorisParser.SubqueryContext;
 import org.apache.doris.nereids.DorisParser.SubqueryExpressionContext;
 import org.apache.doris.nereids.DorisParser.SubstringContext;
-import org.apache.doris.nereids.DorisParser.SupportedUnsetStatementContext;
 import org.apache.doris.nereids.DorisParser.SwitchCatalogContext;
 import org.apache.doris.nereids.DorisParser.SyncContext;
 import org.apache.doris.nereids.DorisParser.SystemVariableContext;
@@ -479,6 +478,7 @@ import 
org.apache.doris.nereids.DorisParser.TypeConstructorContext;
 import org.apache.doris.nereids.DorisParser.UninstallPluginContext;
 import org.apache.doris.nereids.DorisParser.UnitIdentifierContext;
 import org.apache.doris.nereids.DorisParser.UnlockTablesContext;
+import org.apache.doris.nereids.DorisParser.UnsetStatementContext;
 import org.apache.doris.nereids.DorisParser.UnsupportedStartTransactionContext;
 import org.apache.doris.nereids.DorisParser.UpdateAssignmentContext;
 import org.apache.doris.nereids.DorisParser.UpdateAssignmentSeqContext;
@@ -1254,7 +1254,7 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
                 ? Maps.newHashMap(visitPropertyClause(ctx.jobProperties)) : 
Maps.newHashMap();
         String comment =
                 visitCommentSpec(ctx.commentSpec());
-        String executeSql = ctx.supportedDmlStatement() == null ? "" : 
getOriginSql(ctx.supportedDmlStatement());
+        String executeSql = ctx.dmlStatement() == null ? "" : 
getOriginSql(ctx.dmlStatement());
         JobFromToClauseContext jobFromToClauseCtx = ctx.jobFromToClause();
         String sourceType = null;
         String targetDb = null;
@@ -1273,7 +1273,7 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
         return new CreateJobCommand(createJobInfo);
     }
 
-    private void checkJobNameKey(String key, String keyFormat, 
DorisParser.SupportedJobStatementContext parseContext) {
+    private void checkJobNameKey(String key, String keyFormat, 
ParserRuleContext parseContext) {
         if (key.isEmpty() || !key.equalsIgnoreCase(keyFormat)) {
             throw new ParseException(keyFormat + " should be: '" + keyFormat + 
"'", parseContext);
         }
@@ -1283,7 +1283,7 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
     public LogicalPlan visitAlterJob(DorisParser.AlterJobContext ctx) {
         Map<String, String> properties = ctx.propertyClause() != null
                 ? Maps.newHashMap(visitPropertyClause(ctx.propertyClause())) : 
Maps.newHashMap();
-        String executeSql = ctx.supportedDmlStatement() != null ? 
getOriginSql(ctx.supportedDmlStatement()) : "";
+        String executeSql = ctx.dmlStatement() != null ? 
getOriginSql(ctx.dmlStatement()) : "";
         String sourceType = null;
         String targetDb = null;
         Map<String, String> sourceProperties = Maps.newHashMap();
@@ -5568,7 +5568,7 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
     }
 
     @Override
-    public LogicalPlan 
visitSupportedUnsetStatement(SupportedUnsetStatementContext ctx) {
+    public LogicalPlan visitUnsetStatement(UnsetStatementContext ctx) {
         if (ctx.DEFAULT() != null && ctx.STORAGE() != null && ctx.VAULT() != 
null) {
             return new UnsetDefaultStorageVaultCommand();
         }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilderForEncryption.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilderForEncryption.java
index 006750dbe5d..1e074463d94 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilderForEncryption.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilderForEncryption.java
@@ -227,8 +227,8 @@ public class LogicalPlanBuilderForEncryption extends 
LogicalPlanBuilder {
     // create job select tvf
     @Override
     public LogicalPlan 
visitCreateScheduledJob(DorisParser.CreateScheduledJobContext ctx) {
-        if (ctx.supportedDmlStatement() instanceof InsertTableContext) {
-            visitInsertTable((InsertTableContext) ctx.supportedDmlStatement());
+        if (ctx.dmlStatement() instanceof InsertTableContext) {
+            visitInsertTable((InsertTableContext) ctx.dmlStatement());
         } else if (ctx.jobFromToClause() != null) {
             JobFromToClauseContext jobFromToClauseContext = 
ctx.jobFromToClause();
             
encryptProperty(visitPropertyItemList(jobFromToClauseContext.sourceProperties),
@@ -242,8 +242,8 @@ public class LogicalPlanBuilderForEncryption extends 
LogicalPlanBuilder {
     // alter job select tvf
     @Override
     public LogicalPlan visitAlterJob(DorisParser.AlterJobContext ctx) {
-        if (ctx.supportedDmlStatement() instanceof InsertTableContext) {
-            visitInsertTable((InsertTableContext) ctx.supportedDmlStatement());
+        if (ctx.dmlStatement() instanceof InsertTableContext) {
+            visitInsertTable((InsertTableContext) ctx.dmlStatement());
         } else if (ctx.jobFromToClause() != null) {
             JobFromToClauseContext jobFromToClauseContext = 
ctx.jobFromToClause();
             
encryptProperty(visitPropertyItemList(jobFromToClauseContext.sourceProperties),
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/ParseInsertPartitionSpecTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/ParseInsertPartitionSpecTest.java
index 487a224bc8a..70a3d0e6375 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/ParseInsertPartitionSpecTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/ParseInsertPartitionSpecTest.java
@@ -23,7 +23,6 @@ import 
org.apache.doris.nereids.trees.expressions.literal.StringLikeLiteral;
 import org.apache.doris.qe.ConnectContext;
 
 import com.google.common.collect.Maps;
-import org.antlr.v4.runtime.ParserRuleContext;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
@@ -81,30 +80,12 @@ public class ParseInsertPartitionSpecTest {
     }
 
     /**
-     * Helper method to parse SQL and extract PartitionSpecContext using 
reflection.
+     * Helper method to parse SQL and extract PartitionSpecContext.
      */
-    private Object parsePartitionSpec(String insertSql) throws Exception {
-        // Use NereidsParser.toAst() to parse the SQL and get the AST
-        ParserRuleContext tree = NereidsParser.toAst(
-                insertSql, DorisParser::singleStatement);
-
-        // The tree is a SingleStatementContext, which contains a 
StatementContext
-        // which contains a StatementBaseContext which contains an 
InsertTableContext
-        // Use reflection to navigate the AST structure
-        Method getChildMethod = ParserRuleContext.class.getMethod("getChild", 
int.class);
-
-        // Get statement from singleStatement (index 0)
-        Object statement = getChildMethod.invoke(tree, 0);
-
-        // Get statementBase from statement (index 0)
-        Object statementBase = getChildMethod.invoke(statement, 0);
-
-        // Get insertTable from statementBase (index 0)
-        Object insertTableCtx = getChildMethod.invoke(statementBase, 0);
-
-        // Get partitionSpec() from insertTableCtx using the method
-        Method partitionSpecMethod = 
insertTableCtx.getClass().getMethod("partitionSpec");
-        return partitionSpecMethod.invoke(insertTableCtx);
+    private DorisParser.PartitionSpecContext parsePartitionSpec(String 
insertSql) {
+        DorisParser.InsertTableContext insertTableContext = 
(DorisParser.InsertTableContext) NereidsParser.toAst(
+                insertSql, DorisParser::dmlStatement);
+        return insertTableContext.partitionSpec();
     }
 
     @Test
diff --git a/fe/fe-sql-parser/README.md b/fe/fe-sql-parser/README.md
index 73d2d509d46..3cab071b33f 100644
--- a/fe/fe-sql-parser/README.md
+++ b/fe/fe-sql-parser/README.md
@@ -385,8 +385,8 @@ import org.antlr.v4.runtime.tree.ParseTreeWalker;
 
 public class DropGuardListener extends DorisParserBaseListener {
     @Override
-    public void 
enterSupportedDropStatement(DorisParser.SupportedDropStatementContext ctx) {
-        throw new SecurityException("DROP statements are not allowed: " + 
ctx.getText());
+    public void enterDropTable(DorisParser.DropTableContext ctx) {
+        throw new SecurityException("DROP TABLE statements are not allowed: " 
+ ctx.getText());
     }
 }
 
@@ -411,8 +411,8 @@ public class AuditListener extends DorisParserBaseListener {
     @Override public void enterDelete(DorisParser.DeleteContext ctx) {
         writes.add("DELETE " + ctx.tableName.getText());
     }
-    @Override public void 
enterSupportedDropStatement(DorisParser.SupportedDropStatementContext ctx) {
-        writes.add("DROP " + ctx.getText());
+    @Override public void enterDropTable(DorisParser.DropTableContext ctx) {
+        writes.add("DROP TABLE " + ctx.name.getText());
     }
 }
 ```
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 42ac02d145e..2176dd3fb84 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
@@ -46,33 +46,97 @@ statement
     ;
 
 statementBase
+    : queryOrDmlStatement
+    | createStatementDispatch
+    | alterStatementDispatch
+    | dropStatementDispatch
+    | showStatementDispatch
+    | pauseStatement
+    | resumeStatement
+    | cancelStatementDispatch
+    | refreshStatementDispatch
+    | killStatementDispatch
+    | buildStatement
+    | syncLoadStatement
+    | stopLoadStatement
+    | cleanStatement
+    | setStatement
+    | unsetStatement
+    | recoverStatement
+    | adminStatement
+    | useStatement
+    | analyzeStatsStatement
+    | transactionStatement
+    | grantRevokeStatement
+    ;
+
+queryOrDmlStatement
     : explain? query outFileClause?     #statementDefault
-    | supportedDmlStatement             #supportedDmlStatementAlias
-    | supportedCreateStatement          #supportedCreateStatementAlias
-    | supportedAlterStatement           #supportedAlterStatementAlias
-    | materializedViewStatement         #materializedViewStatementAlias
-    | supportedJobStatement             #supportedJobStatementAlias
-    | constraintStatement               #constraintStatementAlias
-    | supportedCleanStatement           #supportedCleanStatementAlias
-    | supportedDescribeStatement        #supportedDescribeStatementAlias
-    | supportedDropStatement            #supportedDropStatementAlias
-    | supportedSetStatement             #supportedSetStatementAlias
-    | supportedUnsetStatement           #supportedUnsetStatementAlias
-    | supportedRefreshStatement         #supportedRefreshStatementAlias
-    | supportedShowStatement            #supportedShowStatementAlias
-    | supportedLoadStatement            #supportedLoadStatementAlias
-    | supportedCancelStatement          #supportedCancelStatementAlias
-    | supportedRecoverStatement         #supportedRecoverStatementAlias
-    | supportedAdminStatement           #supportedAdminStatementAlias
-    | supportedUseStatement             #supportedUseStatementAlias
-    | supportedOtherStatement           #supportedOtherStatementAlias
-    | supportedKillStatement            #supportedKillStatementAlias
-    | supportedStatsStatement           #supportedStatsStatementAlias
-    | supportedTransactionStatement     #supportedTransactionStatementAlias
-    | supportedGrantRevokeStatement     #supportedGrantRevokeStatementAlias
-    ;
-
-materializedViewStatement
+    | dmlStatement                      #dmlStatementAlias
+    | describeStatement                 #describeStatementAlias
+    | otherStatement                    #otherStatementAlias
+    | loadDmlStatement                  #loadStatementAlias
+    ;
+
+createStatementDispatch
+    : createStatement
+    | createMaterializedViewStatement
+    | createJobStatement
+    | createLoadStatement
+    ;
+
+alterStatementDispatch
+    : alterStatement
+    | alterMaterializedViewStatement
+    | alterJobStatement
+    | alterConstraintStatement
+    | alterStatsStatement
+    ;
+
+dropStatementDispatch
+    : dropStatement
+    | dropMaterializedViewStatement
+    | dropJobStatement
+    | dropStatsStatement
+    ;
+
+showStatementDispatch
+    : showStatement
+    | showMaterializedViewStatement
+    | showConstraintStatement
+    | showLoadStatement
+    | showStatsStatement
+    ;
+
+pauseStatement
+    : pauseMaterializedViewStatement
+    | pauseJobStatement
+    | pauseLoadStatement
+    ;
+
+resumeStatement
+    : resumeMaterializedViewStatement
+    | resumeJobStatement
+    | resumeLoadStatement
+    ;
+
+cancelStatementDispatch
+    : cancelMaterializedViewStatement
+    | cancelJobStatement
+    | cancelStatement
+    ;
+
+refreshStatementDispatch
+    : refreshMaterializedViewStatement
+    | refreshStatement
+    ;
+
+killStatementDispatch
+    : killStatement
+    | killStatsStatement
+    ;
+
+createMaterializedViewStatement
     : CREATE MATERIALIZED VIEW (IF NOT EXISTS)? mvName=multipartIdentifier
         (LEFT_PAREN cols=simpleColumnDefs RIGHT_PAREN)? buildMode?
         (REFRESH refreshMethod? refreshTrigger?)?
@@ -83,17 +147,38 @@ materializedViewStatement
         (BUCKETS (INTEGER_VALUE | AUTO))?)?
         propertyClause?
         AS? query                                                              
                 #createMTMV
-    | REFRESH MATERIALIZED VIEW mvName=multipartIdentifier (partitionSpec | 
COMPLETE | AUTO)    #refreshMTMV
-    | ALTER MATERIALIZED VIEW mvName=multipartIdentifier ((RENAME 
renameNewName=multipartIdentifier)
+    ;
+
+refreshMaterializedViewStatement
+    : REFRESH MATERIALIZED VIEW mvName=multipartIdentifier (partitionSpec | 
COMPLETE | AUTO)    #refreshMTMV
+    ;
+
+alterMaterializedViewStatement
+    : ALTER MATERIALIZED VIEW mvName=multipartIdentifier ((RENAME 
renameNewName=multipartIdentifier)
         | (REFRESH (refreshMethod | refreshTrigger | refreshMethod 
refreshTrigger))
         | REPLACE WITH MATERIALIZED VIEW replaceNewName=identifier 
propertyClause?
         | (SET  LEFT_PAREN fileProperties=propertyItemList RIGHT_PAREN))       
                 #alterMTMV
-    | DROP MATERIALIZED VIEW (IF EXISTS)? mvName=multipartIdentifier
+    ;
+
+dropMaterializedViewStatement
+    : DROP MATERIALIZED VIEW (IF EXISTS)? mvName=multipartIdentifier
         (ON tableName=multipartIdentifier)?                                    
                 #dropMV
-    | PAUSE MATERIALIZED VIEW JOB ON mvName=multipartIdentifier                
                 #pauseMTMV
-    | RESUME MATERIALIZED VIEW JOB ON mvName=multipartIdentifier               
                 #resumeMTMV
-    | CANCEL MATERIALIZED VIEW TASK taskId=INTEGER_VALUE ON 
mvName=multipartIdentifier          #cancelMTMVTask
-    | SHOW CREATE MATERIALIZED VIEW mvName=multipartIdentifier                 
                 #showCreateMTMV
+    ;
+
+pauseMaterializedViewStatement
+    : PAUSE MATERIALIZED VIEW JOB ON mvName=multipartIdentifier                
                 #pauseMTMV
+    ;
+
+resumeMaterializedViewStatement
+    : RESUME MATERIALIZED VIEW JOB ON mvName=multipartIdentifier               
                 #resumeMTMV
+    ;
+
+cancelMaterializedViewStatement
+    : CANCEL MATERIALIZED VIEW TASK taskId=INTEGER_VALUE ON 
mvName=multipartIdentifier          #cancelMTMVTask
+    ;
+
+showMaterializedViewStatement
+    : SHOW CREATE MATERIALIZED VIEW mvName=multipartIdentifier                 
                 #showCreateMTMV
     ;
 
 jobFromToClause
@@ -101,7 +186,7 @@ jobFromToClause
       TO DATABASE targetDb=identifier (LEFT_PAREN 
targetProperties=propertyItemList RIGHT_PAREN)?
     ;
 
-supportedJobStatement
+createJobStatement
     : CREATE JOB label=multipartIdentifier jobProperties=propertyClause?
       ON (STREAMING | SCHEDULE(
             (EVERY timeInterval=INTEGER_VALUE timeUnit=identifier
@@ -112,29 +197,49 @@ supportedJobStatement
             )
          )
       commentSpec?
-       (jobFromToClause | DO supportedDmlStatement )                           
                                                              
#createScheduledJob
-   | PAUSE JOB WHERE (jobNameKey=identifier) EQ (jobNameValue=STRING_LITERAL)  
                                                              #pauseJob
-   | ALTER JOB (jobName=multipartIdentifier)
-               (propertyClause | supportedDmlStatement | propertyClause  
supportedDmlStatement
-               | jobFromToClause | propertyClause jobFromToClause)             
                                                              #alterJob
-   | DROP JOB (IF EXISTS)? WHERE (jobNameKey=identifier) EQ 
(jobNameValue=STRING_LITERAL)                                                   
 #dropJob
-   | RESUME JOB WHERE (jobNameKey=identifier) EQ (jobNameValue=STRING_LITERAL) 
                                                              #resumeJob
-   | CANCEL TASK WHERE (jobNameKey=identifier) EQ 
(jobNameValue=STRING_LITERAL) AND (taskIdKey=identifier) EQ 
(taskIdValue=INTEGER_VALUE)    #cancelJobTask
+       (jobFromToClause | DO dmlStatement )                                    
                                                              
#createScheduledJob
    ;
-constraintStatement
+
+pauseJobStatement
+    : PAUSE JOB WHERE (jobNameKey=identifier) EQ (jobNameValue=STRING_LITERAL) 
                                                              #pauseJob
+    ;
+
+alterJobStatement
+    : ALTER JOB (jobName=multipartIdentifier)
+               (propertyClause | dmlStatement | propertyClause dmlStatement
+               | jobFromToClause | propertyClause jobFromToClause)             
                                                              #alterJob
+    ;
+
+dropJobStatement
+    : DROP JOB (IF EXISTS)? WHERE (jobNameKey=identifier) EQ 
(jobNameValue=STRING_LITERAL)                                                   
 #dropJob
+    ;
+
+resumeJobStatement
+    : RESUME JOB WHERE (jobNameKey=identifier) EQ 
(jobNameValue=STRING_LITERAL)                                                   
           #resumeJob
+    ;
+
+cancelJobStatement
+    : CANCEL TASK WHERE (jobNameKey=identifier) EQ 
(jobNameValue=STRING_LITERAL)
+        AND (taskIdKey=identifier) EQ (taskIdValue=INTEGER_VALUE)              
                                                               #cancelJobTask
+    ;
+
+alterConstraintStatement
     : ALTER TABLE table=multipartIdentifier
         ADD CONSTRAINT constraintName=errorCapturingIdentifier
         constraint                                                        
#addConstraint
     | ALTER TABLE table=multipartIdentifier
         DROP CONSTRAINT constraintName=errorCapturingIdentifier           
#dropConstraint
-    | SHOW CONSTRAINTS FROM table=multipartIdentifier                     
#showConstraint
+    ;
+
+showConstraintStatement
+    : SHOW CONSTRAINTS FROM table=multipartIdentifier                     
#showConstraint
     ;
 
 optSpecBranch
     : ATSIGN BRANCH LEFT_PAREN name=identifier RIGHT_PAREN
     ;
 
-supportedDmlStatement
+dmlStatement
     : explain? cte? INSERT INTO tvfName=identifier
         LEFT_PAREN tvfProperties=propertyItemList RIGHT_PAREN
         (WITH LABEL labelName=identifier)?
@@ -188,7 +293,7 @@ mergeNotMatchedClause
         INSERT cols=identifierList? VALUES rowConstructor
     ;
 
-supportedCreateStatement
+createStatement
     : CREATE (EXTERNAL | TEMPORARY)? TABLE (IF NOT EXISTS)? 
name=multipartIdentifier
         ((ctasCols=identifierList)? | (LEFT_PAREN columnDefs (COMMA 
indexDefs)? COMMA? RIGHT_PAREN))
         (ENGINE EQ engine=identifier)?
@@ -233,8 +338,6 @@ supportedCreateStatement
         USING LEFT_PAREN booleanExpression RIGHT_PAREN                    
#createRowPolicy
     | CREATE STORAGE POLICY (IF NOT EXISTS)?
         name=identifier properties=propertyClause?                             
 #createStoragePolicy
-    | BUILD INDEX (name=identifier)? ON tableName=multipartIdentifier
-        partitionSpec?                                                         
 #buildIndex
     | CREATE INDEX (IF NOT EXISTS)? name=identifier
         ON tableName=multipartIdentifier identifierList
         (USING indexType=(BLOOMFILTER | NGRAM_BF | INVERTED | ANN))?
@@ -284,13 +387,18 @@ supportedCreateStatement
         name=identifier properties=propertyClause?                             
     #createIndexNormalizer
     ;
 
+buildStatement
+    : BUILD INDEX (name=identifier)? ON tableName=multipartIdentifier
+        partitionSpec?                                                         
 #buildIndex
+    ;
+
 dictionaryColumnDefs:
        dictionaryColumnDef (COMMA dictionaryColumnDef)*;
 
 dictionaryColumnDef:
        colName = identifier columnType = (KEY | VALUE) ;
 
-supportedAlterStatement
+alterStatement
     : ALTER SYSTEM alterSystemClause                                           
             #alterSystem
     | ALTER VIEW name=multipartIdentifier
       (MODIFY commentSpec |
@@ -345,7 +453,7 @@ supportedAlterStatement
         passwordOption requireClause? commentSpec?                             
             #alterUser
     ;
 
-supportedDropStatement
+dropStatement
     : DROP CATALOG RECYCLE BIN WHERE idType=STRING_LITERAL EQ id=INTEGER_VALUE 
 #dropCatalogRecycleBin
     | DROP ENCRYPTKEY (IF EXISTS)? name=multipartIdentifier                    
 #dropEncryptkey
     | DROP ROLE (IF EXISTS)? name=identifierOrText                             
 #dropRole
@@ -381,7 +489,7 @@ supportedDropStatement
     | DROP STREAM (IF EXISTS)? name=multipartIdentifier FORCE?                 
 #dropStream
     ;
 
-supportedShowStatement
+showStatement
     : SHOW statementScope? VARIABLES wildWhere?                                
     #showVariables
     | SHOW AUTHORS                                                             
     #showAuthors
     | SHOW ALTER TABLE (ROLLUP | (MATERIALIZED VIEW) | COLUMN)
@@ -504,19 +612,37 @@ supportedShowStatement
     | SHOW CREATE STREAM name=multipartIdentifier                              
     #showCreateStream
     ;
 
-supportedLoadStatement
+syncLoadStatement
     : SYNC                                                                     
     #sync
-    | SHOW CREATE LOAD FOR label=multipartIdentifier                           
     #showCreateLoad    
-    | createRoutineLoad                                                        
     #createRoutineLoadAlias
-    | LOAD mysqlDataDesc
+    ;
+
+createLoadStatement
+    : createRoutineLoad                                                        
     #createRoutineLoadAlias
+    ;
+
+loadDmlStatement
+    : LOAD mysqlDataDesc
         (PROPERTIES LEFT_PAREN properties=propertyItemList RIGHT_PAREN)?
         (commentSpec)?                                                         
     #mysqlLoad
-    | SHOW ALL? CREATE ROUTINE LOAD FOR label=multipartIdentifier              
     #showCreateRoutineLoad
-    | PAUSE ROUTINE LOAD FOR label=multipartIdentifier                         
     #pauseRoutineLoad
+    ;
+
+pauseLoadStatement
+    : PAUSE ROUTINE LOAD FOR label=multipartIdentifier                         
     #pauseRoutineLoad
     | PAUSE ALL ROUTINE LOAD                                                   
     #pauseAllRoutineLoad
-    | RESUME ROUTINE LOAD FOR label=multipartIdentifier                        
     #resumeRoutineLoad
+    ;
+
+resumeLoadStatement
+    : RESUME ROUTINE LOAD FOR label=multipartIdentifier                        
     #resumeRoutineLoad
     | RESUME ALL ROUTINE LOAD                                                  
     #resumeAllRoutineLoad
-    | STOP ROUTINE LOAD FOR label=multipartIdentifier                          
     #stopRoutineLoad
+    ;
+
+stopLoadStatement
+    : STOP ROUTINE LOAD FOR label=multipartIdentifier                          
     #stopRoutineLoad
+    ;
+
+showLoadStatement
+    : SHOW CREATE LOAD FOR label=multipartIdentifier                           
     #showCreateLoad
+    | SHOW ALL? CREATE ROUTINE LOAD FOR label=multipartIdentifier              
     #showCreateRoutineLoad
     | SHOW ALL? ROUTINE LOAD ((FOR label=multipartIdentifier) | (LIKE 
STRING_LITERAL)?)         #showRoutineLoad
     | SHOW ROUTINE LOAD TASK ((FOR label=multipartIdentifier)
         | (((FROM | IN) database=identifier)? wildWhere?))                     
      #showRoutineLoadTask
@@ -527,12 +653,12 @@ supportedLoadStatement
     | SHOW INVERTED INDEX NORMALIZER                                           
     #showIndexNormalizer
     ;
 
-supportedKillStatement
+killStatement
     : KILL (CONNECTION)? INTEGER_VALUE                                         
     #killConnection
     | KILL QUERY (INTEGER_VALUE | STRING_LITERAL)                              
     #killQuery
     ;
 
-supportedOtherStatement
+otherStatement
     : HELP mark=identifierOrText                                               
     #help
     | UNLOCK TABLES                                                            
     #unlockTables
     | INSTALL PLUGIN FROM source=identifierOrText properties=propertyClause?   
     #installPlugin
@@ -618,7 +744,7 @@ importColumnDesc
     | LEFT_PAREN name=identifier (EQ booleanExpression)? RIGHT_PAREN
     ;
 
-supportedRefreshStatement
+refreshStatement
     : REFRESH CATALOG name=identifier propertyClause?                          
     #refreshCatalog
     | REFRESH DATABASE name=multipartIdentifier propertyClause?                
     #refreshDatabase
     | REFRESH TABLE name=multipartIdentifier                                   
     #refreshTable
@@ -626,7 +752,7 @@ supportedRefreshStatement
     | REFRESH LDAP (ALL | (FOR user=identifierOrText))?                        
     #refreshLdap
     ;
 
-supportedCleanStatement
+cleanStatement
     : CLEAN ALL PROFILE                                                        
     #cleanAllProfile
     | CLEAN LABEL label=identifier? (FROM | IN) database=identifier            
     #cleanLabel
     | CLEAN QUERY STATS ((FOR database=identifier)
@@ -634,7 +760,7 @@ supportedCleanStatement
     | CLEAN ALL QUERY STATS                                                    
     #cleanAllQueryStats
     ;
 
-supportedCancelStatement
+cancelStatement
     : CANCEL LOAD ((FROM | IN) database=identifier)? wildWhere?                
     #cancelLoad
     | CANCEL EXPORT ((FROM | IN) database=identifier)? wildWhere?              
     #cancelExport
     | CANCEL WARM UP JOB wildWhere?                                            
     #cancelWarmUpJob
@@ -650,7 +776,7 @@ supportedCancelStatement
             (COMMA jobIds+=INTEGER_VALUE)* RIGHT_PAREN)?                       
     #cancelAlterTable
     ;
 
-supportedAdminStatement
+adminStatement
     : ADMIN SHOW REPLICA DISTRIBUTION FROM baseTableRef                        
     #adminShowReplicaDistribution
     | ADMIN REBALANCE DISK (ON LEFT_PAREN backends+=STRING_LITERAL
         (COMMA backends+=STRING_LITERAL)* RIGHT_PAREN)?                        
     #adminRebalanceDisk
@@ -690,7 +816,7 @@ roleMappingRuleClause
       RIGHT_PAREN
     ;
 
-supportedRecoverStatement
+recoverStatement
     : RECOVER DATABASE name=identifier id=INTEGER_VALUE? (AS 
alias=identifier)?     #recoverDatabase
     | RECOVER TABLE name=multipartIdentifier
         id=INTEGER_VALUE? (AS alias=identifier)?                               
     #recoverTable
@@ -708,13 +834,13 @@ wildWhere
     | WHERE expression
     ;
 
-supportedTransactionStatement
+transactionStatement
     : BEGIN (WITH LABEL identifier?)?                                          
     #transactionBegin
     | COMMIT WORK? (AND NO? CHAIN)? (NO? RELEASE)?                             
     #transcationCommit
     | ROLLBACK WORK? (AND NO? CHAIN)? (NO? RELEASE)?                           
     #transactionRollback
     ;
 
-supportedGrantRevokeStatement
+grantRevokeStatement
     : GRANT privilegeList ON multipartIdentifierOrAsterisk
         TO (userIdentify | ROLE identifierOrText)                              
             #grantTablePrivilege
     | GRANT privilegeList ON
@@ -882,7 +1008,7 @@ fromRollup
     : FROM rollup=identifier
     ;
 
-supportedStatsStatement
+showStatsStatement
     : SHOW AUTO? ANALYZE (jobId=INTEGER_VALUE | tableName=multipartIdentifier)?
         (WHERE (stateKey=identifier) EQ (stateValue=STRING_LITERAL))?          
 #showAnalyze
     | SHOW QUEUED ANALYZE JOBS tableName=multipartIdentifier?
@@ -892,25 +1018,37 @@ supportedStatsStatement
     | SHOW COLUMN CACHED? STATS tableName=multipartIdentifier
         columnList=identifierList? partitionSpec?                              
 #showColumnStats
     | SHOW ANALYZE TASK STATUS jobId=INTEGER_VALUE                             
 #showAnalyzeTask
-    | ANALYZE DATABASE name=multipartIdentifier
+    | SHOW INDEX STATS tableName=multipartIdentifier indexId=identifier        
 #showIndexStats
+    | SHOW TABLE STATS tableName=multipartIdentifier
+        partitionSpec? columnList=identifierList?                              
 #showTableStats
+    | SHOW TABLE STATS tableId=INTEGER_VALUE                                   
 #showTableStats
+    ;
+
+analyzeStatsStatement
+    : ANALYZE DATABASE name=multipartIdentifier
         (WITH analyzeProperties)* propertyClause?                              
 #analyzeDatabase
     | ANALYZE TABLE name=multipartIdentifier partitionSpec?
         columns=identifierList? (WITH analyzeProperties)* propertyClause?      
 #analyzeTable
-    | ALTER TABLE name=multipartIdentifier SET STATS
+    ;
+
+alterStatsStatement
+    : ALTER TABLE name=multipartIdentifier SET STATS
         LEFT_PAREN propertyItemList RIGHT_PAREN partitionSpec?                 
 #alterTableStats
     | ALTER TABLE name=multipartIdentifier (INDEX indexName=identifier)?
         MODIFY COLUMN columnName=identifier
         SET STATS LEFT_PAREN propertyItemList RIGHT_PAREN partitionSpec?       
 #alterColumnStats
-    | SHOW INDEX STATS tableName=multipartIdentifier indexId=identifier        
 #showIndexStats
-    | DROP STATS tableName=multipartIdentifier
+    ;
+
+dropStatsStatement
+    : DROP STATS tableName=multipartIdentifier
         columns=identifierList? partitionSpec?                                 
 #dropStats
     | DROP CACHED STATS tableName=multipartIdentifier                          
 #dropCachedStats
     | DROP EXPIRED STATS                                                       
 #dropExpiredStats
-    | KILL ANALYZE jobId=INTEGER_VALUE                                         
 #killAnalyzeJob
     | DROP ANALYZE JOB INTEGER_VALUE                                           
 #dropAnalyzeJob
-    | SHOW TABLE STATS tableName=multipartIdentifier
-        partitionSpec? columnList=identifierList?                              
 #showTableStats
-    | SHOW TABLE STATS tableId=INTEGER_VALUE                                   
 #showTableStats
+    ;
+
+killStatsStatement
+    : KILL ANALYZE jobId=INTEGER_VALUE                                         
 #killAnalyzeJob
     ;
 
 analyzeProperties
@@ -979,7 +1117,7 @@ dataTypeList
     : dataType (COMMA dataType)*
     ;
 
-supportedSetStatement
+setStatement
     : SET (optionWithType | optionWithoutType)
         (COMMA (optionWithType | optionWithoutType))*                   
#setOptions
     | SET identifier AS DEFAULT STORAGE VAULT                           
#setDefaultStorageVault
@@ -1021,12 +1159,12 @@ isolationLevel
     : ISOLATION LEVEL ((READ UNCOMMITTED) | (READ COMMITTED) | (REPEATABLE 
READ) | (SERIALIZABLE))
     ;
 
-supportedUnsetStatement
+unsetStatement
     : UNSET statementScope? VARIABLE (ALL | identifier)
     | UNSET DEFAULT STORAGE VAULT
     ;
 
-supportedUseStatement
+useStatement
      : SWITCH catalog=identifier                                               
         #switchCatalog
      | USE (catalog=identifier DOT)? database=identifier                       
         #useDatabase
      | USE ((catalog=identifier DOT)? database=identifier)? ATSIGN 
cluster=identifier   #useCloudCluster
@@ -1037,7 +1175,7 @@ stageAndPattern
         (LEFT_PAREN pattern=STRING_LITERAL RIGHT_PAREN)?
     ;
 
-supportedDescribeStatement
+describeStatement
     : explainCommand FUNCTION tvfName=identifier LEFT_PAREN
         (properties=propertyItemList)? RIGHT_PAREN tableAlias   
#describeTableValuedFunction
     | explainCommand multipartIdentifier ALL                    
#describeTableAll
diff --git 
a/fe/fe-sql-parser/src/test/java/org/apache/doris/sqlparser/StatementBaseDispatchTest.java
 
b/fe/fe-sql-parser/src/test/java/org/apache/doris/sqlparser/StatementBaseDispatchTest.java
new file mode 100644
index 00000000000..13eb8dd45f8
--- /dev/null
+++ 
b/fe/fe-sql-parser/src/test/java/org/apache/doris/sqlparser/StatementBaseDispatchTest.java
@@ -0,0 +1,160 @@
+// 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.MultiStatementsContext;
+import org.apache.doris.nereids.DorisParser.SingleStatementContext;
+import org.apache.doris.nereids.exceptions.ParseException;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.stream.Stream;
+
+class StatementBaseDispatchTest {
+    private final DorisSqlParser parser = new DorisSqlParser();
+
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("statementFamilies")
+    void parsesEveryStatementFamily(String family, String sql) {
+        assertParses(sql);
+    }
+
+    private static Stream<Arguments> statementFamilies() {
+        return Stream.of(
+                Arguments.of("query", "SELECT 1"),
+                Arguments.of("dml", "INSERT INTO t SELECT 1"),
+                Arguments.of("create", "CREATE DATABASE db"),
+                Arguments.of("alter", "ALTER TABLE t RENAME t2"),
+                Arguments.of("materialized view", "CREATE MATERIALIZED VIEW mv 
AS SELECT 1"),
+                Arguments.of("job", "PAUSE JOB WHERE jobName = 'job'"),
+                Arguments.of("constraint", "SHOW CONSTRAINTS FROM t"),
+                Arguments.of("clean", "CLEAN ALL PROFILE"),
+                Arguments.of("describe", "DESCRIBE t"),
+                Arguments.of("drop", "DROP TABLE t"),
+                Arguments.of("set", "SET x = 1"),
+                Arguments.of("unset", "UNSET VARIABLE x"),
+                Arguments.of("refresh", "REFRESH TABLE t"),
+                Arguments.of("show", "SHOW TABLES"),
+                Arguments.of("load", "SYNC"),
+                Arguments.of("cancel", "CANCEL LOAD"),
+                Arguments.of("recover", "RECOVER TABLE t"),
+                Arguments.of("admin", "ADMIN CLEAN TRASH"),
+                Arguments.of("use", "USE db"),
+                Arguments.of("other", "HELP 'SHOW'"),
+                Arguments.of("kill", "KILL 1"),
+                Arguments.of("stats", "ANALYZE TABLE t"),
+                Arguments.of("transaction", "BEGIN"),
+                Arguments.of("grant/revoke", "GRANT ALL ON db.t TO 
'user'@'%'"));
+    }
+
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("sharedPrefixStatements")
+    void parsesSharedFirstTokenStatements(String branch, String sql) {
+        assertParses(sql);
+    }
+
+    private static Stream<Arguments> sharedPrefixStatements() {
+        return Stream.of(
+                Arguments.of("CREATE ddl", "CREATE DATABASE db"),
+                Arguments.of("CREATE mv", "CREATE MATERIALIZED VIEW mv AS 
SELECT 1"),
+                Arguments.of("CREATE job",
+                        "CREATE JOB db.job ON SCHEDULE AT '2026-01-01 
00:00:00' DO INSERT INTO t SELECT 1"),
+                Arguments.of("CREATE load",
+                        "CREATE ROUTINE LOAD db.job ON t PROPERTIES 
(\"max_batch_interval\" = \"10\") "
+                                + "FROM KAFKA (\"kafka_broker_list\" = 
\"localhost:9092\")"),
+                Arguments.of("ALTER ddl", "ALTER TABLE t RENAME t2"),
+                Arguments.of("ALTER mv", "ALTER MATERIALIZED VIEW mv RENAME 
mv2"),
+                Arguments.of("ALTER job", "ALTER JOB db.job PROPERTIES (\"k\" 
= \"v\")"),
+                Arguments.of("ALTER constraint", "ALTER TABLE t ADD CONSTRAINT 
pk PRIMARY KEY (id)"),
+                Arguments.of("ALTER stats", "ALTER TABLE t SET STATS 
(\"row_count\" = \"1\")"),
+                Arguments.of("SHOW command", "SHOW TABLES"),
+                Arguments.of("SHOW mv", "SHOW CREATE MATERIALIZED VIEW mv"),
+                Arguments.of("SHOW constraint", "SHOW CONSTRAINTS FROM t"),
+                Arguments.of("SHOW load", "SHOW ROUTINE LOAD"),
+                Arguments.of("SHOW stats", "SHOW ANALYZE"),
+                Arguments.of("DROP ddl", "DROP TABLE t"),
+                Arguments.of("DROP mv", "DROP MATERIALIZED VIEW mv"),
+                Arguments.of("DROP job", "DROP JOB WHERE jobName = 'job'"),
+                Arguments.of("DROP stats", "DROP STATS t"),
+                Arguments.of("PAUSE mv", "PAUSE MATERIALIZED VIEW JOB ON mv"),
+                Arguments.of("PAUSE job", "PAUSE JOB WHERE jobName = 'job'"),
+                Arguments.of("PAUSE load", "PAUSE ROUTINE LOAD FOR db.job"),
+                Arguments.of("RESUME mv", "RESUME MATERIALIZED VIEW JOB ON 
mv"),
+                Arguments.of("RESUME job", "RESUME JOB WHERE jobName = 'job'"),
+                Arguments.of("RESUME load", "RESUME ROUTINE LOAD FOR db.job"),
+                Arguments.of("CANCEL mv", "CANCEL MATERIALIZED VIEW TASK 1 ON 
mv"),
+                Arguments.of("CANCEL job", "CANCEL TASK WHERE jobName = 'job' 
AND taskId = 1"),
+                Arguments.of("CANCEL command", "CANCEL LOAD"),
+                Arguments.of("REFRESH mv", "REFRESH MATERIALIZED VIEW mv 
COMPLETE"),
+                Arguments.of("REFRESH command", "REFRESH TABLE t"),
+                Arguments.of("KILL connection", "KILL 1"),
+                Arguments.of("KILL analyze", "KILL ANALYZE 1"));
+    }
+
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("delayedDispatchStatements")
+    void parsesStatementsWhoseBranchNeedsMoreLookahead(String branch, String 
sql) {
+        assertParses(sql);
+    }
+
+    private static Stream<Arguments> delayedDispatchStatements() {
+        return Stream.of(
+                Arguments.of("EXPLAIN query", "EXPLAIN SELECT 1"),
+                Arguments.of("EXPLAIN dml", "EXPLAIN INSERT INTO t SELECT 1"),
+                Arguments.of("WITH query", "WITH c AS (SELECT 1) SELECT * FROM 
c"),
+                Arguments.of("WITH dml", "WITH c AS (SELECT 1) INSERT INTO t 
SELECT * FROM c"),
+                Arguments.of("parenthesized query", "(SELECT 1)"),
+                Arguments.of("query outfile", "SELECT 1 INTO OUTFILE 
'file:///tmp/result'"),
+                Arguments.of("warm-up explain", "EXPLAIN WARM UP SELECT * FROM 
t"),
+                Arguments.of("describe", "DESC t"));
+    }
+
+    @ParameterizedTest(name = "truncated: {0}")
+    @MethodSource("truncatedStatements")
+    void rejectsTruncatedSharedPrefixesAtEndOfInput(String sql) {
+        ParseException exception = 
Assertions.assertThrows(ParseException.class, () -> parser.parseStatement(sql));
+        Assertions.assertTrue(exception.getMessage().contains("line 1, pos " + 
sql.length()), exception::getMessage);
+    }
+
+    private static Stream<String> truncatedStatements() {
+        return Stream.of("CREATE", "ALTER TABLE", "SHOW", "DROP", "ADMIN", 
"CANCEL", "REFRESH", "KILL");
+    }
+
+    @Test
+    void parsesMultiStatementsWithCommentsAndExtraSemicolons() {
+        String sql = "; /* leading */ SELECT 1;; CREATE DATABASE db; -- 
comment\n SHOW TABLES;;";
+        MultiStatementsContext context = parser.parseStatements(sql);
+        Assertions.assertEquals(3, context.statement().size());
+    }
+
+    @Test
+    void rejectsInvalidMiddleStatement() {
+        String sql = "SELECT 1; CREATE; SHOW TABLES";
+        ParseException exception = 
Assertions.assertThrows(ParseException.class, () -> 
parser.parseStatements(sql));
+        Assertions.assertTrue(exception.getMessage().contains("line 1, pos 
16"), exception::getMessage);
+    }
+
+    private void assertParses(String sql) {
+        SingleStatementContext context = parser.parseStatement(sql);
+        Assertions.assertNotNull(context.statement());
+    }
+}


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

Reply via email to