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

目标

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

  1. token的大小写问题, Hive里select 和SELECT都能接受
  2. 分号问题,也就是必须能解析一个字符串里包含多个sql语句的情况
  3. 解析规则,类似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 · 访问量 661

猜你喜欢

转载自blog.csdn.net/bigdataolddriver/article/details/103935723
今日推荐