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

相关推荐
ALLSectorSorft43 分钟前
相亲小程序用户注册与登录系统模块搭建
java·大数据·服务器·数据库·python
让头发掉下来1 小时前
Sqoop详细学习文档
大数据·hive·hadoop·hbase·sqoop
一直在努力的小宁3 小时前
Diffuse and Disperse: Image Generation with Representation Regularization
大数据·人工智能·计算机视觉·diffuse
宸津-代码粉碎机8 小时前
LLM 模型部署难题的技术突破:从轻量化到分布式推理的全栈解决方案
java·大数据·人工智能·分布式·python
NeRF_er14 小时前
STORM代码阅读笔记
大数据·笔记·storm
TDengine (老段)18 小时前
TDengine 中 TDgp 中添加机器学习模型
大数据·数据库·算法·机器学习·数据分析·时序数据库·tdengine
希艾席帝恩20 小时前
拥抱智慧物流时代:数字孪生技术的应用与前景
大数据·人工智能·低代码·数字化转型·业务系统
Bar_artist20 小时前
离线智能破局,架构创新突围:RockAI与中国AI的“另一条车道”
大数据·人工智能
牛客企业服务21 小时前
2025校招AI应用:校园招聘的革新与挑战
大数据·人工智能·机器学习·面试·职场和发展·求职招聘·语音识别