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)示例 查询语句 (Query) SELECT子句 FROM子句 WHERE子句 字段: name 字段: age 表: users 条件: age > 18 说明:每个节点代表一个语法结构,血缘分析就是遍历这棵树,提取表名、字段名和过滤条件。

我的经验:在解析复杂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 解析器选型决策树

不知道怎么选?我画了个决策树,你照着走就行:

解析器选型决策树 需要解析SQL吗? 只解析一种SQL方言? 是 否 选 Antlr 选 Calcite Antlr 特点 ✅ 轻量、灵活、性能好 ⚠️ 需要自己写.g4语法文件 Calcite 特点 ✅ 多方言支持、功能丰富 ⚠️ 学习曲线较陡、略重 总结:单一方言用Antlr,多方言用Calcite,别纠结。

3.5 实战:从AST中提取血缘信息

不管用Antlr还是Calcite,最终目的都一样——从AST里提取表名和字段名。我以Calcite为例,展示一个简单的血缘提取流程:

  1. 解析SQL:调用 SqlParser.parseQuery() 得到 SqlNode。
  2. 遍历AST:使用 SqlBasicVisitor 或自定义visitor,访问每个节点。
  3. 提取表名:遇到 SqlSelect 节点,从 getFrom() 中提取表名。
  4. 提取字段名:从 getSelectList() 中提取字段列表。
  5. 处理子查询:递归遍历,处理嵌套SQL。

核心要点:血缘提取的本质就是AST遍历。你只要把AST的每个节点类型搞清楚,提取逻辑就是水到渠成的事。

嗯,到这里,SQL解析的原理和选型你应该心里有数了。Antlr和Calcite没有的好坏,关键看你的场景。我个人建议:如果团队小、SQL方言固定,Antlr上手快;如果要做企业级血缘平台,Calcite的生态更成熟。