hibernate中antlr对于hql生成抽象语法树源码解析
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)?optionalFromTokenFromClausesetClause(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语句的依据。
hibernate中antlr对于hql生成抽象语法树源码解析相关推荐
- hibernate中antlr对于hql与sql的转换源码的一些细节
Hibernate 5.1.11Final 关于hql中的对象类转换成表名. 在from模块里对hql抽象语法树进行匹配的时候,在path()规则会还原在对hql进行语法解析的时候生成的语法树. pu ...
- hibernate中antlr对于hql的词法分析源码解析
Hibernate版本 5.1.11 private HqlParser parse(boolean filter) throws TokenStreamException, RecognitionE ...
- Java生鲜电商平台-电商中海量搜索ElasticSearch架构设计实战与源码解析
Java生鲜电商平台-电商中海量搜索ElasticSearch架构设计实战与源码解析 生鲜电商搜索引擎的特点 众所周知,标准的搜索引擎主要分成三个大的部分,第一步是爬虫系统,第二步是数据分析,第三步才 ...
- java 中线程池的种类,原理以及源码解析(1)
java 中的线程池创建都是Executors 类中提供的方法,并且方法返回线程池对象. Executors 源码: // // Source code recreated from a .class ...
- ReentrantLock中lock/trylock/lockInterruptibly方法的区别及源码解析
看了几篇关于这三者区别的文章,但都说的不够具体,自己去读了下源码,大概是清楚了三者的功能,不想了解源码的可以跳到最后看总结. 首先,ReentrantLock类中使用了大量的CAS操作,也就是Comp ...
- Java解析SQL生成语法树_04. Hive源码 — HQL解析(抽象语法树的生成和语义分析)
HQL的解析过程主要在Driver中的compile方法,这一些主要看这个方法中的代码. 1. compile中的主要内容 public int compile(String command, boo ...
- 抽象语法树在 JavaScript 中的应用
抽象语法树是什么 在计算机科学中,抽象语法树(abstract syntax tree 或者缩写为 AST),或者语法树(syntax tree),是源代码的抽象语法结构的树状表现形式,这里特指编程语 ...
- AST抽象语法树的基本思想
AST抽象语法树的基本思想 前言 AST概述 AST结构 AST解析 转换 生成 前言 在阅读java ORM框架spring data jpa的源码时,发现Hibernate(spring data ...
- php ast 抽象语法树,AST抽象语法树的基本思想
AST抽象语法树的基本思想 前言 AST概述 AST结构 AST解析 转换 生成 前言 在阅读java ORM框架spring data jpa的源码时,发现Hibernate(spring data ...
最新文章
- linux 后台运行jar SpringBoot
- Android 之 Fagment 完全解析
- K8s-V1.17.6支持GPU
- php获取网络文件的几种方式,PHP如何实现获取网络上的文件?
- 大工19春计算机文化基础在线测试1,大工19春《计算机文化基础》在线测试1(含答案)...
- 上传文件时显示选择窗口
- mysql的order by,group by和distinct优化
- 自动选择SVG和VML的WEB页面
- 某些数组和字符串类型转换(转)
- java 利用Future异步获取多线程任务结果
- 网络安全法学习整理笔记
- 如何利用Python进行数据分析
- GC bias GC偏好
- 一文读懂Elephant Swap的LaaS方案的优势之处
- 互联网日报 | 4月8日 星期四 | 蔚来第10万辆量产车下线;哈啰进入电单车生产销售领域;携程宣布正式开始港股招股...
- iOS 根据银行卡号判断银行名称
- 电脑截屏怎么固定到屏幕 截图放在桌面上
- RAM、 ROM 、SRAM 、DRAM 、SDRAM 、DDR (2、3、4)SDRAM辨析
- 深度学习图像分类:Kaggle植物幼苗分类(Plant Seedlings Classification)完整代码
- Android源码编译流程及所需的编译环境
热门文章
- aurora 初学页面元素
- Notepad++使用技巧
- element-ui表单校验
- basler相机的触发线是那两脚_探究机器视觉领域线扫相机和面阵相机的区别
- 获取字段_数据库中敏感字段的标记、标示
- 关于LabVIEW视觉ROI的读取与存储
- 【坐在马桶上看算法】啊哈算法13:零基础彻底弄懂“并查集“
- .net core 后台 post设置等待时间_[vueelementadmin]前端发送的post请求的数据,后端接收不到并报EOFException异常的解决方案...
- 前端每日实战 2018 年 9 月份项目汇总(共 26 个项目)
- window.btoa()方法;使字符编码成base64的形式