现在的位置: 首页 > 综合 > 正文

nhibernate源码分析之八: 解析HQL

2013年12月04日 ⁄ 综合 ⁄ 共 11497字 ⁄ 字号 评论关闭

                                      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集合中。

抱歉!评论已关闭.