基于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

三、优化问题


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

目录
相关文章
|
10天前
|
测试技术 开发者 Python
深入浅出:Python中的装饰器解析与应用###
【10月更文挑战第22天】 本文将带你走进Python装饰器的世界,揭示其背后的魔法。我们将一起探索装饰器的定义、工作原理、常见用法以及如何自定义装饰器,让你的代码更加简洁高效。无论你是Python新手还是有一定经验的开发者,相信这篇文章都能为你带来新的启发和收获。 ###
9 1
|
10天前
|
设计模式 测试技术 开发者
Python中的装饰器深度解析
【10月更文挑战第24天】在Python的世界中,装饰器是那些能够为函数或类“添彩”的魔法工具。本文将带你深入理解装饰器的概念、工作原理以及如何自定义装饰器,让你的代码更加优雅和高效。
|
20天前
|
XML 前端开发 数据格式
Beautiful Soup 解析html | python小知识
在数据驱动的时代,网页数据是非常宝贵的资源。很多时候我们需要从网页上提取数据,进行分析和处理。Beautiful Soup 是一个非常流行的 Python 库,可以帮助我们轻松地解析和提取网页中的数据。本文将详细介绍 Beautiful Soup 的基础知识和常用操作,帮助初学者快速入门和精通这一强大的工具。【10月更文挑战第11天】
51 2
|
19天前
|
数据安全/隐私保护 流计算 开发者
python知识点100篇系列(18)-解析m3u8文件的下载视频
【10月更文挑战第6天】m3u8是苹果公司推出的一种视频播放标准,采用UTF-8编码,主要用于记录视频的网络地址。HLS(Http Live Streaming)是苹果公司提出的一种基于HTTP的流媒体传输协议,通过m3u8索引文件按序访问ts文件,实现音视频播放。本文介绍了如何通过浏览器找到m3u8文件,解析m3u8文件获取ts文件地址,下载ts文件并解密(如有必要),最后使用ffmpeg合并ts文件为mp4文件。
|
5月前
|
XML JavaScript 关系型数据库
Python XML 解析
Python XML 解析
|
6月前
|
XML JavaScript API
Python XML 解析
Python XML 解析
|
XML JavaScript 关系型数据库
|
6月前
|
XML JavaScript API
「Python系列」Python XML解析
在Python中,解析XML文件通常使用内置的`xml.etree.ElementTree`模块,它提供了一个轻量级、高效的方式来解析XML文档。此外,还有其他的第三方库,如`lxml`和`xml.dom`,它们提供了更多的功能和灵活性。
64 0
|
6月前
|
XML 安全 API
Python读写XML文件:深入解析与技术实现
Python读写XML文件:深入解析与技术实现
209 0
|
XML JSON 编解码