在本文中,对 Antlr4 不再做详细介绍,仅借助 Spark Sql 做示例,通过 Antlr4 解析 SQL 解决项目中遇到的一些问题。
解析 SQL 获取对应的表名、字段。
解析 SQL 获取输入表、输出表,输入字段、输出字段。
基于 Antlr4,在解析 SQL 获取字段相关的信息时,以下两种情况是无法解析的:
SQL 中涉及到 * 的查询,诸如 select * from test_tab ,在此,单纯从语法解析角度而言无法得知*具体涉及到哪些字段。
语义歧义,如:select t1.col1 ,col2 from tab1 t1 left join tab2 t2 on t1.id=t2.id ,col2 来自于 tab2 表,由于在 tab1 表中不存在 col1 中,从最终执行来看该 SQL 不会报错,且查询结果正确,但单纯从 SQL 解析的角度而言,无法得知 col2 字段来源于 tab2 表。
诸如问题 1、2 产生的问题,当然也可以通过查询表元数据获取到具体字段信息,但是实际中有更好的解决办法,像 Spark Sql,在执行过程中,我们可以获取它的物理计划(SparkPlan),通过获取物理计划即可获取相应的字段信息。
注:该场景是曾经做某系统,业务代码涉及 SQL 拼接,需要判断当前查询是否为聚合函数,在 SQL 拼接中,普通字段条件过滤和聚合字段条件过滤是不一样的,普通字段条件过滤用 where,而聚合字段则用 having。
如 Spark2.4,语法文件在 /spark/sql/catalyst/src/main/antlr4/org/apache/spark/sql/catalyst/parser 路径,文件后缀为 g4。
<antlr4.version>4.10.1</antlr4.version>
<dependency>
<groupId>org.antlr</groupId>
<artifactId>antlr4-runtime</artifactId>
<version>${antlr4.version}</version>
</dependency>
<plugin>
<groupId>org.antlr</groupId>
<artifactId>antlr4-maven-plugin</artifactId>
<version>${antlr4.version}</version>
<executions>
<execution>
<goals>
<goal>antlr4</goal>
</goals>
<phase>none</phase>
</execution>
</executions>
<configuration>
<sourceDirectory>src/main/scala/cn/zcy/antlr4/spark/g4</sourceDirectory>
<includes>
<include>SparkSqlBaseLexer.g4</include>
<include>SparkSqlBaseParser.g4</include>
</includes>
<visitor>true</visitor>
<listener>true</listener>
</configuration>
</plugin>
(1)右键配置生成文件相关信息(2)生成文件
SparkSql.tokens:antlr4 给每个词法符号指定一个数字形式的类型,它们的对应关系存在该文件中 SparkSqlListener、SparkSqlBaseListener:语法分析器会将文本转换为一个语法树,遍历语法树的时候,会触发一系列的事件回调,SparkSqlListener 接口给出了这些回调方法的定义,SparkSqlBaseListener 则是 SparkSqlListener 的默认实现类。 SparkSqlVisitor、SparkSqlBaseVisitor:Visitor 模式的工具包。 SparkSqlLexer:词法分析器的类定义;语法中的文法规则和词法规则,主要靠这个识别。 SparkSqlParser:包含语法分析器类的定义。
SparkSqlListener 和 SparkSqlVisitor 的区别: Antlr 访问语法树有 Visitor 和 Listener 两种模式,Listener 模式中,每个节点包含 enter 和 exit 两个方法,会解析语法树的各个节点,Visitor 模式则是根据语法树遍历访问。
(1)从 SparkSql.g4 文件中可得知 statement 为根节点,通过测试规则,可以查看对应 SQL 的语法树。
以下仅对解析 SQL 中的表名做示例。
import cn.zhengcaiyun.idata.connector.parser.spark.SparkSqlLexer;
import cn.zhengcaiyun.idata.connector.parser.spark.SparkSqlParser;
import cn.zhengcaiyun.idata.connector.util.model.UpperCaseCharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.atn.PredictionMode;
public class SparkSqlHelper {
public static void main(String[] args) {
String sql = "SELECT T1.COL1,T2.COL2 \n" +
"FROM TAB1 T1 \n" +
"LEFT JOIN TAB2 T2 ON T1.ID=T2.ID";
System.out.println(SparkSqlHelper.parse(sql));
}
public static List<String> parse(String sql) {
UpperCaseCharStream charStream = new UpperCaseCharStream(CharStreams.fromString(sql));
//新建一个词法分析器,解析创建的流
SparkSqlLexer lexer = new SparkSqlLexer(charStream);
//新建一个词法符号的缓冲器,存储词法分析器生成的词法符号
CommonTokenStream tokenStream = new CommonTokenStream(lexer);
//新建语法解析器,处理词法符号缓冲器中的内容
SparkSqlParser parser = new SparkSqlParser(tokenStream);
//指定预测模式(1)
parser.getInterpreter().setPredictionMode(PredictionMode.SLL);
//创建解析器访问对应的方法
SparkSqlAst visitor = new SparkSqlAst();
return (List<String>) visitor.visit(parser.singleStatement());
}
}
(1)预测模式 SLL:在进行预测时会忽略当前解析器上下文,解析器要么返回正确的解析树( LL 预测模式返回的解析树),要么报告语法错误。 LL:在实际实现中一般先用 SSL 模式,若抛 ParseCancellationException,则使用 LL 模式再重试。这种模式在输入的语法组合正确的情况下,保证解析的结果正确;若语法模糊,则无法保证准确结果。 LL_EXACT_AMBIG_DETECTION:除了提供同 LL 模式一样的正确性保证外,为解析过程中遇到的每个歧义决策确定完整而准确的歧义备选项集;这种预测模式不为语法错误的输入的预测行为提供任何保证。
import cn.zcy.antlr4.spark.parser.SparkSqlBaseVisitor;
import cn.zcy.antlr4.spark.parser.SparkSqlParser;
public class SparkSqlAst extends SparkSqlBaseVisitor {
private List<String> tableList = new ArrayList<>();
@Override
public List<String> visitSingleStatement(SparkSqlParser.SingleStatementContext ctx) {
super.visitSingleStatement(ctx);
return tableList;
}
/**
* 通过对语法树的观察可得知,表名的获取可以通过该方法来获取
*
* @param ctx
* @return
*/
@Override
public List<String> visitTableIdentifier(SparkSqlParser.TableIdentifierContext ctx) {
String db = ctx.db == null ? "" : ctx.db.getText();
String tableName = ctx.table == null ? "" : ctx.table.getText();
tableList.add("".equals(db) ? tableName : (db + "." + tableName));
return null;
}
}
执行结果:
参考:《ANTLR4 权威指南》,特恩斯.帕尔 著,张博 译
招贤纳士
政采云技术团队(Zero),一个富有激情、创造力和执行力的团队,Base 在风景如画的杭州。团队现有500多名研发小伙伴,既有来自阿里、华为、网易的“老”兵,也有来自浙大、中科大、杭电等校的新人。团队在日常业务开发之外,还分别在云原生、区块链、人工智能、低代码平台、中间件、大数据、物料体系、工程平台、性能体验、可视化等领域进行技术探索和实践,推动并落地了一系列的内部技术产品,持续探索技术的新边界。此外,团队还纷纷投身社区建设,目前已经是 google flutter、scikit-learn、Apache Dubbo、Apache Rocketmq、Apache Pulsar、CNCF Dapr、Apache DolphinScheduler、alibaba Seata 等众多优秀开源社区的贡献者。
如果你想改变一直被事折腾,希望开始折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊……如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的技术团队的成长过程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 zcy-tc@cai-inc.com