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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 基于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 大神修炼手册:图的深度优先&广度优先遍历,深入骨髓的解析
在 Python 编程中,掌握图的深度优先遍历(DFS)和广度优先遍历(BFS)是进阶的关键。这两种算法不仅理论重要,还能解决实际问题。本文介绍了图的基本概念、邻接表表示方法,并给出了 DFS 和 BFS 的 Python 实现代码示例,帮助读者深入理解并应用这些算法。
23 2
|
19天前
|
测试技术 开发者 Python
深入浅出:Python中的装饰器解析与应用###
【10月更文挑战第22天】 本文将带你走进Python装饰器的世界,揭示其背后的魔法。我们将一起探索装饰器的定义、工作原理、常见用法以及如何自定义装饰器,让你的代码更加简洁高效。无论你是Python新手还是有一定经验的开发者,相信这篇文章都能为你带来新的启发和收获。 ###
12 1
|
19天前
|
设计模式 测试技术 开发者
Python中的装饰器深度解析
【10月更文挑战第24天】在Python的世界中,装饰器是那些能够为函数或类“添彩”的魔法工具。本文将带你深入理解装饰器的概念、工作原理以及如何自定义装饰器,让你的代码更加优雅和高效。
|
28天前
|
数据安全/隐私保护 流计算 开发者
python知识点100篇系列(18)-解析m3u8文件的下载视频
【10月更文挑战第6天】m3u8是苹果公司推出的一种视频播放标准,采用UTF-8编码,主要用于记录视频的网络地址。HLS(Http Live Streaming)是苹果公司提出的一种基于HTTP的流媒体传输协议,通过m3u8索引文件按序访问ts文件,实现音视频播放。本文介绍了如何通过浏览器找到m3u8文件,解析m3u8文件获取ts文件地址,下载ts文件并解密(如有必要),最后使用ffmpeg合并ts文件为mp4文件。
|
3天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
16 2
|
1月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
67 0
|
1月前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
52 0
|
1月前
|
存储 Java C++
Collection-PriorityQueue源码解析
Collection-PriorityQueue源码解析
60 0
|
1月前
|
安全 Java 程序员
Collection-Stack&Queue源码解析
Collection-Stack&Queue源码解析
80 0
|
4天前
|
存储 安全 Linux
Golang的GMP调度模型与源码解析
【11月更文挑战第11天】GMP 调度模型是 Go 语言运行时系统的核心部分,用于高效管理和调度大量协程(goroutine)。它通过少量的操作系统线程(M)和逻辑处理器(P)来调度大量的轻量级协程(G),从而实现高性能的并发处理。GMP 模型通过本地队列和全局队列来减少锁竞争,提高调度效率。在 Go 源码中,`runtime.h` 文件定义了关键数据结构,`schedule()` 和 `findrunnable()` 函数实现了核心调度逻辑。通过深入研究 GMP 模型,可以更好地理解 Go 语言的并发机制。