@Spongcer
2015-01-09T18:02:45.000000Z
字数 7593
阅读 3142
ParseException
GroupingSets
Antlr
Hive
目前,Hive在解析GROUPING SETS
语句的时候,如果GROUPING SETS
中存在由两个或者多个普通表达式所组成的子表达式组合,则每个子表达式组合中的第一个普通表达式只能够是不带任何限定名的普通Identifier,否则Hive会在Parser阶段抛出ParseException
异常,因此,下面两条语句会在Parser阶段抛出ParseException
异常:
drop table test;
create table test(tc1 int, tc2 int, tc3 int);
explain select test.tc1, test.tc2 from test group by test.tc1, test.tc2 grouping sets(test.tc1, (test.tc1, test.tc2));
explain select tc1+tc2, tc2 from test group by tc1+tc2, tc2 grouping sets(tc2, (tc1 + tc2, tc2));
drop table test;
Hive后台打印出的ParseException
异常堆栈信息如下:
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>
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>
2015-01-07 09:53:34,721 INFO [main]: ql.Driver (Driver.java:checkConcurrency(158)) - Concurrency mode is disabled, not creating a lock manager
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>
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>
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))
2015-01-07 09:53:34,734 ERROR [main]: ql.Driver (SessionState.java:printError(545)) - FAILED: ParseException line 1:105 missing ) at ',' near '<EOF>'
line 1:116 extraneous input ')' expecting EOF near '<EOF>'
org.apache.hadoop.hive.ql.parse.ParseException: line 1:105 missing ) at ',' near '<EOF>'
line 1:116 extraneous input ')' expecting EOF near '<EOF>'
at org.apache.hadoop.hive.ql.parse.ParseDriver.parse(ParseDriver.java:210)
at org.apache.hadoop.hive.ql.parse.ParseDriver.parse(ParseDriver.java:166)
at org.apache.hadoop.hive.ql.Driver.compile(Driver.java:404)
at org.apache.hadoop.hive.ql.Driver.compile(Driver.java:322)
at org.apache.hadoop.hive.ql.Driver.compileInternal(Driver.java:975)
at org.apache.hadoop.hive.ql.Driver.runInternal(Driver.java:1040)
at org.apache.hadoop.hive.ql.Driver.run(Driver.java:911)
at org.apache.hadoop.hive.ql.Driver.run(Driver.java:901)
at org.apache.hadoop.hive.cli.CliDriver.processLocalCmd(CliDriver.java:268)
at org.apache.hadoop.hive.cli.CliDriver.processCmd(CliDriver.java:220)
at org.apache.hadoop.hive.cli.CliDriver.processLine(CliDriver.java:423)
at org.apache.hadoop.hive.cli.CliDriver.executeDriver(CliDriver.java:792)
at org.apache.hadoop.hive.cli.CliDriver.run(CliDriver.java:686)
at org.apache.hadoop.hive.cli.CliDriver.main(CliDriver.java:625)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.apache.hadoop.util.RunJar.main(RunJar.java:208)
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>
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>
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>
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>
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
异常:
drop table test;
create table test(tc1 int, tc2 int, tc3 int);
explain select tc1, test.tc2 from test group by tc1, test.tc2 grouping sets(tc1, (tc1, test.tc2));
explain select tc1+tc2, tc1 from test group by tc1+tc2, tc1 grouping sets(tc1, (tc1, tc1 + tc2));
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));
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
,而不是两个或两个以上。
与上面解析GROUPING SETS
语句的相关语法定义如下:
groupingSetExpression
@init {gParent.pushMsg("grouping set expression", state); }
@after {gParent.popMsg(state); }
:
groupByExpression
-> ^(TOK_GROUPING_SETS_EXPRESSION groupByExpression)
|
LPAREN
groupByExpression (COMMA groupByExpression)*
RPAREN
-> ^(TOK_GROUPING_SETS_EXPRESSION groupByExpression+)
|
LPAREN
RPAREN
-> ^(TOK_GROUPING_SETS_EXPRESSION)
;
atomExpression
:
KW_NULL -> TOK_NULL
| dateLiteral
| constant
| castExpression
| caseExpression
| whenExpression
| (functionName LPAREN) => function
| tableOrColumn
| LPAREN! expression RPAREN!
;
precedenceFieldExpression
:
atomExpression ((LSQUARE^ expression RSQUARE!) | (DOT^ identifier))*
;
precedencePlusExpression
:
precedenceStarExpression (precedencePlusOperator^ precedenceStarExpression)*
;
上面的语法位于ql/src/java/org/apache/hadoop/hive/ql/parse/IdentifiersParser.g文件中,该文件所在的目录下还有词法文件HiveLexer.g以及另外三个语法文件SelectClauseParser.g、FromClauseParser.g、HiveParser.g,ANTLR 3.4会将上面的文件编译成org.apache.hadoop.hive.ql.parse包下的HiveLexer.java
、HiveParser.java
、HiveParser_FromClauseParser.java
、HiveParser_IdentifiersParser.java
、HiveParser_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
异常:在需要右括号")"的地方出现逗号","。
那么,如何避免抛出这样的异常呢?
经过分析,我们发现,在groupingSetExpression
的2号分支中,存在上述语句的完整匹配,因此,我们将groupingSetExpression
的1号分支和2号分支交换位置,即可让ANTLR 3.4优先在原2号分支中选择出一条分支路径,忽略原1号分支中的路径,即可解决此问题。
交换后,groupingSetExpression
的语法定义如下:
groupingSetExpression
@init {gParent.pushMsg("grouping set expression", state); }
@after {gParent.popMsg(state); }
:
LPAREN
groupByExpression (COMMA groupByExpression)*
RPAREN
-> ^(TOK_GROUPING_SETS_EXPRESSION groupByExpression+)
|
groupByExpression
-> ^(TOK_GROUPING_SETS_EXPRESSION groupByExpression)
|
LPAREN
RPAREN
-> ^(TOK_GROUPING_SETS_EXPRESSION)
;
但是交换后,唯一的缺点就是,打破了语法解析文件的编写规则:先简单,后复杂,先原子,后组合。
更改后,已经通过Hive的单元测试,但是由于无法穷举出单元测试以外的所有情况,因此不能够保证此中修正方法不会引起其它的连带bug,如有更好的解决方案,期待交流:zhaohm3@asiainfo.com