在StarRocks中,用户提交的SQL查询文本在FE需要经过一系列处理,最终生成分布式执行计划并分发到各个Backend(BE)节点进行计算。核心流程包括以下五个步骤:
- Parser解析:将SQL文本解析为抽象语法树AST,表示其语法结构。
- Analyze分析:对AST进行语义分析,验证表、列是否存在,检查语法合法性等。
- Logical Plan生成:将AST转换为逻辑执行计划Logical Plan,即用Operator描述,描述查询的逻辑操作。
- 规则重写:基于一系列优化规则(如谓词下推、列裁剪)重写Logical Plan,提升执行效率。
- Optimizer优化:通过代价模型选择最优的物理执行计划Physical Plan,并分发到BE节点执行。
本文聚焦于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插件查看)
简单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 插件,然后就可以使用该文件自动生成代码
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;}@Overridepublic 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<>();}@Overridepublic 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> {@Overridepublic ParseNode visitSingleStatement(SimpleSQLParser.SingleStatementContext ctx) {return visit(ctx.selectStatement());}@Overridepublic ParseNode visitSelect(SimpleSQLParser.SelectContext ctx) {SelectList selectList = (SelectList) visit(ctx.selectList());String tableName = ctx.tableName().identifier().ID().getText();return new SelectStmt(selectList, tableName);}@Overridepublic ParseNode visitAllColumns(SimpleSQLParser.AllColumnsContext ctx) {return new SelectList(true, null);}@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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 转换为 ASTASTBuilder 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共享