找回密码
 会员注册
查看: 20|回复: 0

基于Antlr4的Sql解析

[复制链接]

7

主题

0

回帖

22

积分

新手上路

积分
22
发表于 2024-10-13 08:56:09 | 显示全部楼层 |阅读模式
基于Antlr4的Sql解析 在实际业务中,涉及到 SQL 解析的场景非常多,常见的诸如 MySQL 之类的 SQL 解析有像阿里的 Druid 之类的工具可以解析,但目前各种技术栈推出、更新迅速,经常会遇到某些 SQL 用常规 SQL 解析工具无法解析的情况,此类场景,可自定义借助 Antlr4 实现解析。在本文中,对 Antlr4 不再做详细介绍,仅借助 Spark Sql 做示例,通过 Antlr4 解析 SQL 解决项目中遇到的一些问题。一、常见业务场景1、鉴权解析 SQL 获取对应的表名、字段。2、数据血缘解析 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),通过获取物理计划即可获取相应的字段信息。3、其他场景输入一个表达式,判断该表达式类型,是否为窗口函数或者聚合函数等。注:该场景是曾经做某系统,业务代码涉及 SQL 拼接,需要判断当前查询是否为聚合函数,在 SQL 拼接中,普通字段条件过滤和聚合字段条件过滤是不一样的,普通字段条件过滤用 where,而聚合字段则用 having。二、IDEA 中 Antlr4 的环境部署1、Antlr4 插件安装2、获取语法文件如 Spark2.4,语法文件在 /spark/sql/catalyst/src/main/antlr4/org/apache/spark/sql/catalyst/parser 路径,文件后缀为 g4。3、pom.xml 依赖引入4.10.1   org.antlr   antlr4-runtime   ${antlr4.version}    org.antlr   antlr4-maven-plugin   ${antlr4.version}                                                   antlr4                               none                                    src/main/scala/cn/zcy/antlr4/spark/g4                                    SparkSqlBaseLexer.g4                    SparkSqlBaseParser.g4                                true                true         4、生成源码文件(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、语法树查看(1)从 SparkSql.g4 文件中可得知 statement 为根节点,通过测试规则,可以查看对应 SQL 的语法树。2、代码解析以下仅对解析 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 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) 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 tableList = new ArrayList();        @Override    public List visitSingleStatement(SparkSqlParser.SingleStatementContext ctx) {        super.visitSingleStatement(ctx);        return tableList;    }    /**     * 通过对语法树的观察可得知,表名的获取可以通过该方法来获取     *     * @param ctx     * @return     */    @Override    public List 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 权威指南》,特恩斯.帕尔 著,张博 译招贤纳士团队一个富有激情、创造力和执行力的团队,Base 在风景如画的杭州。团队现有500多名研发小伙伴,既有来自阿里、华为、网易的“老”兵,也有来自浙大、中科大、杭电等校的新人。团队在日常业务开发之外,还分别在云原生、区块链、人工智能、低代码平台、中间件、大数据、物料体系、工程平台、性能体验、可视化等领域进行技术探索和实践,推动并落地了一系列的内部技术产品,持续探索技术的新边界。此外,团队还纷纷投身社区建设,目前已经是 google flutter、scikit-learn、Apache Dubbo、Apache Rocketmq、Apache Pulsar、CNCF Dapr、Apache DolphinScheduler、alibaba Seata 等众多优秀开源社区的贡献者。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-26 12:41 , Processed in 2.693576 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表