ITPub博客

首页 > 大数据 > 数据挖掘 > StarRocks 查询探秘(一):SELECT语句的解析之旅

StarRocks 查询探秘(一):SELECT语句的解析之旅

原创 数据挖掘 作者:乐逍遥happy 时间:2025-08-02 16:46:18 0 删除 编辑

在 StarRocks中,用户提交的SQL查询文本在FE需要经过一系列处理,最终生成分布式执行计划并分发到各个Backend(BE)节点进行计算。核心流程包括以下五个步骤:

  1. Parser解析 :将SQL文本解析为抽象语法树AST,表示其语法结构。

  2. Analyze分析 :对AST进行语义分析,验证表、列是否存在,检查语法合法性等。

  3. Logical Plan生成 :将AST转换为逻辑执行计划Logical Plan,即用Operator描述,描述查询的逻辑操作。

  4. 规则重写 :基于一系列优化规则(如谓词下推、列裁剪)重写Logical Plan,提升执行效率。

  5. Optimizer优化 :通过代价模型选择最优的物理执行计划Physical Plan,并分发到BE节点执行。


image.png


本文聚焦于Parser解析阶段,以一个简单SQL语句为例,结合ANTLR4解析器,详细分析SQL文本如何转化为AST,并简要介绍StarRocks的实现方式。

SQL语句是一种结构化查询语言,其文本形式在计算机中通常通过 AST(抽象语法树)表示。AST是一种树形数据结构,能有效表达SQL的词法、语法及嵌套层次结构。例如,以下SQL语句

    SELECT a, sum(b) FROM tablex GROUP BY a ORDER BY a LIMIT 3;

    通过解析工具可生成对应的解析树(可使用ANTLR Preview插件查看)

    image.png


    简单SELECT语句案例:ANTLR4与SQL解析

    以下通过一个简单SQL语句, 展示ANTLR4解析流程。

      SELECT xx FROM table_xx;

      1.创建一个maven工程,增加以下依赖

                <dependency>            <groupId>org.antlr</groupId>            <artifactId>antlr4</artifactId>            <version>4.11.1</version>        </dependency>

        2.创建一个 SimpleSQL.g4 规则文件

          grammar SimpleSQL;
          
          
          // 语法规则 singleStatement     : selectStatement EOF     ;
          selectStatement     : SELECT selectList FROM tableName #select     ;
          selectList     : '*' #allColumns     | identifier (',' identifier)* #columnList     ;
          tableName     : identifier     ;
          identifier     : ID     ;
          // 词法规则 SELECT : 'SELECT' | 'select'; FROM : 'FROM' | 'from'; ID : [a-zA-Z_][a-zA-Z0-9_]*; WS : [ \t\r\n]+ -> skip;

          3.IDEA 安装 ANTLR 插件,然后就可以使用该文件自动生成代码

          image.png

          4.创建 Java 类表示 AST 节点,SelectStmt 用来描述 select 语句,用于存储解析后的 select SQL

            import java.util.ArrayList;
            import java.util.List;
            
            
            abstract class ParseNode {}
            class SelectStmt extends ParseNode {     private final SelectList selectList;     private final String tableName;
                public SelectStmt(SelectList selectList, String tableName) {         this.selectList = selectList;         this.tableName = tableName;     }
                @Override     public String toString() {         return "SELECT " + selectList + " FROM " + tableName;     } }
            class SelectList extends ParseNode {     private final boolean isAllColumns;     private final List<String> columns;
                public SelectList(boolean isAllColumns, List<String> columns) {         this.isAllColumns = isAllColumns;         this.columns = columns != null ? columns : new ArrayList<>();     }
                @Override     public String toString() {         return isAllColumns ? "*" : String.join(", ", columns);     } }
            5.创建 ASTBuilder 类,继承 ANTLR4 的 SimpleSQLBaseVisitor,将解析树转换为 AST,实现对应的visitXXX 方法,转成对应的 ParseNode
              import com.sql.parser.SimpleSQLBaseVisitor;
              import com.sql.parser.SimpleSQLParser;
              
              
              import java.util.ArrayList; import java.util.List;
              public class ASTBuilder extends SimpleSQLBaseVisitor<ParseNode> {     @Override     public ParseNode visitSingleStatement(SimpleSQLParser.SingleStatementContext ctx) {         return visit(ctx.selectStatement());     }
                  @Override     public ParseNode visitSelect(SimpleSQLParser.SelectContext ctx) {         SelectList selectList = (SelectList) visit(ctx.selectList());         String tableName = ctx.tableName().identifier().ID().getText();         return new SelectStmt(selectList, tableName);     }
                  @Override     public ParseNode visitAllColumns(SimpleSQLParser.AllColumnsContext ctx) {         return new SelectList(true, null);     }
                  @Override     public ParseNode visitColumnList(SimpleSQLParser.ColumnListContext ctx) {         List<String> columns = new ArrayList<>();         for (SimpleSQLParser.IdentifierContext idCtx : ctx.identifier()) {             columns.add(idCtx.ID().getText());         }         return new SelectList(false, columns);     } }
              6.实现自定义的 SqlParser 类,作为解析入口,调用 ANTLR4 的 Lexer 和 Parser,并使用 ASTBuilder 生成 AST
                import com.sql.parser.SimpleSQLLexer;
                import com.sql.parser.SimpleSQLParser;
                import org.antlr.v4.runtime.*;
                import org.antlr.v4.runtime.tree.ParseTree;
                
                
                public class SqlParser {     public static ParseNode parse(String sql) throws Exception {         CharStream input = CharStreams.fromString(sql);         SimpleSQLLexer lexer = new SimpleSQLLexer(input);
                        lexer.removeErrorListeners();         lexer.addErrorListener(new BaseErrorListener() {             @Override             public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol,                                     int line, int charPositionInLine, String msg, RecognitionException e) {                 throw new RuntimeException("Syntax error at line " + line + ":" + charPositionInLine + " " + msg);             }         });
                        CommonTokenStream tokens = new CommonTokenStream(lexer);         SimpleSQLParser parser = new SimpleSQLParser(tokens);
                        parser.removeErrorListeners();         parser.addErrorListener(new BaseErrorListener() {             @Override             public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol,                                     int line, int charPositionInLine, String msg, RecognitionException e) {                 throw new RuntimeException("Syntax error at line " + line + ":" + charPositionInLine + " " + msg);             }         });
                        ParseTree tree = parser.singleStatement();
                        // 使用 ASTBuilder 转换为 AST         ASTBuilder builder = new ASTBuilder();         return builder.visit(tree);     } }
                7.写个简单的测试用例
                  public class SqlTest {
                      public static void main(String[] args) {
                          String[] sqls = {
                                  "SELECT * FROM table_x",
                                  "SELECT a, b FROM table_x",
                                  "SELECT a, b FROM "
                          };
                  
                  
                          for (String sql : sqls) {             try {                 ParseNode ast = SqlParser.parse(sql);                 System.out.println("AST: " + ast);             } catch (Exception e) {                 System.out.println("Error: " + e.getMessage());             }         }     } }

                  输出结果如下,第三条sql解析语法错误

                  图片


                  StarRocks中的SQL解析

                  StarRocks基于ANTLR4对词法语法进行解析的逻辑也是一样,其核心组件包括:

                  • StarRocks.g4  和  StarRocksLex.g4 :定义词法和语法规则。

                  • ParseNode :接口类,所有AST节点都要实现该接口。

                  • SqlParser :解析入口,调用ANTLR4的Lexer和Parser。

                  • AstBuilder :将解析树转换为AST。


                  ParseNode,  是一个接口,所有具体的 AST 节点都继承或实现它

                  图片

                  SqlParser

                  图片
                  调用 StarRocksLexer,StarRocksParser进行词法,语法解析
                  图片

                  AstBuilder 处理单个 SQL 语句,递归访问子节点。AstBuilder  继承自AbstractParseTreeVisitor ,通过重写  visit  方法(如  visitCreateDbStatement 、 visitCreateTableStatement )遍历解析树,生成对应的  ParseNode  节点,通过 visitChildren 遍历子节点;

                  图片
                  最终构建一棵树,返回 ParseNode 的具体实现类实例。至此 Parser 解析阶段完成。
                  更多大数据干货,欢迎关注我的微信公众号—BigData共享


                  来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70046653/viewspace-3092640/ ,如需转载,请注明出处,否则将追究法律责任。

                  请登录后发表评论 登录
                  全部评论

                  注册时间:2025-06-05

                  • 博文量
                    18
                  • 访问量
                    17427