3、血缘解析引擎:SQL解析原理、AST抽象语法树、解析器选型(Antlr/Calcite)
做血缘分析,第一步就是读懂SQL。说白了,你得让机器理解“SELECT a FROM b WHERE c”到底在干什么。这活儿就是SQL解析干的。
我刚开始做数据治理时,觉得解析SQL不就是字符串匹配嘛。后来被一个嵌套子查询的血缘搞崩了,才老老实实回来啃解析原理。嗯,今天咱们就把这块硬骨头啃下来。
3.1 SQL解析到底在做什么?
SQL解析,就是把一段文本变成计算机能理解的结构化数据。整个过程分两步:
- 词法分析(Lexer):把SQL字符串拆成一个个“单词”,也就是。比如
SELECT、FROM、table_name、WHERE、=这些都是。 - 语法分析(Parser):根据SQL语法规则,把这些组装成一棵树——就是AST(抽象语法树)。
核心理解:词法分析是“分词”,语法分析是“造句”。没有这俩,血缘分析就是空中楼阁。
举个例子,一条简单的SQL:
SELECT name, age FROM users WHERE age > 18
经过词法分析,会变成:
SELECT(关键字)name(标识符),(逗号)age(标识符)FROM(关键字)users(标识符)WHERE(关键字)age(标识符)>(操作符)18(数字字面量)
然后语法分析器把这些组装成AST。AST长什么样?往下看。
3.2 AST抽象语法树:血缘分析的骨架
AST是一棵树,每个节点代表SQL中的一个语法结构。根节点通常是整个查询语句,子节点是SELECT子句、FROM子句、WHERE子句等等。
我习惯把AST比作“SQL的骨架”。你想想看,血缘分析要提取表名、字段名、过滤条件,这些信息全挂在AST的节点上。没有AST,你只能靠正则表达式瞎猜——那玩意儿在复杂SQL面前就是个弟弟。
上面那条SQL的AST大概长这样:
我的经验:在解析复杂SQL时,AST的深度可能达到十几层。我建议你先把AST打印出来看看结构,再写遍历逻辑。别一上来就写代码,容易翻车。
3.3 解析器选型:Antlr vs Calcite
市面上主流的SQL解析器就两个:Antlr和Calcite。我两个都用过,各有千秋。下面这张表帮你快速对比:
| 对比维度 | Antlr | Calcite |
|---|---|---|
| 本质 | 通用解析器生成器 | SQL优化器 + 解析框架 |
| SQL方言支持 | 需要自己写语法文件(.g4) | 内置MySQL、Spark、Hive等多种方言 |
| AST操作 | 生成ParseTree,需要自己遍历 | 提供SqlNode,自带visitor模式 |
| 学习曲线 | 中等(需要学.g4语法) | 较陡(概念多,但功能强) |
| 性能 | 快,轻量 | 中等,功能多所以略重 |
| 适用场景 | 自定义SQL解析、小团队快速开发 | 多方言支持、需要SQL优化/校验 |
3.3.1 Antlr:灵活但需要自己造轮子
Antlr是一个解析器生成器。你写一个.g4语法文件,它就能生成对应的词法分析器和语法分析器。说白了,你告诉它SQL长什么样,它帮你生成解析代码。
我在项目中用过Antlr解析Hive SQL。当时团队需要自定义一些血缘规则,Antlr的灵活性帮了大忙。但有个坑——你得自己写语法文件。如果SQL方言比较偏门,这个工作量不小。
一个简单的.g4片段:
grammar SimpleSQL;
query
: SELECT columnList FROM tableName (WHERE condition)? EOF
;
SELECT : 'SELECT' ;
FROM : 'FROM' ;
WHERE : 'WHERE' ;
columnList
: IDENTIFIER (',' IDENTIFIER)*
;
tableName
: IDENTIFIER
;
condition
: IDENTIFIER '>' NUMBER
;
IDENTIFIER : [a-zA-Z_][a-zA-Z0-9_]* ;
NUMBER : [0-9]+ ;
WS : [ \t\r\n]+ -> skip ;
避坑指南:我曾经在Antlr的.g4文件里漏掉了一个空格规则,结果解析出来的AST全是乱码。调试了整整一个下午才发现。记住:词法规则里的空格一定要skip掉,不然你会怀疑人生。
3.3.2 Calcite:开箱即用,但概念多
Calcite是Apache的一个SQL优化框架。它内置了多种SQL方言的解析器,你不需要写语法文件,直接调用API就能解析SQL。而且它返回的SqlNode结构清晰,自带visitor模式,遍历起来很方便。
我后来做跨数据源的血缘分析时,果断换了Calcite。因为它支持MySQL、Spark、Hive、Flink等多种方言,省去了我维护多个.g4文件的痛苦。
用Calcite解析SQL的代码示例:
import org.apache.calcite.sql.*;
import org.apache.calcite.sql.parser.*;
public class CalciteParserDemo {
public static void main(String[] args) throws Exception {
String sql = "SELECT name, age FROM users WHERE age > 18";
SqlParser parser = SqlParser.create(sql);
SqlNode sqlNode = parser.parseQuery();
// 遍历AST,提取表名和字段名
sqlNode.accept(new SqlBasicVisitor<Void>() {
@Override
public Void visit(SqlCall call) {
if (call instanceof SqlSelect) {
SqlSelect select = (SqlSelect) call;
System.out.println("表名: " + select.getFrom());
System.out.println("字段: " + select.getSelectList());
}
return super.visit(call);
}
});
}
}
我的建议:如果你只需要解析一种SQL方言,Antlr更轻量。如果你要处理多种方言(比如公司同时用Hive和Spark),直接上Calcite,省心。
3.4 解析器选型决策树
不知道怎么选?我画了个决策树,你照着走就行:
3.5 实战:从AST中提取血缘信息
不管用Antlr还是Calcite,最终目的都一样——从AST里提取表名和字段名。我以Calcite为例,展示一个简单的血缘提取流程:
- 解析SQL:调用
SqlParser.parseQuery()得到SqlNode。 - 遍历AST:使用
SqlBasicVisitor或自定义visitor,访问每个节点。 - 提取表名:遇到
SqlSelect节点,从getFrom()中提取表名。 - 提取字段名:从
getSelectList()中提取字段列表。 - 处理子查询:递归遍历,处理嵌套SQL。
核心要点:血缘提取的本质就是AST遍历。你只要把AST的每个节点类型搞清楚,提取逻辑就是水到渠成的事。
嗯,到这里,SQL解析的原理和选型你应该心里有数了。Antlr和Calcite没有的好坏,关键看你的场景。我个人建议:如果团队小、SQL方言固定,Antlr上手快;如果要做企业级血缘平台,Calcite的生态更成熟。