系列:用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
<EOF>=-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