Hibernate版本 5.1.11

private HqlParser parse(boolean filter) throws TokenStreamException, RecognitionException {// Parse the query string into an HQL AST.final HqlParser parser = HqlParser.getInstance( hql );parser.setFilter( filter );LOG.debugf( "parse() - HQL: %s", hql );parser.statement();final AST hqlAst = parser.getAST();final NodeTraverser walker = new NodeTraverser( new JavaConstantConverter( factory ) );walker.traverseDepthFirst( hqlAst );showHqlAst( hqlAst );parser.getParseErrorHandler().throwQueryException();return parser;
}

对于hql的解析的起点在QueryTranslatorImpl的parse()方法中。在这个方法中会调用HqlParse的parse()方法对需要操作的hql生成抽象语法树。getInstance()方法中,只是简单的调用了HqlParse的构造方法,其具体的构造方法如下。

private HqlParser(String hql) {// The fix for HHH-558...super( new HqlLexer( new StringReader( hql ) ) );parseErrorHandler = new ErrorCounter( hql );// Create nodes that track line and column number.setASTFactory( new HqlASTFactory() );
}

在这个构造方法中,主要是根据hql生成了相应的StringReader来便于hql的读取,同时生成HqlLexer来完成相应的词法分析,最后将生成的HqlLexer作为词法分析器存放在语法分析器中的成员之一。

HqlLexer继承自HqlBaseLexer,而HqlBaseLexer继承自antlr的类CharScanner,构造方法中主要完成两件事情,首先,将传入的包含了所要分析的hql语句的StringReader保证为CharBuffer,作为成员存放,便于数据的读取。同时根据,将定义在语法g4文件,hql.g4文件中的关键字存放在hashTable中,便于在读取hql中,将相应的char转化成相应的关键字。

语法文件中关于关键字的定义如下:

EXISTS="exists";
FALSE="false";
FETCH="fetch";
FROM="from";
FULL="full";
GROUP="group";
HAVING="having";
IN="in";
INDICES="indices";

而在经过antlr编译后生成的java类HqlSqlTokenTypes中,这些关键字都会相应的一次被赋予对应的Int值。

int EXISTS = 19;
int FALSE = 20;
int FETCH = 21;
int FROM = 22;
int FULL = 23;
int GROUP = 24;
int HAVING = 25;
int IN = 26;
int INDICES = 27;

同时我们可以看到,在相应的HqlBaseLexer构造方法中,这些关键字都会被转化ANTLRHashString作为键,与HqlSqlTokenTypes类中对应的int值作为值,组成键值对,存放在hashTable中。

literals.put(new ANTLRHashString("ascending", this), new Integer(110));
literals.put(new ANTLRHashString("descending", this), new Integer(111));
literals.put(new ANTLRHashString("false", this), new Integer(20));
literals.put(new ANTLRHashString("exists", this), new Integer(19));
literals.put(new ANTLRHashString("asc", this), new Integer(8));
literals.put(new ANTLRHashString("left", this), new Integer(33));

而ANTLRHashString中的构造方法很简单,主要就是存放对应的关键字,以及当前的词法分析器继承了CharScanner的HqlBaseLexer。

在词法分析器HqlBaseLexer的构造方法中,主要完成的是上述个关于关键字与相应的int值对应的处理与查询,在接下来语法分析器Parse具体进行语法分析的时候,会对关键字和符号进行更详细的处理。

在HqlParse调用statment()方法正式进行语法分析来生成抽象语法树的时候,每次都会调用LA(1)来读取下一个抽象为hql语句元素的Token,来作为下一个语法规则进行匹配的对象。如下面的代码。

try {      // for error handling{switch ( LA(1)) {case UPDATE:{updateStatement();astFactory.addASTChild(currentAST, returnAST);break;}

而在LA()中,最后在HqlBaseLexer的父类TokenBuffer的fill()方法中主要还是调用词法分析器HqlBaseLexer的nextToken()方法,来解析获取当前语句的下一个Token来在Parse中进行语法分析。

private final void fill(int var1) throws TokenStreamException {this.syncConsume();while(this.queue.nbrEntries < var1 + this.markerOffset) {this.queue.append(this.input.nextToken());}}

在HqlBaseLexer的nextToken()方法中,通过LA()方法获取当前char流中当前消费位置的char来进行分析,可以通过改变LA()中的参数来获得接下来的某个char元素,在通过match()方法完成相应的关键字匹配后,会通过刷新新的读取位置。完成词法分析的词也就是Token会加入在当前的缓冲流中,并记录该词在流中的起始位置与长度。

在nextToken()的词法分析流程中,首要的是对一些简单符号的分析,例如括号逗号。

switch ( LA(1)) {
case '=':
{mEQ(true);theRetToken=_returnToken;break;
}
case '!':  case '^':
{mNE(true);theRetToken=_returnToken;break;
}
case ',':
{mCOMMA(true);theRetToken=_returnToken;break;
}

在类似符号的匹配中,一旦被确认成功,例如等于号,将会通过mEQ()确认相应的Token类型并结束读取数据来分析词法。

public final void mEQ(boolean _createToken) throws RecognitionException, CharStreamException, TokenStreamException {int _ttype; Token _token=null; int _begin=text.length();_ttype = EQ;int _saveIndex;match('=');if ( _createToken && _token==null && _ttype!=Token.SKIP ) {_token = makeToken(_ttype);_token.setText(new String(text.getBuffer(), _begin, text.length()-_begin));}_returnToken = _token;
}

在mEQ()中,首先将代表词Token类型的int值设为括号EQ所对应的值(值跟之前的关键字一样有antlr根据语法文件编译后产生),之后调用match()方法进行再一次检验。

public void match(char var1) throws MismatchedCharException, CharStreamException {if (this.LA(1) != var1) {throw new MismatchedCharException(this.LA(1), var1, false, this);} else {this.consume();}
}

match()主要在进行了一次检验,如果确认通过则调用consume()方法刷新下一次的读取位置并将当前char放入缓冲流中便于之后的具体获取。

在完成match()之后,生成括号类型的Token,并记录在缓冲流中的位置与长度,返回交给语法分析器进行语法分析。

在完成对于简单符号的判断之后如果并没有匹配成功,会进行数字的相应处理。

case '.':  case '0':  case '1':  case '2':
case '3':  case '4':  case '5':  case '6':
case '7':  case '8':  case '9':
{mNUM_INT(true);theRetToken=_returnToken;break;
}

在mNUM_INT()中会对数字进行相应的词法分析。

对数字的分析相应的比较长,以小数点.的分析部分为例子。

switch ( LA(1)) {
case '.':
{match('.');if ( inputState.guessing==0 ) {_ttype = DOT;}

首先,读取到小数点之后,会通过matcjh(0方法将小数点加入到缓冲流中,并将读取位置刷新到小数点下一位置。

if (((LA(1) >= '0' && LA(1) <= '9'))) {{int _cnt353=0;_loop353:do {if (((LA(1) >= '0' && LA(1) <= '9'))) {matchRange('0','9');}else {if ( _cnt353>=1 ) { break _loop353; } else {throw new NoViableAltForCharException((char)LA(1), getFilename(), getLine(), getColumn());}}_cnt353++;} while (true);}

之后继续匹配小数点后面的char元素,如果是1到9的数字,继续通过matchRange()完成类似match()的操作来更新下一个读取的位置并加入缓冲流准备返回。

{if ((LA(1)=='e')) {mEXPONENT(false);}else {}}{if ((LA(1)=='b'||LA(1)=='d'||LA(1)=='f')) {mFLOAT_SUFFIX(true);f1=_returnToken;if ( inputState.guessing==0 ) {t=f1;}}else {}}if ( inputState.guessing==0 ) {if ( t != null && t.getText().toUpperCase().indexOf("BD")>=0) {_ttype = NUM_BIG_DECIMAL;}else if (t != null && t.getText().toUpperCase().indexOf('F')>=0) {_ttype = NUM_FLOAT;}else {_ttype = NUM_DOUBLE; // assume double}}}else {}}break;
}

之后如果匹配到e,则会mEXPONENT(),嵌套分析。

protected final void mEXPONENT(boolean _createToken) throws RecognitionException, CharStreamException, TokenStreamException {int _ttype; Token _token=null; int _begin=text.length();_ttype = EXPONENT;int _saveIndex;{match('e');}{switch ( LA(1)) {case '+':{match('+');break;}case '-':{match('-');break;}case '0':  case '1':  case '2':  case '3':case '4':  case '5':  case '6':  case '7':case '8':  case '9':{break;}default:{throw new NoViableAltForCharException((char)LA(1), getFilename(), getLine(), getColumn());}}}{int _cnt381=0;_loop381:do {if (((LA(1) >= '0' && LA(1) <= '9'))) {matchRange('0','9');}else {if ( _cnt381>=1 ) { break _loop381; } else {throw new NoViableAltForCharException((char)LA(1), getFilename(), getLine(), getColumn());}}_cnt381++;} while (true);}if ( _createToken && _token==null && _ttype!=Token.SKIP ) {_token = makeToken(_ttype);_token.setText(new String(text.getBuffer(), _begin, text.length()-_begin));}_returnToken = _token;
}

在进入mEXPONENT()方法之后,会根据match()刷新读取率位置顺势读取e之后的数据。在下面的逻辑中,如果出现了加号减号数字之外的数据,将会报错,而加号减号数字则会同样根据match()完成之前一样的更新缓冲流的操作,在e的解析完成后,由于此次是在数字中的解析,e只是数字的一部分,不会生成相应的Token所有只是简单结束并继续当前数字分析。

e之后是bdf三个字母对于数字后缀的解析,在mFLOAT_SUFFIX()方法进行处理,由于出现这种字母代表数字已经进入结尾,所以将会生成Token记录该数字的类型,并结束对于这个数字的词法分析。

if ( t != null && t.getText().toUpperCase().indexOf("BD")>=0) {_ttype = NUM_BIG_DECIMAL;
}
else if (t != null && t.getText().toUpperCase().indexOf('F')>=0) {_ttype = NUM_FLOAT;
}
else {_ttype = NUM_DOUBLE; // assume double
}

最后会根据数字的后缀来判定数字在HQL中的类型。

if ( _createToken && _token==null && _ttype!=Token.SKIP ) {_token = makeToken(_ttype);_token.setText(new String(text.getBuffer(), _begin, text.length()-_begin));
}
_returnToken = _token;

生成的数字与之前一样生成对应类型的Token,并记录在缓冲流中的开始位置与长度。

在完成数字分析,是对大于小于的词法分析,相对简单,之后是对关键字或者hql中字符常量的分析,在mIDENT()方法中完成。

public final void mIDENT(boolean _createToken) throws RecognitionException, CharStreamException, TokenStreamException {int _ttype; Token _token=null; int _begin=text.length();_ttype = IDENT;int _saveIndex;mID_START_LETTER(false);{_loop339:do {if ((_tokenSet_1.member(LA(1)))) {mID_LETTER(false);}else {break _loop339;}} while (true);}if ( inputState.guessing==0 ) {// Setting this flag allows the grammar to use keywords as identifiers, if necessary.setPossibleID(true);}_ttype = testLiteralsTable(_ttype);if ( _createToken && _token==null && _ttype!=Token.SKIP ) {_token = makeToken(_ttype);_token.setText(new String(text.getBuffer(), _begin, text.length()-_begin));}_returnToken = _token;
}

此处的重点主要在于关键字和字符常量的判定,在按照之前的步骤完成词语的读取完毕之后,调用testLiteralsTable()方法。

public int testLiteralsTable(int var1) {this.hashString.setBuffer(this.text.getBuffer(), this.text.length());Integer var2 = (Integer)this.literals.get(this.hashString);if (var2 != null) {var1 = var2;}return var1;
}

在这个方法中,从缓冲流获取读取的词在构造方法中放入的关键字的HashTable寻找,如果找到则返回相应的int值,也就是读取到了关键字,并返回关键字对应的int值。如果没有,则作为常量类型返回。

以上主要就是关于antlr对于hql的词法分析。

hibernate中antlr对于hql的词法分析源码解析相关推荐

  1. Java生鲜电商平台-SpringCloud微服务架构中网络请求性能优化与源码解析

    Java生鲜电商平台-SpringCloud微服务架构中网络请求性能优化与源码解析 说明:Java生鲜电商平台中,由于服务进行了拆分,很多的业务服务导致了请求的网络延迟与性能消耗,对应的这些问题,我们 ...

  2. Mahout中关于MultiLayer Perceptron模块的源码解析

    Mahout中关于MultiLayer Perceptron模块的源码解析 前段时间学习NN时使用到了BPNN,考虑到模型的分布式扩展,我想到使用Mahout的MultiLayer Perceptro ...

  3. Spring中AOP相关的API及源码解析,原来AOP是这样子的

    前言 之所以写这么一篇文章主要是因为下篇文章将结束Spring启动整个流程的分析,从解析配置到创建对象再到属性注入最后再将创建好的对象初始化成为一个真正意义上的Bean.因为下篇文章会涉及到AOP,所 ...

  4. usestate中的回调函数_React Hooks 源码解析(3):useState

    React 源码版本: v16.11.0 源码注释笔记: airingursb/react​github.com 在写本文之前,事先阅读了网上了一些文章,关于 Hooks 的源码解析要么过于浅显.要么 ...

  5. 深入探究JDK中Timer的使用方式与源码解析

    导言 定时器Timer的使用 构造方法 实例方法 使用方式 1. 执行时间晚于当前时间 2. 执行时间早于当前时间 3. 向Timer中添加多个任务 4. 周期性执行任务 5. 停止任务 源码解析 T ...

  6. 链表node中保存的是什么_Redis源码解析一 --链表结构

    Redis源码剖析-链表结构 1. redis中的链表 在redis中链表的应用非常广泛,例如列表键的底层实现之一就是链表.而且,在redis中的链表结构被实现成为双向链表,因此,在头部和尾部进行的操 ...

  7. Python 中 assert的使用位置及源码解析

    assert大意为:如果符合条件则继续运行,否则将报错退出. 举一简单例子: a = 30#assert a > 1assert a < 1, "a小于1出错" 再看一 ...

  8. hibernate中antlr对于hql生成抽象语法树源码解析

    Hibernate版本5.1.11FInal 以一句update语句作为例子. update com.tydhot.eninty.User set userName=:userName where u ...

  9. hibernate中antlr对于hql与sql的转换源码的一些细节

    Hibernate 5.1.11Final 关于hql中的对象类转换成表名. 在from模块里对hql抽象语法树进行匹配的时候,在path()规则会还原在对hql进行语法解析的时候生成的语法树. pu ...

最新文章

  1. 微波人体感应模块 24G 24.125g 感应开关微波传感器模块
  2. centos下使用mysql,centos下使用mysql的一些问题和解决方法
  3. lwip协议栈中超时定时器实现原理
  4. OpenGL - Normal Map
  5. 兰大202005批次计算机基础,【每日一校】2020年兰州大学 各批次录取人数与分数线统计...
  6. HDU 5832——A water problem 2016CCPC网络赛1001
  7. android 禁掉唤醒锁acquire()
  8. 【BZOJ 3652】大新闻 数位dp+期望概率dp
  9. fiddler4请求拒绝原因
  10. 计算机网络第七版 4-55
  11. 10、Halcon图像条形码和二维码识别
  12. 常见黑客渗透测试工具
  13. 以码为梦,心向远方,路在脚下|211应届计算机毕业生的迷茫
  14. linux中的/usr,/var,/opt目录详解
  15. 抖音壁纸小程序,星光壁纸小程序2.0版本,升级版
  16. pyautoGUI自动化脚本
  17. xshell调用js脚本开发
  18. MySQL下载和安装(Windows)
  19. Windows系统怎样配置PHP环境
  20. java心跳监控服务_JavaHeartBeat-应用服务器心跳检测

热门文章

  1. Zookeeper集群安装Version3.5.1
  2. Ranger-AdminServer安装
  3. 1.php查询数据,数据查询 · thinkphp5 · 看云
  4. vuedraggable嵌套块拖拽_Vue 基于 vuedraggable 实现选中、拖拽、排序效果
  5. mysql html 转义_HTML/Mysql/XML 转义字符,备查
  6. ASP.NET中密码保护,MD5和SHA1算法的使用
  7. 通过系统进程查找sql语句
  8. Vue动态传值与接收步骤
  9. Eclipse 中,web项目在Tomcat运行时填写不了Server name
  10. 利用锁分析器进行线程竞争检测