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

以下在网上搜集了一些比较好的sql格式化工具,各有利弊。但对hive sql 来说目前还没有比较好的工具可以直接拿过来用。

1. ApexSQL Refactor SQL formatter

          https://www.apexsql.com/sql-tools-refactor.aspx

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

       LL(*)的概念与实现 http://pfmiles.github.io/blog/concept-and-implementation-of-ll-star/

      语法分析的各种工具对比:

                   https://en.wikipedia.org/wiki/Comparison_of_parser_generators

                   https://stackoverflow.com/questions/41427905/how-many-ways-are-there-to-build-a-parser

hive-master/ql/pom.xml => org.antlr

自己构建

下载antlr https://www.antlr3.org/download/antlr-3.5.2-complete.jar

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;
  1. 打开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

        2. https://alexecollins.com/antlr4-and-maven-tutorial/

格式化(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被提前输出。

为此只能思考更加精密的方法。目前制造了个简单的工具

https://github.com/whomm/hsqlformat

参考文档

  1. eclipise
  1. eclipse maven 配置
  1. 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解析过程详解
  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
  1. 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 生成的词法分析器。
  1. durid
  1. https://github.com/alibaba/druid/wiki/SQL-Parser
  2. https://github.com/alibaba/druid/wiki/SQL_Format