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

在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节点执行。

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

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

css 复制代码
SELECT a, sum(b) FROM tablex GROUP BY a ORDER BY a LIMIT 3;

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


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

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

sql 复制代码
SELECT xx FROM table_xx;

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

xml 复制代码
        <dependency>            <groupId>org.antlr</groupId>            <artifactId>antlr4</artifactId>            <version>4.11.1</version>        </dependency>

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

css 复制代码
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 插件,然后就可以使用该文件自动生成代码

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

arduino 复制代码
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

typescript 复制代码
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

java 复制代码
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.写个简单的测试用例

typescript 复制代码
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共享

相关推荐
B站计算机毕业设计之家4 小时前
智慧交通项目:Python+PySide6 车辆检测系统 YOLOv8+OpenCV 自定义视频 自定义检测区域 (源码+文档)✅
大数据·python·opencv·yolo·智慧交通·交通·车流量
数据与人工智能律师5 小时前
AI的法治迷宫:技术层、模型层、应用层的法律痛点
大数据·网络·人工智能·云计算·区块链
码上地球10 小时前
大数据成矿预测系列(三) | 从统计模型到机器学习:为何机器学习是成矿预测的新前沿?
大数据·机器学习·数据挖掘
Hello.Reader11 小时前
Flink 作业测试依赖、MiniCluster、DataStream 与 Table/SQL 上手
大数据·sql·flink
代码匠心12 小时前
从零开始学Flink:实时流处理实战
java·大数据·后端·flink
cxr82813 小时前
AI智能体赋能文化传承与创新领域:社群身份认同的数字空间重构与文化融合策略
大数据·人工智能·重构·提示词工程·ai赋能
IT研究室15 小时前
大数据毕业设计选题推荐-基于大数据的全球用水量数据可视化分析系统-大数据-Spark-Hadoop-Bigdata
大数据·信息可视化·课程设计
yueyuebaobaoxinx15 小时前
从工具到中枢:2025 年 AI 重构实体经济的实践图景
大数据·人工智能·重构
huluang15 小时前
基于AI驱动的项目重构与落地实施指南
大数据·人工智能·重构
zezexihaha15 小时前
生成式 AI 重构内容创作:从辅助工具到智能工厂
大数据·人工智能·重构