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共享

相关推荐
搞科研的小刘选手33 分钟前
【EI稳定】检索第六届大数据经济与信息化管理国际学术会议(BDEIM 2025)
大数据·人工智能·经济
一水鉴天2 小时前
整体设计 全面梳理复盘 之39 生态工具链 到顶级表征及其完全公理化
大数据·人工智能·算法
草原印象3 小时前
全文检索ElasticSearch实战
大数据·elasticsearch·全文检索
Guheyunyi4 小时前
安防监控系统,如何为你的生活构筑智慧安全屏障?
大数据·人工智能·安全·信息可视化·生活
TDengine (老段)4 小时前
TDengine 字符串函数 Replace 用户手册
java·大数据·数据库·物联网·时序数据库·tdengine·涛思数据
武子康4 小时前
大数据-156 Apache Druid+Kafka 实时分析实战:JSON 拉平摄取与 SQL 指标全流程
大数据·后端·nosql
邮专薛之谦4 小时前
Git复习(查询版本)
大数据·elasticsearch·搜索引擎
悟能不能悟5 小时前
部署和测试 apereo/cas
大数据
小二·6 小时前
Git 高频操作命令大全(分类整理 + 修正说明)
大数据·git·elasticsearch
chatexcel6 小时前
ChatExcel亮相GTC2025全球流量大会
大数据·人工智能