hive sql 格式化自己来

当前比较好的sql格式化工具

以下在网上搜集了一些比较好的sql格式化工具,各有利弊。但对hive sql 来说目前还没有比较好的工具可以直接拿过来用。
1. ApexSQL Refactor SQL formatter
2. SQL Pretty Printer
         Instant SQL Formatter (在线免费版本 ) http://www.dpriver.com/pp/sqlformat.htm
         SQL Pretty Printer (桌面应用收费版本) http://www.dpriver.com/products/sqlpp/desktop_index.php
3. druid
         https://github.com/alibaba/druid 开源免费

sql格式化之druid

      druid(alibaba)实现了sql语法的分析(和antlr生成的parse非常像),目前已经支持大部分常用的sql语法。查看其代码发现Terence Parr(antlr的作者)说的挺对的,词法和语法分析十分的枯燥,而且容易出现错误。可维护性特别差。
      druid内部实现了一个format方法并支持很多种sql语法,虽然druid的主要方向并不在此,有些大柴小用但是如果可用也是极好的。目前看druid在hive的语法实现上不完全,有些语法还未支持(比如定义个es的外表)。但是要实现具体的语法,门槛还是有的,相较antlr还是有点复杂、而且学习该技能通用型较差。实地勘探发现druid在sql格式化方法中的两个问题,当然在格式化这个问题上这个还存在另外一个更大的问题就是注释(这个它并没有关注,只是保留了两个listener):

1 ) 这个地方匹配末尾的一个 \n 如果是最后一行的注释没有换行符号 这个会报上个 intifiy error
          代码中找: // single line comment结束符错误 这个
2) sqlformat features 参数没有向内部传递,配置了不生效。里面还没实现这些逻辑
         public static SQLStatementParser createSQLStatementParser(String sql, String dbType, SQLParserFeature… features) {
         if (JdbcUtils.ORACLE.equals(dbType) || JdbcUtils.ALI_ORACLE.equals(dbType)) {
相对问题看它的实现方式还很有参考价值的,性能毕竟是druid的核心关注。druid hivesql格式化的具体执行过程:
1)类的继承和集成关系
                                      <–SQLExprParser <–HiveExprParser { HiveLexer(实例化) }
SQLParser { Lexer }                                                     |(实例化)
                                    <– SQLStatementParser { SQLExprParser } <– HiveStatementParser
2)类的构建过程
SQLParser { Lexer } <– SQLStatementParser { SQLExprParser: HiveExprParser { Lexer: HiveLexer(实例化) } , Lexer: SQLExprParser的HiveLexer } <– HiveStatementParser
题外话: druid构建了在线语法解析的工具,对于权限控制、注入检测、分库分表等有非常大的实际使用价值(公有云的权限控制,公司集团内部的代理中间件等等都有很好的场景)。

hive 源码中的sql语法解析解析器如何构建

hive中的sql parser 使用的是antlr ,目前用的是3.5.2。 antlr基于ll(*)实现的ll(*) parser
        论文:
                1. LL(*): The Foundation of the ANTLR Parser Generator https://www.antlr.org/papers/LL-star-PLDI11.pdf
                2. Adaptive LL(*) Parsing: The Power of Dynamic Analysis https://www.antlr.org/papers/allstar-techreport.pdf
       ll parser wiki: https://en.wikipedia.org/wiki/LL_parser
      语法分析的各种工具对比:
hive-master/ql/pom.xml => org.antlr
自己构建
1) cp hive-master/ql/src/java/org/apache/hadoop/hive/ql/parse/*.g ./
2)
whomm@bogon > ~/Desktop/hiveparse > mkdir output
whomm@bogon > ~/Desktop/hiveparse > which antlr3
antlr3: aliased to java -jar /usr/local/lib/antlr-3.5.2-complete.jar
whomm@bogon > ~/Desktop/hiveparse > ll
total 328
-rwxr-xr-x@ 1 whomm staff 11K 10 29 11:02 FromClauseParser.g
-rwxr-xr-x@ 1 whomm staff 1.9K 10 29 11:02 HintParser.g
-rwxr-xr-x@ 1 whomm staff 11K 10 29 11:02 HiveLexer.g
-rwxr-xr-x@ 1 whomm staff 91K 10 29 11:02 HiveParser.g
-rwxr-xr-x@ 1 whomm staff 23K 10 29 11:02 IdentifiersParser.g
-rwxr-xr-x@ 1 whomm staff 11K 10 29 11:02 ResourcePlanParser.g
-rwxr-xr-x@ 1 whomm staff 5.7K 10 29 11:02 SelectClauseParser.g
drwxr-xr-x 2 whomm staff 68B 11 2 10:18 output
whomm@bogon > ~/Desktop/hiveparse > antlr3 HiveParser.g -o output

hive源码中语法解析的过程

    1. hive-master/ql/src/java/org/apache/hadoop/hive/ql/parse/ParseDriver.java
    2. hive-master/ql/src/java/org/apache/hadoop/hive/ql/parse/Driver.java
      1. Driver类:compile函数: tree = ParseUtils.parse(command, ctx); -> ParseDriver类 pd.parse(command, ctx, viewFullyQualifiedName)
      2. 最后获取到 ASTNode tree;
    3. 打开hive的debug模式 ,构建个错误的语法可以直接看到调用堆栈:NoViableAltException(26@[])
at org.apache.hadoop.hive.ql.parse.HiveParser.statement(HiveParser.java:1028)
at org.apache.hadoop.hive.ql.parse.ParseDriver.parse(ParseDriver.java:201)
at org.apache.hadoop.hive.ql.parse.ParseDriver.parse(ParseDriver.java:166)
at org.apache.hadoop.hive.ql.Driver.compile(Driver.java:418)
at org.apache.hadoop.hive.ql.Driver.compile(Driver.java:312)
at org.apache.hadoop.hive.ql.Driver.compileInternal(Driver.java:1201)
at org.apache.hadoop.hive.ql.Driver.runInternal(Driver.java:1296)
at org.apache.hadoop.hive.ql.Driver.run(Driver.java:1127)
at org.apache.hadoop.hive.ql.Driver.run(Driver.java:1115)
at org.apache.hadoop.hive.cli.CliDriver.processLocalCmd(CliDriver.java:220)
at org.apache.hadoop.hive.cli.CliDriver.processCmd(CliDriver.java:172)
at org.apache.hadoop.hive.cli.CliDriver.processLine(CliDriver.java:383)
at org.apache.hadoop.hive.cli.CliDriver.executeDriver(CliDriver.java:775)
at org.apache.hadoop.hive.cli.CliDriver.run(CliDriver.java:693)
at org.apache.hadoop.hive.cli.CliDriver.main(CliDriver.java:628)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.apache.hadoop.util.RunJar.run(RunJar.java:221)
at org.apache.hadoop.util.RunJar.main(RunJar.java:136)
FAILED: ParseException line 1:0 cannot recognize input near ‘com’ ‘sdfsdf’ ‘<EOF>’

利用hive的sql语法自己实现antlr

antlr3 HiveLexer.g -o out #目前hive master分支antlr依赖的是3.5.2版本
antlr3 HiveParser.g -o out
maven中添加antlr&编译
<build>
<sourceDirectory>${basedir}/src/java</sourceDirectory>
<testSourceDirectory>${basedir}/src/test</testSourceDirectory>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.antlr</groupId>
<artifactId>antlr3-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>antlr</goal>
</goals>
</execution>
</executions>
<configuration>
<sourceDirectory>${basedir}/src/main/java/whomm/hsqlformat/hive/parse</sourceDirectory>
<includes>
<include>**/HiveLexer.g</include>
<include>**/HiveParser.g</include>
</includes>
</configuration>
</plugin>
命令:mvn org.antlr:antlr3-maven-plugin:antlr
maven 配置参考:
        1. ANTLR 3.x Creating and Executing a Grammar in Eclipse https://vimeo.com/8015802

格式化(antlr的黑魔法)

不得不说这个antlr是个好产品,确实把复杂的事情简单化了。
1. 逻辑和语法定义是分离的,语法的定义简单明了。
2. 能直接生成语法树,支持生成各种语言。
Hive-master的hive语法包含几个文件(hive-master/ql/src/java/org/apache/hadoop/hive/ql/parse):
HiveLexer.g
LINE_COMMENT : ‘–‘ (~(‘\n’|’\r’))* { $channel=HIDDEN; }
行注释默认被送入到hidden通道了,语法解析的时候会被直接忽略
特殊的字符作为变量就在这个文件中修改
FromClauseParser.g
from语句解析
IdentifiersParser.g
标识符定义 函数名称、系统函数、关键字等
里面有个 nonReserved这个很重要非保留的关键字是可以作为标识符的。
比如 select a as date from mytable 这个date不添加转义会报错的,但是该处如果添加 “ | KW_DATE ” date可直接作为标识符使用
ResourcePlanParser.g
资源计划
SelectClauseParser.g
select语句解析
HiveParser.g
import了 SelectClauseParser, FromClauseParser, IdentifiersParser, ResourcePlanParser 实现了所有的hive语法解析
statement 就是入口规则
HintParser.g
hive的hint语法解析
       如果 parser里面添加带有linecomment的语法识别还是比较繁琐的,毕竟语法解析的目标是实际的语句。如果利用语法树每个节点的 line 和 charPositionInLine 和 lexer后的comment token的 line 和 charpositoninline ,的前后位置关系来确定注释所在的位置,简单看是行不通的因为:
      parse 后的树里面的接近keyword的 token 是没有 line 和 charPositionInLine的。这个是符合逻辑的,AST有些是逻辑节点是不涉及具体 keyword的,或者是n个keyword生成的逻辑节点 (Stack Overflow里面也有相关讨论:https://stackoverflow.com/questions/9954882/antlr-preserve-line-number-and-position-in-tree-grammar) 。
虽然这个思路是不错的。但这个思路也会存在一些问题:
以下两个例子是查询语句中的场景。ddl语句中的create语句,逻辑节点少不会出现这种情况。
1)逻辑节点被优先遍历到
例如 union all 节点。
select a.id from a union all select b.id from b union all select c.id from c
此时会生成
TOK_QUERY
    TOK_UNIONALL
          TOK_UNIONALL
                 TOK_QUERY ( select a.id from a )
                 TOK_QUERY (select b.id from b)
         TOK_QUERY (select c.id from c)
这样的棵树, 深度遍历树的时候遇到这个节点,如果这个节点是需要输出的。
查看当前node和comment的偏移量。如果当前node大于等于 comment 的 行号 和 所在的列 就输出comment。
union all 节点会被优先遍历到,所以会将 第一个select 里面的comment 提前输出来。
2)新增的虚拟节点
但是对于query类型的sql语句解析成语法树的时候会在 ast tree上 增加子查询的虚拟节点。
比如:
select id from ( select a.id from a union all select b.id from b union all select c.id from c) tmp
TOK_QUERY
     TOK_FROM
         TOK_SUBQUERY
              TOK_QUERY
                    TOK_FROM
                           TOK_SUBQUERY
                                  TOK_UNIONALL
                                             TOK_UNIONALL
                                                    TOK_QUERY
                                                   TOK_QUERY
                                TOK_QUERY
              _u1
              TOK_INSERT
          …
        tmp
       TOK_INSERT
       …
_u1即为解析过程中新增的逻辑节点。这个新增出来的子查询也会导致后面的comment被提前输出。
为此只能思考更加精密的方法。目前制造了个简单的工具

参考文档

  1. eclipise
    1. eclipse maven 配置 https://www.cnblogs.com/tangshengwei/p/6341462.html
  2. hive
    1. hive sql解析和应用 https://www.jianshu.com/p/7cd2afacc9bb
    2. hive sql 的编译过程 https://tech.meituan.com/hive_sql_to_mapreduce.html
    3. hive sql解析过程详解 https://www.cnblogs.com/yaojingang/p/5446310.html
    4. Hive源码分析:Driver类运行过程 https://yq.aliyun.com/articles/26327
    5. Hive Wiki: https://cwiki.apache.org/confluence/display/Hive/Home
    6. HiveSQL编译过程: http://www.slideshare.net/recruitcojp/internal-hive
    7. Join Optimization in Hive: Join Strategies in Hive from the 2011 Hadoop Summit (Liyin Tang, Namit Jain)
    8. Hive Design Docs: https://cwiki.apache.org/confluence/display/Hive/DesignDocs
    9. hivesql 解析过程 https://github.com/alan2lin/hive_ql_parser
  3. antlr(Another Tool for Language Recognition)
    1. Antlr: http://www.antlr.org/
    2. Wiki Antlr介绍: http://en.wikipedia.org/wiki/ANTLR
    3. 使用 Antlr 开发领域语言 https://www.ibm.com/developerworks/cn/java/j-lo-antlr/index.html
    4. The Definitive ANTLR Reference https://doc.lagout.org/programmation/Pragmatic%20Programmers/The%20Definitive%20ANTLR%20Reference.pdf
    5. 《antlr 权威指南》(pdf 下载 http://www.safuli.com/blog/articles/7985.html
    6. 《antlr 2.7.5 中文文档》 http://www.blogjava.net/huanzhugege/archive/2008/06/30/211762.html
    7. 基础知识:词法分析器(通常称为扫描器)将输入的字符流分解为词汇表中的一个个的符号,然后输出 到语法分析器,语法分析器将语法结构应用于那些符号流。因为 ANTLR 为词法分析、语法 分析和树分析引入了相同的识别机制,ANTLR 生成的词法分析器比基于 DFA 词法分析器更强 大,比如 DLG 和 lex 生成的词法分析器。
  4. durid
    1. https://github.com/alibaba/druid/wiki/SQL-Parser
    2. https://github.com/alibaba/druid/wiki/SQL_Format

发表评论

电子邮件地址不会被公开。 必填项已用*标注