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

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 基于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

三、优化问题


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

目录
相关文章
|
5天前
|
Python
关于 Python 列表解析式的作用域问题
关于 Python 列表解析式的作用域问题
31 11
|
3天前
|
数据可视化 数据挖掘 API
Python中的数据可视化利器:Matplotlib与Seaborn对比解析
在Python数据科学领域,数据可视化是一个重要环节。它不仅帮助我们理解数据,更能够让我们洞察数据背后的故事。本文将深入探讨两种广泛使用的数据可视化库——Matplotlib与Seaborn,通过对比它们的特点、优劣势以及适用场景,为读者提供一个清晰的选择指南。无论是初学者还是有经验的开发者,都能从中找到有价值的信息,提升自己的数据可视化技能。
|
5天前
|
Rust Python
Python 解析 toml 配置文件
Python 解析 toml 配置文件
12 1
|
5天前
|
Python
Python 解析 yaml 配置文件
Python 解析 yaml 配置文件
11 0
|
1天前
|
机器学习/深度学习 人工智能 数据可视化
Python比较适合哪些场景的编程?
Python比较适合哪些场景的编程?
14 7
|
6天前
|
数据挖掘 索引 Python
Python数据挖掘编程基础3
字典在数学上是一个映射,类似列表但使用自定义键而非数字索引,键在整个字典中必须唯一。可以通过直接赋值、`dict`函数或`dict.fromkeys`创建字典,并通过键访问元素。集合是一种不重复且无序的数据结构,可通过花括号或`set`函数创建,支持并集、交集、差集和对称差集等运算。
15 9
|
2天前
|
存储 数据处理 开发者
深入浅出:Python编程基础与实战技巧
【9月更文挑战第32天】本文将引导读者从零开始,掌握Python编程语言的核心概念,并通过实际代码示例深入理解。我们将逐步探索变量、数据结构、控制流、函数、类和异常处理等基本知识,并结合实用案例,如数据处理、文件操作和网络请求,提升编程技能。无论您是初学者还是有一定经验的开发者,这篇文章都能帮助您巩固基础,拓展视野。
|
1天前
|
大数据 Python
Python 高级编程:深入探索高级代码实践
本文深入探讨了Python的四大高级特性:装饰器、生成器、上下文管理器及并发与并行编程。通过装饰器,我们能够在不改动原函数的基础上增添功能;生成器允许按需生成值,优化处理大数据;上下文管理器确保资源被妥善管理和释放;多线程等技术则助力高效完成并发任务。本文通过具体代码实例详细解析这些特性的应用方法,帮助读者提升Python编程水平。
18 5
|
2天前
|
数据采集 机器学习/深度学习 人工智能
Python编程之旅:从基础到精通
【9月更文挑战第32天】本文将带你进入Python的世界,从基础语法到高级特性,再到实战项目,让你全面掌握Python编程技能。无论你是初学者还是有一定基础的开发者,都能在这篇文章中找到适合自己的学习路径和方法。让我们一起踏上Python编程之旅,开启一段充满挑战和乐趣的学习历程吧!
|
5天前
|
存储 开发者 Python
探索Python编程的奥秘
【9月更文挑战第29天】本文将带你走进Python的世界,通过深入浅出的方式,解析Python编程的基本概念和核心特性。我们将一起探讨变量、数据类型、控制结构、函数等基础知识,并通过实际代码示例,让你更好地理解和掌握Python编程。无论你是编程新手,还是有一定基础的开发者,都能在这篇文章中找到新的启示和收获。让我们一起探索Python编程的奥秘,开启编程之旅吧!

推荐镜像

更多
下一篇
无影云桌面