Hibernate版本5.1.11FInal
以一句update语句作为例子。
update com.tydhot.eninty.User set userName=:userName where userId=:userId
上面这句hql经过antlr的语法解析之后,得到的语法树如下。
\-[50] Node: 'update'
+-[22] Node: 'FROM'
| \-[90] Node: 'RANGE'
| \-[15] Node: '.'
| +-[15] Node: '.'
| | +-[15] Node: '.'
| | | +-[108] Node: 'com'
| | | \-[108] Node: 'tydhot'
| | \-[108] Node: 'eninty'
| \-[108] Node: 'User'
+-[46] Node: 'set'
| \-[105] Node: '='
| +-[108] Node: 'userName'
| \-[127] Node: ':'
| \-[108] Node: 'userName'
\-[52] Node: 'where'
\-[105] Node: '='
+-[108] Node: 'userId'
\-[127] Node: ':'
\-[108] Node: 'userId'
在antlr的语法树中,使用BaseAST作为一个语法树节点的抽象,在BaseAST存储中,其数据结构更加类似一个链表。
protected BaseAST down;
protected BaseAST right;
BaseAST中down代表其在树结构中的子节点,而right则代表跟他从属一个父节点的兄弟节点。在语法树中,父节点的down只指向逻辑顺序的第一个子节点。
承接上文,当QueryTranslatorImpl中调用parse()方法中,当语法解析器parser调用statement正式开始语法解析。
从语法文件hql.g中可以看到,所有hql解析的起点是statement规则。
statement
: ( updateStatement | deleteStatement | selectStatement | insertStatement ) (EOF!)
;
从这里可以看到,语法解析器会依次从下面四个规则一次尝试去选择匹配,四个规则也正好对应了数据库操作的增删改查。
这里再次放上等待作为例子等待解析的hql语句。
update com.tydhot.eninty.User set userName=:userName where userId=:userId
我们可以看到,首先会尝试去选择匹配updateStatement规则,如代码所示。
try { // for error handling
{
switch ( LA(1)) {
case UPDATE:
{
updateStatement();
astFactory.addASTChild(currentAST, returnAST);
break;
}
case DELETE:
{
deleteStatement();
astFactory.addASTChild(currentAST, returnAST);
break;
}
case EOF:
case FROM:
case GROUP:
case ORDER:
case SELECT:
case WHERE:
{
selectStatement();
astFactory.addASTChild(currentAST, returnAST);
break;
}
case INSERT:
{
insertStatement();
astFactory.addASTChild(currentAST, returnAST);
break;
}
default:
{
throw new NoViableAltException(LT(1), getFilename());
}
}
在上方的代码中,会从上一篇文章所提到的词法解析器去提取所等到解析的hql语句的第一个词,在这里,如果是update关键字开头,自然会开始进入updateStatement规则的匹配。
updateStatement
: UPDATE^ (optionalVersioned)?
optionalFromTokenFromClause
setClause
(whereClause)?
;
从语法文件的配置还是可以看出,只有在第一个在字符匹配到了update才会继续进行下面的匹配。接下来是根据这个语法规则antlr编译的代码。
match(UPDATE);
{
switch ( LA(1)) {
case VERSIONED:
{
AST tmp2_AST = null;
tmp2_AST = astFactory.create(LT(1));
astFactory.addASTChild(currentAST, tmp2_AST);
match(VERSIONED);
break;
}
case FROM:
case IDENT:
{
break;
}
default:
{
throw new NoViableAltException(LT(1), getFilename());
}
}
}
optionalFromTokenFromClause();
astFactory.addASTChild(currentAST, returnAST);
setClause();
astFactory.addASTChild(currentAST, returnAST);
{
switch ( LA(1)) {
case WHERE:
{
whereClause();
astFactory.addASTChild(currentAST, returnAST);
break;
}
case EOF:
{
break;
}
default:
{
throw new NoViableAltException(LT(1), getFilename());
}
}
}
updateStatement_AST = (AST)currentAST.root;
可以看到,在取出第一个词之后会通过match()确认关键字update的确认无误,并生成第一个update节点作为当前树的根节点,之后判断下一个词是否匹配版本关键字,对应语法文件的optionalVersion,当然根据语法文件中的正则显示该语句块可加可不加,那么自然代码中对于没有匹配到这一关键字,也没有什么别的操作。
而对于我们的例子语句
update com.tydhot.eninty.User set userName=:userName where userId=:userId
自然也没有影响,会继续往下匹配下一个语法规则,下一步则会匹配optionalFromTokenClause规则,代码中也会进入optionalFromTokenClause()代码段。
optionalFromTokenFromClause!
: (FROM!)? f:path (a:asAlias)? {
AST #range = #([RANGE, "RANGE"], #f, #a);
#optionalFromTokenFromClause = #([FROM, "FROM"], #range);
}
;
optionalFromTokenClause的语法规则就显得稍微复杂。编译后的代码如下。
switch ( LA(1)) {
case FROM:
{
match(FROM);
break;
}
case IDENT:
{
break;
}
default:
{
throw new NoViableAltException(LT(1), getFilename());
}
}
}
path();
f_AST = (AST)returnAST;
{
switch ( LA(1)) {
case AS:
case IDENT:
{
asAlias();
a_AST = (AST)returnAST;
break;
}
case EOF:
case SET:
case WHERE:
{
break;
}
default:
{
throw new NoViableAltException(LT(1), getFilename());
}
}
}
optionalFromTokenFromClause_AST = (AST)currentAST.root;
AST range = (AST)astFactory.make( (new ASTArray(3)).add(astFactory.create(RANGE,"RANGE")).add(f_AST).add(a_AST));
optionalFromTokenFromClause_AST = (AST)astFactory.make( (new ASTArray(2)).add(astFactory.create(FROM,"FROM")).add(range));
currentAST.root = optionalFromTokenFromClause_AST;
currentAST.child = optionalFromTokenFromClause_AST!=null &&optionalFromTokenFromClause_AST.getFirstChild()!=null ?
optionalFromTokenFromClause_AST.getFirstChild() : optionalFromTokenFromClause_AST;
currentAST.advanceChildToEnd();
代码比较长,根据语法规则,首先会尝试匹配from关键字,当然无论有没有匹配到都不会影响下面的操作。
接下来会对下一个部分强制进行path规则的匹配,也会进入代码的path()方法。
path
: identifier ( DOT^ { weakKeywords(); } identifier )*
;
Path的规则就显得很简单,首先强制要求必须存在一个常量,也就是说update语句的操作表对象至少要存在一个,但是在hql语句中可能存在以一个类名作为对象,就像下面这个例子。
update com.tydhot.eninty.User set userName=:userName where userId=:userId
那么根据语法规则可以匹配若干个点‘ .’起头的关键字进行匹配,下面是path()的代码。
identifier();
astFactory.addASTChild(currentAST, returnAST);
{
_loop315:
do {
if ((LA(1)==DOT)) {
AST tmp10_AST = null;
tmp10_AST = astFactory.create(LT(1));
astFactory.makeASTRoot(currentAST, tmp10_AST);
match(DOT);
weakKeywords();
identifier();
astFactory.addASTChild(currentAST, returnAST);
}
else {
break _loop315;
}
} while (true);
}
path_AST = (AST)currentAST.root;
在path()中,会强制第一个匹配一个常量,匹配到则会生成一个语法树节点,之后会尝试在循环中不断去匹配点‘ .’,如果匹配到点,也会生成一个相应的语法树节点,并将这个节点作为当前树的根节点,其down则会指向刚才生成的常量节点,之后会继续匹配下一个常量,并作为第一个常量节点right所指向的节点。再下一次循环中,如果继续匹配到了点,则生成一个相应的新节点作为当前树的根节点,其down指向第一个循环所产生的点对应的树节点(也就是根节点),以此不断循环产生节点,达到一次中序遍历可以换元整个结果的目的,之前例子该部分生成的语法树如下。
\-[15] Node: '.'
| +-[15] Node: '.'
| | +-[15] Node: '.'
| | | +-[108] Node: 'com'
| | | \-[108] Node: 'tydhot'
| | \-[108] Node: 'eninty'
| \-[108] Node: 'User'
从最左边的com节点开始进行一次先序遍历就可以还原整个结果。
在path()部分得到的这部分结果将会回到optionalFromTokenClause规则当中继续作为语法树的成员操作。
之后如果匹配到了AS关键字或者空格一个常量,那么会继续进入asAlias的规则匹配,但是根据语法规则,此处也是可有可无的部分,给出的例子即使没有匹配到,也不会出现语法错误。
ST range = (AST)astFactory.make( (new ASTArray(3)).add(astFactory.create(RANGE,"RANGE")).add(f_AST).add(a_AST));
optionalFromTokenFromClause_AST = (AST)astFactory.make( (new ASTArray(2)).add(astFactory.create(FROM,"FROM")).add(range));
根据语法规则,产生的对象和其别名将会作为RANGE节点的子节点存放在语法树中,而会继续产生一个FROM节点继续作为RANGE的父节点,最后在optionalFromTokenClause规则中产生的树如下。
+-[22] Node: 'FROM'
| \-[90] Node: 'RANGE'
| \-[15] Node: '.'
| +-[15] Node: '.'
| | +-[15] Node: '.'
| | | +-[108] Node: 'com'
| | | \-[108] Node: 'tydhot'
| | \-[108] Node: 'eninty'
| \-[108] Node: 'User'
在完成了optionalFromTokenClause规则后,将得到的树的根节点作为update的子节点,也是update的第一个子节点,被down所指向,而update的其他子节点将会被from节点所指向。
optionalFromTokenFromClause();
astFactory.addASTChild(currentAST, returnAST);
setClause();
astFactory.addASTChild(currentAST, returnAST);
回到update规则,接下来会强制进入setClause部分的语法匹配。接下来经过语法规则编译的代码部分与上文类似,重点解析语法规则。
setClause
: (SET^ assignment (COMMA! assignment)*)
;
assignment
: stateField EQ^ newValue
;
stateField
: path
;
newValue
: concatenation
;
在setClause的语法规则中,根据正则,则会强制从set关键字进行匹配,接下来也强制至少存在一个等号表达式,复数的表达式可以通过逗号之间隔开,而在等号左边,则支持path规则,也就是上文中通过.隔开常量的java对象形式的表达,而右边则是一个concatenation规则,也就是被赋值的值。
concatenation
: additiveExpression
( c:CONCAT^ { #c.setType(EXPR_LIST); #c.setText("concatList"); }
additiveExpression
( CONCAT! additiveExpression )*
{ #concatenation = #([METHOD_CALL, "||"], #([IDENT, "concat"]), #c ); } )?
;
赋值语句分为五级,第4级也就是优先度最低的通过|隔开.
additiveExpression
: multiplyExpression ( ( PLUS^ | MINUS^ ) multiplyExpression )*
;
第3级则是根据加减分隔,根据先序遍历的性质,这里也将会在接下来的乘除操作之后进行加减。
multiplyExpression
: unaryExpression ( ( STAR^ | DIV^ | MOD^ ) unaryExpression )*
;
第2级也就是乘除,优先级高于加减。
unaryExpression
: MINUS^ {#MINUS.setType(UNARY_MINUS);} unaryExpression
| PLUS^ {#PLUS.setType(UNARY_PLUS);} unaryExpression
| caseExpression
| quantifiedExpression
| atom
;
caseExpression
// NOTE : the unaryExpression rule contains the subQuery rule
: simpleCaseStatement
| searchedCaseStatement
;
simpleCaseStatement
: CASE^ unaryExpression (simpleCaseWhenClause)+ (elseClause)? END! {
#simpleCaseStatement.setType(CASE2);
}
;
simpleCaseWhenClause
: (WHEN^ unaryExpression THEN! unaryExpression)
;
elseClause
: (ELSE^ unaryExpression)
;
searchedCaseStatement
: CASE^ (searchedCaseWhenClause)+ (elseClause)? END!
;
searchedCaseWhenClause
: (WHEN^ logicalExpression THEN! unaryExpression)
;
quantifiedExpression
: ( SOME^ | EXISTS^ | ALL^ | ANY^ )
( identifier | collectionExpr | (OPEN! ( subQuery ) CLOSE!) )
;
第一级则是一些逻辑操作,优先度是除了常量和数字之外最高的。
atom
: primaryExpression
(
DOT^ identifier
( options { greedy=true; } :
( op:OPEN^ {#op.setType(METHOD_CALL);} exprList CLOSE! ) )?
| lb:OPEN_BRACKET^ {#lb.setType(INDEX_OP);} expression CLOSE_BRACKET!
)*
;
// level 0 - the basic element of an expression
primaryExpression
: { validateSoftKeyword("function") && LA(2) == OPEN && LA(3) == QUOTED_STRING }? jpaFunctionSyntax
| { validateSoftKeyword("cast") && LA(2) == OPEN }? castFunction
| identPrimary ( options {greedy=true;} : DOT^ "class" )?
| constant
| parameter
| OPEN! (expressionOrVector | subQuery) CLOSE!
;
第0级也就是一些常量和数字,是所有词中,被处理优先级最高的,这些也将在serClause规则中依次被处理成为语法树中的节点。
我们的例子在经过set规则后,生成的树的根节点将会被之前的from部分的right所指向,代表同为update的节点的子节点,在语句的顺序上,也在其兄弟节点from节点的之后,生成的语法树会成为如下所示。
\-[50] Node: 'update'
+-[22] Node: 'FROM'
| \-[90] Node: 'RANGE'
| \-[15] Node: '.'
| +-[15] Node: '.'
| | +-[15] Node: '.'
| | | +-[108] Node: 'com'
| | | \-[108] Node: 'tydhot'
| | \-[108] Node: 'eninty'
| \-[108] Node: 'User'
+-[46] Node: 'set'
| \-[105] Node: '='
| +-[108] Node: 'userName'
| \-[127] Node: ':'
| \-[108] Node: 'userName'
最后根据我们一开始的语法规则,where关键字的匹配是可有可无的,但是我们的例子给出了where 关键字,所以同样的也要相应的继续whereClause规则的匹配。
whereClause
: WHERE^ logicalExpression
;
ogicalExpression
: expression
;
// Main expression rule
expression
: logicalOrExpression
;
// level 7 - OR
logicalOrExpression
: logicalAndExpression ( OR^ logicalAndExpression )*
;
// level 6 - AND, NOT
logicalAndExpression
: negatedExpression ( AND^ negatedExpression )*
;
// NOT nodes aren't generated. Instead, the operator in the sub-tree will be
// negated, if possible. Expressions without a NOT parent are passed through.
negatedExpression!
{ weakKeywords(); } // Weak keywords can appear in an expression, so look ahead.
: NOT^ x:negatedExpression { #negatedExpression = negateNode(#x); }
| y:equalityExpression { #negatedExpression = #y; }
;
equalityExpression
: x:relationalExpression (
( EQ^
| is:IS^ { #is.setType(EQ); } (NOT! { #is.setType(NE); } )?
| NE^
| ne:SQL_NE^ { #ne.setType(NE); }
) y:relationalExpression)* {
// Post process the equality expression to clean up 'is null', etc.
#equalityExpression = processEqualityExpression(#equalityExpression);
}
;
Where的语法规则,在之前set的5级基础上又添加了三级,分别是第七级的与,第六级的是非,和第五级的逻辑等式集合规则。
经过上述的语法规则后,生成的语法树加入到最后的结果如下。
\-[50] Node: 'update'
+-[22] Node: 'FROM'
| \-[90] Node: 'RANGE'
| \-[15] Node: '.'
| +-[15] Node: '.'
| | +-[15] Node: '.'
| | | +-[108] Node: 'com'
| | | \-[108] Node: 'tydhot'
| | \-[108] Node: 'eninty'
| \-[108] Node: 'User'
+-[46] Node: 'set'
| \-[105] Node: '='
| +-[108] Node: 'userName'
| \-[127] Node: ':'
| \-[108] Node: 'userName'
\-[52] Node: 'where'
\-[105] Node: '='
+-[108] Node: 'userId'
\-[127] Node: ':'
\-[108] Node: 'userId'
在语法树中,关键字部分下的子树通过中序遍历,都可以得到例子中的子句,最后将得到的字句根据关键字的顺序组合,就能得到例子中的hql语句,这个语法树,也将被用来作为转换sql语句的依据。