nhibernate源码分析之八: 解析HQL
作者:张老三
本文将对HQL查询文本的解析过程进行分析,这个可以说是NH中比较复杂的一块了(个人认为),涉及到的类也比较多。
建议阅读之前先深呼吸十下,看完之后脑袋成浆糊可不要找我哟。:-)
在HQL数据加载一文中,我们有提到QueryTranslator的创建过程,代码如下:
//*** SessionFactoryImpl.cs 429行 ***
private QueryTranslator GetQuery(string query, bool shallow)
{
QueryCacheKey cacheKey = new QueryCacheKey(query, shallow);
QueryTranslator q = (QueryTranslator) Get(cacheKey);
if ( q==null) {
q = new QueryTranslator(dialect);
Put(cacheKey, q);
}
q.Compile(this, query, querySubstitutions, shallow);
return q;
}
当创建(或从缓存取得)QueryTranslator对象后,就调用它的Compile方法对HQL文本进行解析,
参数querySubstitutions是用于指定要替换的关健字,它可在配置文件中给出。
//*** QueryTranslator.cs 116行 ***
[MethodImpl(MethodImplOptions.Synchronized)]
public void Compile(ISessionFactoryImplementor factory, string queryString,
IDictionary replacements, bool scalar) {
if (!compiled) {
this.factory = factory;
this.replacements = replacements;
this.shallowQuery = scalar;
Compile(queryString);
}
}
这个方法上加上了Synchronized选项,指定这是一个单线程的方法(同一时间只能有一个线程执行此方法,与使用Lock是等价的),不知出于什么原因考虑?
//*** QueryTranslator.cs 132行 ***
protected void Compile(string queryString) {
this.queryString = queryString;
try {
ParserHelper.Parse(
new PreprocessingParser(replacements),
queryString,
ParserHelper.HqlSeparators,
this);
RenderSql();
}
catch (QueryException qe) {
qe.QueryString = queryString;
throw qe;
}
catch (MappingException me)
{
throw me;
}
catch (Exception e)
{
log.Debug("unexpected query compilation problem", e);
QueryException qe = new QueryException("Incorrect query syntax", e);
qe.QueryString = queryString;
throw qe;
}
PostInstantiate();
compiled = true;
}
将解析HQL文本的任务交给ParserHelper处理,这是一个用于解析的帮助类,注意Parser方法的第一个参数是一个PreprocessingParser对象。
//*** ParserHelper.cs 25行 ***
public static void Parse(IParser p, string text, string seperators, QueryTranslator q)
{
StringTokenizer tokens = new StringTokenizer(text, seperators, true);
p.Start(q);
foreach(string token in tokens) {
p.Token(token, q);
}
p.End(q);
}
首先创建一个StringTokenizer对象,这是一个迭代HQL文本的类,稍后有详细分析。然后启动解析器,并迭代处理HQL文本片断。
所有的处理HQL文本片断的解析器都实现了IParser接口,
public interface IParser {
void Token(string token, QueryTranslator q);
void Start(QueryTranslator q);
void End(QueryTranslator q);
}
StringTokenizer类
StringTokenizer类是一个实现了枚举接口IEnumerable的类, 用于迭代HQL文本中的片断,下面详细说明一下。
public class StringTokenizer : IEnumerable {
//*** 28行 ***
public StringTokenizer(string str, string delim, bool returnDelims) {
_origin = str; // 要处理的HQL查询文本。
_delim = delim; // 分隔符, 迭代器以分隔符为准返回片断。
// HQL分隔符在ParseHelper中定义,为" /n/r/f/t,()=<>&|+-=/*'^![]#~//"
_returnDelim = returnDelims; // 指定是否返回分隔符。
}
public IEnumerator GetEnumerator() {
return new StringTokenizerEnumerator(this);
}
枚举器,可用于foreach操作。
private class StringTokenizerEnumerator : IEnumerator {
private StringTokenizer _stokenizer;
private int _cursor = 0;
private String _next = null;
// 此处省略部分代码...
private string GetNext() {
char c;
bool isDelim;
// 检查当前光标是否已超出HQL文本的长度.
if( _cursor >= _stokenizer._origin.Length )
return null;
c = _stokenizer._origin[_cursor];
isDelim = (_stokenizer._delim.IndexOf(c) != -1);
// 检查当前字符是否为HQL分隔符.
if ( isDelim ) {
_cursor++;
if ( _stokenizer._returnDelim ) {
return c.ToString(); // 如允许返回分隔符,则返回它。
}
return GetNext(); // 处理下一片断。
}
// 取得下一分隔符的位置。
int nextDelimPos = _stokenizer._origin.IndexOfAny(_stokenizer._delim.ToCharArray(), _cursor);
if (nextDelimPos == -1) {
nextDelimPos = _stokenizer._origin.Length;
}
// 取得当前光标至下一分隔符之前的字符串片断并返回。
string nextToken = _stokenizer._origin.Substring(_cursor, nextDelimPos - _cursor);
_cursor = nextDelimPos;
return nextToken;
}
}
}
现在就不难理解ParserHelper.Parse方法中的这个代码了,
foreach(string token in tokens) {
p.Token(token, q);
}
这里的token就是我们从枚举器中取得的字符串片断了。
//*** PreprocessingParser.cs 58行 ***
public void Token(string token, QueryTranslator q) {
//handle quoted strings
if (quoted) {
quotedString.Append(token);
}
if ("'".Equals(token)) {
if (quoted) {
token = quotedString.ToString();
}
else {
quotedString = new StringBuilder(20).Append(token);
}
quoted = !quoted;
}
if (quoted) return;
//ignore whitespace
if (ParserHelper.IsWhitespace(token)) return;
//do replacements
string substoken = (string) replacements[token];
token = (substoken == null) ? token : substoken;
//handle HQL2 collection syntax
if (currentCollectionProp != null) {
if (StringHelper.OpenParen.Equals(token)) {
return;
}
else if (StringHelper.ClosedParen.Equals(token)) {
currentCollectionProp = null;
return ;
}
else {
token += StringHelper.Dot + currentCollectionProp;
}
}
else {
string prop = (string) collectionProps[token.ToLower()];
if (prop != null) {
currentCollectionProp = prop;
return ;
}
}
//handle <=, >=, !=, is not, not between, not in
if (lastToken == null) {
lastToken = token;
}
else {
string doubleToken = (token.Length > 1)?
lastToken + ' ' + token :
lastToken + token;
if (operators.Contains(doubleToken.ToLower())) {
parser.Token(doubleToken, q);
lastToken = null;
}
else {
parser.Token(lastToken, q);
lastToken = token;
}
}
}
PreprocessingParser解析器主要处理引号、空格、替换、集合以及一些操作符,parser是ClauseParser的实例,用于后续的解析处理。
//*** ClauseParser.cs 19行 ***
public virtual void Token(string token, QueryTranslator q)
{
string lcToken = token.ToLower();
if ( token.Equals(StringHelper.OpenParen ) ) {
parenCount++;
}
else if ( token.Equals(StringHelper.ClosedParen ) ) {
parenCount--;
}
if (byExpected && !lcToken.Equals("by")) {
throw new QueryException("BY expected after GROUP or ORDER: " + token);
}
bool isClauseStart = parenCount==0; //ignore subselect keywords
if (isClauseStart) {
if (lcToken.Equals("select")) {
selectTokens = new ArrayList();
cacheSelectTokens = true;
}
else if (lcToken.Equals("from")) {
child = new FromParser();
child.Start(q);
cacheSelectTokens = false;
}
else if (lcToken.Equals("where")) {
EndChild(q);
child = new WhereParser(q.dialect);
child.Start(q);
}
else if (lcToken.Equals("order")) {
EndChild(q);
child = new OrderByParser();
byExpected = true;
}
else if (lcToken.Equals("having")) {
EndChild(q);
child = new HavingParser(q.dialect);
child.Start(q);
}
else if (lcToken.Equals("group")) {
EndChild(q);
child = new GroupByParser();
byExpected = true;
}
else if (lcToken.Equals("by")) {
if (!byExpected) throw new QueryException("GROUP or ORDER expected before BY");
child.Start(q);
byExpected = false;
}
else {
isClauseStart = false;
}
}
if (!isClauseStart) {
if (cacheSelectTokens) {
selectTokens.Add(token);
}
else {
if (child == null) {
throw new QueryException("query must begin with SELECT or FROM: " + token);
}
else {
child.Token(token, q);
}
}
}
}
在这里,根据不同token, 创建不同的子解析器进行解析。
//*** QueryTranslator.cs 522行 ***
private void RenderSql() {
int rtsize;
if (returnTypes.Count == 0 && scalarTypes.Count == 0) {
//ie no select clause in HQL
returnTypes = fromTypes;
rtsize = returnTypes.Count;
}
else {
rtsize = returnTypes.Count;
foreach(string entityName in entitiesToFetch) {
returnTypes.Add(entityName);
}
}
int size = returnTypes.Count;
names = new string[size];
persisters = new IQueryable[size];
suffixes = new string[size];
includeInSelect = new bool[size];
for (int i=0; i<size; i++) {
string name = (string) returnTypes[i];
persisters[i] = GetPersisterForName(name);
suffixes[i] = (size==1) ? String.Empty :
i.ToString() + StringHelper.Underscore;
names[i] = name;
includeInSelect[i] = !entitiesToFetch.Contains(name);
if ( includeInSelect[i] ) selectLength++;
if ( name.Equals(collectionOwnerName) ) collectionOwnerColumn = i;
}
string scalarSelect = RenderScalarSelect();
int scalarSize = scalarTypes.Count;
hasScalars = scalarTypes.Count!=rtsize;
types = new IType[scalarSize];
for (int i=0; i<scalarSize; i++ ) {
types[i] = (IType) scalarTypes[i];
}
QuerySelect sql = new QuerySelect( factory.Dialect );
sql.Distinct = distinct;
if ( !shallowQuery ) {
RenderIdentifierSelect(sql);
RenderPropertiesSelect(sql);
}
if ( CollectionPersister!=null ) {
sql.AddSelectFragmentString( collectionPersister.MultiselectClauseFragment(fetchName) );
}
if ( hasScalars || shallowQuery ) sql.AddSelectFragmentString(scalarSelect);
MergeJoins( sql.JoinFragment );
sql.SetWhereTokens(whereTokens);
sql.SetGroupByTokens(groupByTokens);
sql.SetHavingTokens(havingTokens);
sql.SetOrderByTokens(orderByTokens);
if ( CollectionPersister!=null && CollectionPersister.HasOrdering ) {
sql.AddOrderBy( CollectionPersister.GetSQLOrderByString(fetchName) );
}
scalarColumnNames = GenerateColumnNames(types, factory);
// initialize the set of queries identifer spaces
foreach(string name in collections.Values) {
CollectionPersister p = GetCollectionPersister(name);
AddIdentifierSpace( p.QualifiedTableName );
}
foreach(string name in typeMap.Keys) {
IQueryable p = GetPersisterForName( name );
AddIdentifierSpace( p.IdentifierSpace );
}
sqlString = sql.ToQuerySqlString();
System.Type[] classes = new System.Type[types.Length];
for (int i=0; i<types.Length; i++) {
if (types[i]!=null) classes[i] = (types[i] is PrimitiveType) ?
((PrimitiveType) types[i]).PrimitiveClass :
types[i].ReturnedClass;
}
try {
if (holderClass!=null) holderConstructor = holderClass.GetConstructor(classes);
}
catch(Exception nsme) {
throw new QueryException("could not find constructor for: " + holderClass.Name, nsme);
}
}
产生一个SqlString对象,并取得要返回的实体和字段。
解析器
下面用一些实际的HQL查询文本来分析解析器的处理过程。
一. " from User "
这应该算是一个最简单的HQL了, 以下内容所述的分发均指从ProcessingParser.Token中分派到ClauseParser的字符串片断。
第一次分发,得到一个"form"(空格被忽略),ClauseParser.Token中以下代码被执行:
else if (lcToken.Equals("from")) {
child = new FromParser();
child.Start(q);
cacheSelectTokens = false;
}
创建一个FormParser子解析器并启动。
注意:因为isClauseStart为true, 所以,child.Token并不会被调用。
第二次分发,得到一个"User", ClauseParser.Token中以下代码被执行:
else {
isClauseStart = false;
}
设置isClauseStart为false, 所以,child.Token被调用。
//*** FormParser.cs 38行 ***
public void Token(string token, QueryTranslator q) {
// start by looking for HQL keywords....
string lcToken = token.ToLower();
if ( lcToken.Equals(StringHelper.Comma) ) {
}
else if ( lcToken.Equals("join") ) {
}
else if ( lcToken.Equals("fetch") ) {
}
else if ( lcToken.Equals("outer") ) {
}
else if ( joinTypes.Contains(lcToken) ) {
}
else if (lcToken.Equals("class")) {
}
else if ( lcToken.Equals("in") ) {
}
else if ( lcToken.Equals("as") ) {
}
else {
if ( afterAs || expectingAs ) {
}
else if (afterIn) {
}
else {
IQueryable p = q.GetPersisterUsingImports(token);
if (p!=null) {
// starts with the name of a mapped class (new style)
if (joinType!=JoinType.None) throw new QueryException("...");
entityName = q.CreateNameFor( p.MappedClass );
q.AddFromClass( entityName, p );
expectingAs = true;
}
else if ( token.IndexOf('.') < 0 ) {
}
else {
}
}
}
}
上面的代码只给出了我们的例子会执行的代码,其余的部分都省略了,可参见源码。
首先通过token取类持久化对象,在本例中,得到的是User对象的类持久化对象,然后根据映射的类建立一个实体名称,最后将实体名称加入到QueryTranslator的formTypes集合中。