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

相关推荐
亚远景aspice1 小时前
亚远景热烈祝贺保隆科技通过ASPICE CL2评估
大数据·人工智能·物联网
赵谨言2 小时前
基于python大数据的城市扬尘数宇化监控系统的设计与开发
大数据·开发语言·经验分享·python
程序员小羊!2 小时前
Flink状态编程之算子状态(OperatorState)
大数据·flink
TaoSense3 小时前
Milvus向量数据库介绍
大数据·人工智能
智海观潮3 小时前
聊聊Spark的分区
java·大数据·spark
洛克大航海3 小时前
集群环境安装与部署 Hadoop
大数据·hadoop·ubuntu·集群部署 hadoop
EasyCVR4 小时前
赋能智慧水利:视频汇聚平台EasyCVR智慧水利工程视频管理系统解决方案
大数据
程序员洲洲5 小时前
使用亮数据爬虫API一键式爬取Facebook数据
大数据·数据·亮数据·bright data·爬虫api
汽车仪器仪表相关领域5 小时前
工业商业安全 “哨兵”:GT-NHVR-20-A1 点型可燃气体探测器实操解析与场景适配
大数据·人工智能·功能测试·安全·安全性测试
ctrigger6 小时前
电子信息三胞胎:电子信息工程、电子科学技术、电子信息科学技术
大数据·注册电气工程师