基于antlr-3.5.2+Python实现一般HiveSQL血缘解析(二)

简介: 基于antlr-3.5.2+Python实现一般HiveSQL血缘解析(二)

前言


作为一个开发实践项目,实现对HiveSQL语句的解析可以很有效的作为管理用户查询权限的管理。对于这方面的知识本身也不是非常熟悉了解,很多时候也是边学边记。身边也没有人指导这个方案具体该怎么实现怎么做,只有需求是要将复杂查询或者是关联建表的SQL语句能够将其库名和表名全都给提取出来并且能够实现上下游的追溯。这个功能最好还是用JAVA或者Scala写,毕竟Hive的底层还是JAVA写的方便,但是初步改写的话我常用语言还是python,java还是有点生疏的。因此本人是打算使用python来进行sql的血缘解析的,在网上搜索一番后发现有个系列正好是关于此方法的:系列:用python+antlr解析hive sql获得数据血缘关系。


该方法我也完全实现了但是只能对一般的sql进行解析,对于那些出现过复杂SQL关联或者是二次建表语句就会报错,无奈只能先搁置该方法一段时间,因为涉及到HiveSQL本身的grammar文件的改写,这方面确实实在难以下手而且本人对JAVA源码改写完全达不到水准...只能先记录下这次尝试,望以后有机会再来改进!


文章紧接上篇内容。


一.parser grammar改写


前面python测试代码里,parser.statement() 这个调用是固定的,HiveParser.g里对应的规则片段是这样的:

// starting rule          
statement                 
    : explainStatement EOF
    | execStatement EOF   
    ;      

那个starting rule是hive自身的注释。antlr的语法规则被执行时也有一个入口,就和c语言的main函数,java类里的main方法类似。


在一大堆已有的语法规则里,找到入口规则还是比较简单的,因为这个入口有个硬性要求,它必须能处理完整输入,规则匹配条件里就必须带有EOF这个特定的Token,它表示输入的结束(End Of File)。


找到入口规则后,python里的调用语句就知道怎么写了,不过因为java是个强类型语言,python的变量不能随便传,必须符合java的类型系统才行,所以整个调用链条有一个长长的倒推过程:


parser需要有个入口 => 通过EOF找到parser.statement()


Parser实例需要有个初始化函数 => 初始化函数的签名可以通过Parser.javaconstructor这个列表查阅到


Parser是用autoclass自动查找java类名后,映射到python里的"类定义对象",在python里用type检查它的话,类型是JavaClass。


在jinus的文档里找到JavaClass里有__javaconstructor__这个属性

__javaconstructor__的属性是list of tuples, 每个tuple对应一种可接受的构造函数签名,tuple的第1个还是tuple,代表需要传入的参数类型,tuple的的第2个是返回值类型,因为java的构造函数不需要返回值,那里固定是false


这个Parser的构造函数其实也可以通过查阅antlr的说明文档得到,但隐藏得挺深,还有v2 v3 v4的版本区别,不如使用jnius反射回的结果直接。


Parser构造函数签名[('(Lorg/antlr/runtime/TokenStream;)V', False), ('(Lorg/antlr/runtime/TokenStream;Lorg/antlr/runtime/RecognizerSharedState;)V', False)]需要符合TokenStream的这个接口作为输入,在antlr里的文档里查到CommonTokenStream这个类


CommonTokenStream这个类的构造函数需要TokenSource接口,构造函数签名[('()V', False), ('(Lorg/antlr/runtime/TokenSource;I)V', False), ('(Lorg/antlr/runtime/TokenSource;)V', False)]正是前面得到的HiveLexer类实现的接口


HiveLexer类的构造函数需要CharStream接口,构造函数签名[('(Lorg/antlr/runtime/CharStream;)V', False), ('(Lorg/antlr/runtime/CharStream;Lorg/antlr/runtime/RecognizerSharedState;)V', False), ('()V', False)]查antlr的文档知道有StringStream和FileStream两个现成的类,理论上也可以自行用java实现自定义的CharStream


StringStream的构造函数签名`[('()V', False), ('(Ljava/lang/String;)V', False), ('([CI)V', False)]`,这里的的java.lang.String已经是jnius能自动映射到python 内置类型str的类了


构造sql字符串,完成调用链。


要实用,从解决上面的简单测试里就发现的3个问题开始


- token的大小写问题, Hive里select 和SELECT都能接受

- 分号问题,也就是必须能解析一个字符串里包含多个sql语句的情况

- 解析规则,类似insert-select这种hive里接受,但HiveParser.g文件里没有定义的情况


二、得到AST


上一篇的代码其实已经走到了临门一脚。作为解析入口的parser.statement()这个方法是有返回值的,默认生成的返回类型是自动生成的一个类, HiveParser.statement_return, AST 就藏在这个类里,可以通过这个类的getTree(),得到一个类型为CommonTree 的对象。用python代码拿到这个CommonTree的代码如下

import jnius_config
jnius_config.set_classpath('./','./grammar/hive310/antlr-3.5.2-complete.jar')
import jnius
StringStream = jnius.autoclass('grammar.hive310.ANTLRNoCaseStringStream')
Lexer  = jnius.autoclass('grammar.hive310.HiveLexer')
Parser  = jnius.autoclass('grammar.hive310.HiveParser')
TokenStream  = jnius.autoclass('org.antlr.runtime.CommonTokenStream')
sql_string = (
    "SELECT  hour(from_unixtime(cast(gpstime/1000 as BIGINT),'yyyy-MM-dd HH:mm:ss')),dt "
    "from track_point_traffic_dev.tk_track_point_attach_road_info "
    "where admincode ='110105'"
    "and dt BETWEEN '2022-07-28' and '2022-08-04'"
    "limit 1000000"
    )
sqlstream = StringStream(sql_string)
inst = Lexer(sqlstream)
ts = TokenStream(inst)
parser = Parser(ts)
ret  = parser.statements()
treeroot = ret.getTree()
lex=[]
def walktree(node,depth = 0):
    print("%s%s=%s" % ("  "*depth,node.getText(),node.getType()))
    if(node.getType()==24):
        lex.append(node.getText())
    children = node.children
    if not children:
        return
    ch_size = children.size()
    for i in range(ch_size):
        ch =children.get(i)
        walktree(ch,depth + 1)
def get_table(treeroot,depth=0):
    children = treeroot.children
    ch_size = children.size()
    ch = children.get(1)
walktree(treeroot,0)
print(lex)

遍历AST需要先查阅一下CommonTree这个类的[API文档](https://www.antlr3.org/api/Java/org/antlr/runtime/tree/CommonTree.html) ,AST的每个节点都是一个CommonTree这个类的实例,有token这个Field可以访问节点本身代表的token,有getType和getText这样的方法可以直接访问token上的属性,节点的子节点可以访问children这个Field,也可以通过getChildren方法得到,也有相应的parent和getParent。有了这些,在整个AST树上就可以随意游走了。


children的java类型是java.util.List, 不能直接在python里做iteration,代码里通过for循环访问下标做访问。上面的代码输出结果为

7f0b4717883f443ca237b19bac6f16a1.png

三、优化问题


这里其实还有很多问题需要处理,但是有二个问题很容易想到分号和大小写。本篇文章已经够多内容了,放到下篇再讲。

目录
相关文章
|
6天前
|
存储 大数据 Unix
Python生成器 vs 迭代器:从内存到代码的深度解析
在Python中,处理大数据或无限序列时,迭代器与生成器可避免内存溢出。迭代器通过`__iter__`和`__next__`手动实现,控制灵活;生成器用`yield`自动实现,代码简洁、内存高效。生成器适合大文件读取、惰性计算等场景,是性能优化的关键工具。
74 2
|
12天前
|
机器学习/深度学习 文字识别 Java
Python实现PDF图片OCR识别:从原理到实战的全流程解析
本文详解2025年Python实现扫描PDF文本提取的四大OCR方案(Tesseract、EasyOCR、PaddleOCR、OCRmyPDF),涵盖环境配置、图像预处理、核心识别与性能优化,结合财务票据、古籍数字化等实战场景,助力高效构建自动化文档处理系统。
173 0
|
12天前
|
机器学习/深度学习 JSON Java
Java调用Python的5种实用方案:从简单到进阶的全场景解析
在机器学习与大数据融合背景下,Java与Python协同开发成为企业常见需求。本文通过真实案例解析5种主流调用方案,涵盖脚本调用到微服务架构,助力开发者根据业务场景选择最优方案,提升开发效率与系统性能。
144 0
机器学习/深度学习 算法 自动驾驶
116 0
|
20天前
|
算法 安全 数据安全/隐私保护
Python随机数函数全解析:5个核心工具的实战指南
Python的random模块不仅包含基础的随机数生成函数,还提供了如randint()、choice()、shuffle()和sample()等实用工具,适用于游戏开发、密码学、统计模拟等多个领域。本文深入解析这些函数的用法、底层原理及最佳实践,帮助开发者高效利用随机数,提升代码质量与安全性。
98 0
|
25天前
|
数据可视化 Linux iOS开发
Python脚本转EXE文件实战指南:从原理到操作全解析
本教程详解如何将Python脚本打包为EXE文件,涵盖PyInstaller、auto-py-to-exe和cx_Freeze三种工具,包含实战案例与常见问题解决方案,助你轻松发布独立运行的Python程序。
317 2
|
27天前
|
设计模式 缓存 运维
Python装饰器实战场景解析:从原理到应用的10个经典案例
Python装饰器是函数式编程的精华,通过10个实战场景,从日志记录、权限验证到插件系统,全面解析其应用。掌握装饰器,让代码更优雅、灵活,提升开发效率。
88 0
|
XML JavaScript 关系型数据库
Python XML 解析
Python XML 解析
|
XML JavaScript API
Python XML 解析
Python XML 解析
104 0
|
XML JavaScript API
「Python系列」Python XML解析
在Python中,解析XML文件通常使用内置的`xml.etree.ElementTree`模块,它提供了一个轻量级、高效的方式来解析XML文档。此外,还有其他的第三方库,如`lxml`和`xml.dom`,它们提供了更多的功能和灵活性。
155 0

推荐镜像

更多