python解析sql字段血缘_系列:用python+antlr解析hive sql获得数据血缘关系(三)

目标

系列第二篇里利用了HiveParser.g里的pushMsg输出信息,但还没有得到AST(Abstract Syntax Tree抽象语法树 ),不够实用。除了得到AST之外,第二篇末尾还需要解决下面这3个实用性问题

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

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

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

本篇先说清楚如何解决得到AST的问题,然后解决insert-select的实用性

得到AST

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

import jnius_config

jnius_config.set_classpath('./','./grammar/hive110/antlr-3.4-complete.jar')

import jnius

StringStream = jnius.autoclass('org.antlr.runtime.ANTLRStringStream')

Lexer = jnius.autoclass('grammar.hive110.HiveLexer')

Parser = jnius.autoclass('grammar.hive110.HiveParser')

TokenStream = jnius.autoclass('org.antlr.runtime.CommonTokenStream')

sql_string = (

"SELECT DISTINCT a1.c1 AS c2,\n"

" a1.c3 AS c4,\n"

" '' c5\n"

" FROM db2.tb2 AS a1 ;\n"

)

sqlstream = StringStream(sql_string)

inst = Lexer(sqlstream)

ts = TokenStream(inst)

parser = Parser(ts)

ret = parser.statements()

treeroot = ret.getTree()

产生AST的配置

HiveParser.g里需要有选项配置,让antlr产生的代码输出为AST,选项的位置具体在这里

options

{

tokenVocab=HiveLexer;

output=AST;

ASTLabelType=CommonTree;

backtrack=false;

k=3;

}

遍历AST并且输出结构

遍历AST需要先查阅一下CommonTree这个类的API文档 ,

AST的每个节点都是一个CommonTree这个类的实例,有token这个Field可以访问节点本身代表的token,有getType和getText这样的方法可以直接访问token上的属性,节点的子节点可以访问children这个Field,也可以通过getChildren方法得到,也有相应的parent和getParent。有了这些,在整个AST树上就可以随意游走了。

下面是一段简单的递归代码,从根节点开始做深度遍历,并且打印每个节点的文本和对应的数值代码。 附加到前面的代码之后就可以运行

def walktree(node,depth = 0):

print("%s%s=%s" % (" "*depth,node.getText(),node.getType()))

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)

walktree(treeroot,0)

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

None=0

TOK_QUERY=777

TOK_FROM=681

TOK_TABREF=864

TOK_TABNAME=863

db2=26

tb2=26

a1=26

TOK_INSERT=707

TOK_DESTINATION=660

TOK_TAB=835

TOK_TABNAME=863

db1=26

tb1=26

TOK_SELECTDI=792

TOK_SELEXPR=793

.=17

TOK_TABLE_OR_COL=860

a1=26

c1=26

c2=26

TOK_SELEXPR=793

.=17

TOK_TABLE_OR_COL=860

a1=26

c3=26

c4=26

TOK_SELEXPR=793

''=302

c5=26

;=299

=-1

可以看到,我们感兴趣的表名、列名已经做为最底层的叶子节点,出现在了我们的输出内容里,它们的数字类型对应是26,Identifiers。

AST树根的数字类型固定是0,token为null,翻译到python里变成了None。

作为查询语句的标志,数字类型是777,TOK_QUERY。

这样距离我们的最终目标差距就清晰可见了。

解决insert-select的实用性

上一篇用来测试insert-select的代码为

INSERT OVERWRITE db1.tb1 SELECT DISTINCT a1.c1 c2, a1.c3 c4, '' c5 FROM db2.tb2 a1;

写上一篇时主要时间在试图读懂.g文件里insertClause后为什么没出现selectClause,没发现这个写法其实是有错误的,漏了一个TABLE关键字,正确写法是

INSERT OVERWRITE TABLE db1.tb1 SELECT DISTINCT a1.c1 c2, a1.c3 c4, '' c5 FROM db2.tb2 a1;

当然读懂.g文件的时间也没白花,解决了分号以及大小写实用性的问题,具体解决办法放到下一篇。

傲慢程序员

发布了18 篇原创文章 · 获赞 0 · 访问量 641

私信

关注

标签:a1,26,AST,python,jnius,hive,TOK,antlr,children

来源: https://blog.csdn.net/bigdataolddriver/article/details/103935723

你可能感兴趣的:(python解析sql字段血缘)