ICode9

精准搜索请尝试: 精确搜索
首页 > 数据库> 文章详细

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

2020-01-16 14:38:52  阅读:836  来源: 互联网

标签:语句 26 java TOK python HiveParser hive a1 antlr


目标

系列第三篇里做了基本的AST遍历。

在深入做SQL中的表名列名提取前,还需要先解决第三篇里遗留的两个实用性问题,分号和大小写

分号问题

分号问题的表现是自动生成的HiveParser.java代码,只能解析单个的语句,对包含多个语句的sql文本会报错,甚至连单个语句结尾多一个分号都不行。例如这种

SELECT DISTINCT a1.c1 c2, a1.c3 c4, '' c5  FROM db2.tb2 a1 ;

还有这种

SELECT DISTINCT a1.c1 c2, a1.c3 c4, '' c5  FROM db2.tb2 a1 ;
SELECT c6  FROM tb3 ;

原因

引发这个问题的地方很好找,就在HiveParser.g里面,具体就在statement()方法对应的这条RULE上。

// starting rule
statement
	: explainStatement EOF
	| execStatement EOF
	;

这段天书的符号体系和正则表达式很像,简单翻译成人间语言的意思是,HiveParser接受的输入都是statement,一个statement可以是一个explainStatement 加上EOF组成,也可以是execStatement加上EOF组成。explainStatement和execStatement都是有具体类型的单个语句

这个体验不符合使用hive的经验啊,最常用提交Hive语句的方式是通过hive client,它肯定是能处理多个语句的。要往下解决这个分号问题,有两个明显的方向可选

  1. 扩大使用hive源码的范围
  2. 修改HiveParser.g里的语法规则

但要怎么选呢?笔者在Hive源码里上下翻找了一通,再结合Hive实际的使用情形后有了推断。

  • HiveParser.g 的语法规则只被藏在Hive执行引擎深处的代码调用,调用时的输入已经被裁剪约束到只包含单个语句
  • 最常用提交Hive语句的方式是要通过hive client,它还需要被翻译到或者thrift,或者jdbc协议上的调用。
  • Hive client能处理的脚本里,会存在一部分conf设置、运行参数替换这样,需要在执行hive sql前预处理的语句

有了以上几个推断,结论也很显然,处理血缘关系的需求并不需要完备的hive client处理能力,而且裁剪hive源码的工作量不太可控,所以要选择2 ,修改HiveParser.g里的语法规则

如果对1还有兴趣,可以从研究源码同目录下的ParseDriver.java类出发。

规则修订

现在的规则名为statement, 一个语句后面就是EOF结束,作为写过正则的笔者,很自然的想法是,如果能让statement这个rule匹配上多次,分号分隔一下,是不是可以?试验过程略去不说,成功的结果如下

// starting rule
statements
    : statement (SEMICOLON statement )* SEMICOLON* EOF
    ;
statement
    : explainStatement | execStatement
    ;

把原先statement里这个EOF去掉,两行并做一行

然后增加一个statments这个复数名字的rule,SEMICOLON是从HiveLexer.g里的定义找出的,表示分号的写法。在.g文件里的括号()表示括号内的内容作为一个整体判断,*
的作用和正则表达式里类似,表示前面的这个整体出现0到无限次,合起来后的效果就是,一段文本里可以有多个语句,语句之间是单个分号分隔,但最末尾的语句后的分号可以省略

验证输出

测试sql语句如下

SELECT DISTINCT a1.c1 AS c2,
 a1.c3 AS c4,
 '' c5
 FROM db2.tb2 a1 ;
SELECT c6 FROM tb3

修改后要重新产生java代码和编译成class

java -jar antlr-3.4-complete.jar HiveLexer.g HiveParser.g

javac -cp antlr-3.4-complete.jar HiveLexer.java HiveParser*.java ParseError.java

继续用上一篇里写的遍历AST树脚本,脚本比较长,后面还有一个问题,就不重复贴了,只把输出部分复制如下

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
  TOK_QUERY=777
    TOK_FROM=681
      TOK_TABREF=864
        TOK_TABNAME=863
          tb3=26
    TOK_INSERT=707
      TOK_DESTINATION=660
        TOK_DIR=661
          TOK_TMP_FILE=873
      TOK_SELECT=791
        TOK_SELEXPR=793
          TOK_TABLE_OR_COL=860
            c6=26
  <EOF>=-1

可以看到没有报错,并且输出的树内容上,有两个TOK_QUERY节点,对应到两个select语句

大小写问题

大小写问题的表现是,自动生成的HiveParser.java里,会需要输入的sql文本内容里,关键字只能是大写的,小写的关键字会被识别为标识符,然后因为不符合语法规则解析失败。

和前面的分号问题了类似,也有两个选择

  1. 扩大使用hive源码的范围
  2. 修改HiveParser.g里的语法规则

这次的选择和分号问题不一样了,首先是antlr自身的文档上,在处理输入标识符的大小写问题上就有两个完全不同的做法。

  1. 对输入内容预处理,把所有的内容归一化成大写(或者小写)
  2. 在定义关键字时,单独做需要大小写无关(case-insensitive)的处理,如果所有的关键字都需要大小写无关,则所有的规则都要重新定义

第1点好理解,第2点可能略为晦涩,以select这个关键字为栗子说明。

在HiveLexer.g里,select的关键字是这么定义的

KW_SELECT : 'SELECT';

如果要做大小写无关处理,其中一种可行的写法是这样的

KW_SELECT : ('s'|'S')('e'|'E')('l'|'L')('e'|'E')('c'|'C')('t'|'T');

这个改动可行,就是动静有点大。而且很明显的事实是,Hive自己不是这么处理的,如果直接大规模去修改HiveLexer.g ,生成的新代码处理行为如何和Hive本身不一致,就费力不讨好了。所以这里适合对输入内容预处理,把所有的内容归一化成大写

归一化

归一化也还是有两种做法

  1. 扩大hive源码的使用范围,java里实现归一化。
  2. 在python里自己写代码实现归一化

出于尽量和Hive本身处理一直,而且改动不大的目的,选择了扩大hive源码的视野范围。

当然也是因为Hive源码里,有关归一化的代码很短小好处理的原因。

前一节里提到了ParseDriver.java,在里面定义了一个内部类,ANTLRNoCaseStringStream, 这个类起的作用就是处理输入字符流,把字符归一化到大写。把这部分代码抠出来,与前面的ParseError.java类似处理, ANTLRNoCaseStringStream.java的代码如下。

package grammar.hive110;
 import org.antlr.runtime.ANTLRStringStream;
 import org.antlr.runtime.CharStream;
 public class ANTLRNoCaseStringStream extends ANTLRStringStream {

   public ANTLRNoCaseStringStream(String input) {
     super(input);
   }

   @Override
   public int LA(int i) {

     int returnChar = super.LA(i);
     if (returnChar == CharStream.EOF) {
       return returnChar;
     } else if (returnChar == 0) {
       return returnChar;
     }

     return Character.toUpperCase((char) returnChar);
   }
 }

代码修订

因为增加了归一化的代码,需要再重新编译,树的生成代码也略有变化

编译时要增加一个输入文件

java -jar antlr-3.4-complete.jar HiveLexer.g HiveParser.g

javac -cp antlr-3.4-complete.jar HiveLexer.java HiveParser*.java ParseError.java ANTLRNoCaseStringStream.java

还使用之前的树生成代码,因为前面修改了语法规则,解析入口的方法名也要修改
修订后的python代码如下

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

StringStream = jnius.autoclass('grammar.hive110.ANTLRNoCaseStringStream')
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()

傲慢程序员 发布了19 篇原创文章 · 获赞 0 · 访问量 815 私信 关注

标签:语句,26,java,TOK,python,HiveParser,hive,a1,antlr
来源: https://blog.csdn.net/bigdataolddriver/article/details/104000719

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有