[关闭]
@Spongcer 2015-01-09T10:02:45.000000Z 字数 7593 阅读 3037

修复 Hive Grouping Sets ParseException

ParseException GroupingSets Antlr Hive


Hive GROUPING SETS 现状

目前,Hive在解析GROUPING SETS语句的时候,如果GROUPING SETS中存在由两个或者多个普通表达式所组成的子表达式组合,则每个子表达式组合中的第一个普通表达式只能够是不带任何限定名的普通Identifier,否则Hive会在Parser阶段抛出ParseException异常,因此,下面两条语句会在Parser阶段抛出ParseException异常:

  1. drop table test;
  2. create table test(tc1 int, tc2 int, tc3 int);
  3. explain select test.tc1, test.tc2 from test group by test.tc1, test.tc2 grouping sets(test.tc1, (test.tc1, test.tc2));
  4. explain select tc1+tc2, tc2 from test group by tc1+tc2, tc2 grouping sets(tc2, (tc1 + tc2, tc2));
  5. drop table test;

Hive后台打印出的ParseException异常堆栈信息如下:

  1. 2015-01-07 09:53:34,718 INFO [main]: log.PerfLogger (PerfLogger.java:PerfLogBegin(108)) - <PERFLOG method=Driver.run from=org.apache.hadoop.hive.ql.Driver>
  2. 2015-01-07 09:53:34,719 INFO [main]: log.PerfLogger (PerfLogger.java:PerfLogBegin(108)) - <PERFLOG method=TimeToSubmit from=org.apache.hadoop.hive.ql.Driver>
  3. 2015-01-07 09:53:34,721 INFO [main]: ql.Driver (Driver.java:checkConcurrency(158)) - Concurrency mode is disabled, not creating a lock manager
  4. 2015-01-07 09:53:34,721 INFO [main]: log.PerfLogger (PerfLogger.java:PerfLogBegin(108)) - <PERFLOG method=compile from=org.apache.hadoop.hive.ql.Driver>
  5. 2015-01-07 09:53:34,724 INFO [main]: log.PerfLogger (PerfLogger.java:PerfLogBegin(108)) - <PERFLOG method=parse from=org.apache.hadoop.hive.ql.Driver>
  6. 2015-01-07 09:53:34,724 INFO [main]: parse.ParseDriver (ParseDriver.java:parse(185)) - Parsing command: explain select test.tc1, test.tc2 from test group by test.tc1, test.tc2 grouping sets(test.tc1, (test.tc1, test.tc2))
  7. 2015-01-07 09:53:34,734 ERROR [main]: ql.Driver (SessionState.java:printError(545)) - FAILED: ParseException line 1:105 missing ) at ',' near '<EOF>'
  8. line 1:116 extraneous input ')' expecting EOF near '<EOF>'
  9. org.apache.hadoop.hive.ql.parse.ParseException: line 1:105 missing ) at ',' near '<EOF>'
  10. line 1:116 extraneous input ')' expecting EOF near '<EOF>'
  11. at org.apache.hadoop.hive.ql.parse.ParseDriver.parse(ParseDriver.java:210)
  12. at org.apache.hadoop.hive.ql.parse.ParseDriver.parse(ParseDriver.java:166)
  13. at org.apache.hadoop.hive.ql.Driver.compile(Driver.java:404)
  14. at org.apache.hadoop.hive.ql.Driver.compile(Driver.java:322)
  15. at org.apache.hadoop.hive.ql.Driver.compileInternal(Driver.java:975)
  16. at org.apache.hadoop.hive.ql.Driver.runInternal(Driver.java:1040)
  17. at org.apache.hadoop.hive.ql.Driver.run(Driver.java:911)
  18. at org.apache.hadoop.hive.ql.Driver.run(Driver.java:901)
  19. at org.apache.hadoop.hive.cli.CliDriver.processLocalCmd(CliDriver.java:268)
  20. at org.apache.hadoop.hive.cli.CliDriver.processCmd(CliDriver.java:220)
  21. at org.apache.hadoop.hive.cli.CliDriver.processLine(CliDriver.java:423)
  22. at org.apache.hadoop.hive.cli.CliDriver.executeDriver(CliDriver.java:792)
  23. at org.apache.hadoop.hive.cli.CliDriver.run(CliDriver.java:686)
  24. at org.apache.hadoop.hive.cli.CliDriver.main(CliDriver.java:625)
  25. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  26. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
  27. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  28. at java.lang.reflect.Method.invoke(Method.java:606)
  29. at org.apache.hadoop.util.RunJar.main(RunJar.java:208)
  30. 2015-01-07 09:53:34,745 INFO [main]: log.PerfLogger (PerfLogger.java:PerfLogEnd(135)) - </PERFLOG method=compile start=1420595614721 end=1420595614745 duration=24 from=org.apache.hadoop.hive.ql.Driver>
  31. 2015-01-07 09:53:34,745 INFO [main]: log.PerfLogger (PerfLogger.java:PerfLogBegin(108)) - <PERFLOG method=releaseLocks from=org.apache.hadoop.hive.ql.Driver>
  32. 2015-01-07 09:53:34,746 INFO [main]: log.PerfLogger (PerfLogger.java:PerfLogEnd(135)) - </PERFLOG method=releaseLocks start=1420595614745 end=1420595614746 duration=1 from=org.apache.hadoop.hive.ql.Driver>
  33. 2015-01-07 09:53:34,746 INFO [main]: log.PerfLogger (PerfLogger.java:PerfLogBegin(108)) - <PERFLOG method=releaseLocks from=org.apache.hadoop.hive.ql.Driver>
  34. 2015-01-07 09:53:34,746 INFO [main]: log.PerfLogger (PerfLogger.java:PerfLogEnd(135)) - </PERFLOG method=releaseLocks start=1420595614746 end=1420595614746 duration=0 from=org.apache.hadoop.hive.ql.Driver>

第一条语句抛出PrseException异常的原因是:在GROUPING SETS中存在子表达式组合(test.tc1, test.tc2),该子表达式由两个普通表达式组成,但是该子表达式的第一个普通表达式test.tc1带有限定名test

第二条语句抛出PrseException异常的原因是:在GROUPING SETS中存在子表达式组合(tc1 + tc2, tc2),该子表达式有两个普通表达式组成,但是该子表达式的第一个普通表达式tc1 + tc2不是一个没有任何限定名的普通Identifier,而是一个算术表达式tc1 + tc2

下面的语句则不会抛出ParseException异常:

  1. drop table test;
  2. create table test(tc1 int, tc2 int, tc3 int);
  3. explain select tc1, test.tc2 from test group by tc1, test.tc2 grouping sets(tc1, (tc1, test.tc2));
  4. explain select tc1+tc2, tc1 from test group by tc1+tc2, tc1 grouping sets(tc1, (tc1, tc1 + tc2));
  5. explain select test.tc1, test.tc1 + test.tc2 from test group by test.tc1, test.tc1 + test.tc2 grouping sets(test.tc1, (test.tc1), (test.tc1 + test.tc2));
  6. drop table test;

第一条语句和第二条语句不抛出PrseException异常的原因是:GROUPING SETS中的子表达式组合(tc1, test.tc2)(tc1, tc1 + tc2)中的第一个表达式为不带任何限定名的普通表达式tc1

第三条语句不抛出PrseException异常的原因是:虽然GROUPING SETS中存在子表达式组合(test.tc1)(test.tc1 + test.tc2),而且这些组合中的各个表达式均带有限定名test,甚至还可是一个算术表达式(test.tc1 + test.tc2),但是这些子表达式组合中的表达式只有1个:test.tc1或者test.tc1 + test.tc2,而不是两个或两个以上。

Hive Parse 过程分析

与上面解析GROUPING SETS语句的相关语法定义如下:

  1. groupingSetExpression
  2. @init {gParent.pushMsg("grouping set expression", state); }
  3. @after {gParent.popMsg(state); }
  4. :
  5. groupByExpression
  6. -> ^(TOK_GROUPING_SETS_EXPRESSION groupByExpression)
  7. |
  8. LPAREN
  9. groupByExpression (COMMA groupByExpression)*
  10. RPAREN
  11. -> ^(TOK_GROUPING_SETS_EXPRESSION groupByExpression+)
  12. |
  13. LPAREN
  14. RPAREN
  15. -> ^(TOK_GROUPING_SETS_EXPRESSION)
  16. ;
  17. atomExpression
  18. :
  19. KW_NULL -> TOK_NULL
  20. | dateLiteral
  21. | constant
  22. | castExpression
  23. | caseExpression
  24. | whenExpression
  25. | (functionName LPAREN) => function
  26. | tableOrColumn
  27. | LPAREN! expression RPAREN!
  28. ;
  29. precedenceFieldExpression
  30. :
  31. atomExpression ((LSQUARE^ expression RSQUARE!) | (DOT^ identifier))*
  32. ;
  33. precedencePlusExpression
  34. :
  35. precedenceStarExpression (precedencePlusOperator^ precedenceStarExpression)*
  36. ;

上面的语法位于ql/src/java/org/apache/hadoop/hive/ql/parse/IdentifiersParser.g文件中,该文件所在的目录下还有词法文件HiveLexer.g以及另外三个语法文件SelectClauseParser.gFromClauseParser.gHiveParser.gANTLR 3.4会将上面的文件编译成org.apache.hadoop.hive.ql.parse包下的HiveLexer.javaHiveParser.javaHiveParser_FromClauseParser.javaHiveParser_IdentifiersParser.javaHiveParser_SelectClauseParser.java文件,Hive的词法和语法解析模块实际上就是调用的这几个java源文件中所定义类的相关接口。

经过调试ANTLR 3.4的解析过程,发现,引起这些问题的原因是:ANTLR 3.4在进行语法分析时,会调用org.antlr.runtime.DFA类的predict函数进行分支预测,而该函数使用的是非激进式贪心算法尽可能花费较少的代价去选择出一条分支路径;也就是说,predict函数会优先选择分支号较小的路径进行匹配,而且在匹配的过程中,会使用尽可能少的表达式去确定此分支是否符合要求,一旦确定,即返回分支号,如果还存在其它分支也有满足要求的路径,这些分支会被ANTLR 3.4忽略

因此,针对grouping sets(test.tc1, (test.tc1, test.tc2))中的子表达式组合(test.tc1, test.tc2),将groupingSetExpression的1号分支递归到atomExpression的9号分支,然后结合precedenceFieldExpression的2号分支即可找到一条唯一的分支路径:LPAREN! atomExpression DOT^ identifier RPAREN!,该路径和子表达式组合(test.tc1, test.tc2)中的(test.tc1通过非激进式贪心算法匹配成功。

针对grouping sets(tc2, (tc1 + tc2, tc2))中的子表达式组合(tc1 + tc2, tc2),将groupingSetExpression的1号分支递归到atomExpression的9号分支,然后结合precedencePlusExpression即可找到一条唯一的分支路径:LPAREN! precedenceStarExpression precedencePlusOperator^ precedenceStarExpression RPAREN!,该路径和子表达式组合(tc1 + tc2, tc2)中的(tc1 + tc2通过非激进式贪心算法匹配成功。

经过这样的匹配后,我们发现,ANTLR 3.4所确定的分支语法中,下一个字符一定是右括号“RPAREN!”,而我们所输入的语句的(test.tc1(tc1 + tc2的下一个字符都是逗号“,”,因此ANTLR 3.4抛出ParseException异常:在需要右括号")"的地方出现逗号","

Hive ParseException 解决方法

那么,如何避免抛出这样的异常呢?

经过分析,我们发现,在groupingSetExpression的2号分支中,存在上述语句的完整匹配,因此,我们将groupingSetExpression的1号分支和2号分支交换位置,即可让ANTLR 3.4优先在原2号分支中选择出一条分支路径,忽略原1号分支中的路径,即可解决此问题。

交换后,groupingSetExpression的语法定义如下:

  1. groupingSetExpression
  2. @init {gParent.pushMsg("grouping set expression", state); }
  3. @after {gParent.popMsg(state); }
  4. :
  5. LPAREN
  6. groupByExpression (COMMA groupByExpression)*
  7. RPAREN
  8. -> ^(TOK_GROUPING_SETS_EXPRESSION groupByExpression+)
  9. |
  10. groupByExpression
  11. -> ^(TOK_GROUPING_SETS_EXPRESSION groupByExpression)
  12. |
  13. LPAREN
  14. RPAREN
  15. -> ^(TOK_GROUPING_SETS_EXPRESSION)
  16. ;

但是交换后,唯一的缺点就是,打破了语法解析文件的编写规则:先简单,后复杂,先原子,后组合。

更改后,已经通过Hive的单元测试,但是由于无法穷举出单元测试以外的所有情况,因此不能够保证此中修正方法不会引起其它的连带bug,如有更好的解决方案,期待交流:zhaohm3@asiainfo.com

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注