sqlite对于典型select处理流程

一、select s from f where w
这是一个最为基础的sql语句,相当于C语言的printf("hello world ");对于一个简单的应用来说,知道这个套路解决一般问题是没有难度的,在一些复杂的场景,例如涉及到subselect、join、union等各种各样的延伸问题时,如果不太清楚程序本身的执行和解析流程,那么对于问题的演绎会有阻碍。另外由于思路比较简单,喜欢模拟程序的执行来推理出一些东西,所以想看看这个典型的语句在sqlite中是如何处理的。
触发这个问题的原因在于处理一个稍微复杂点的问题:希望首先通过两个不同的table来组合成一个新的table,然后在这个新生成的table基础上来再次执行一些操作。直观的想就是在select的from中构造一张临时表,然后在from的条件中把这个表作为基础表从这张表中select,之后发现这种方法根本行不通,select from之后的表不能是任何一个通过alias表示的子select语句,由此触发了对于这个语句实现方法的一个简单学习。
问题的语法描述大致如此
mysql> select * from (select field, type from user) as newuser where newuser.field in (select field from newuser);
ERROR 1146 (42S02): Table 'mysql.newuser' doesn't exist
在where中再次从from中临时生成的newuser中select成员,此时表达式报错,说明select之后的table只能是物理存在的table,或者是create temporary创建的表格,而不能是 select as 生成的临时表格。
由于mysql的工程过于高端大气上档次,看起来比较复杂,所以依然是从最为简单的sqlite数据库相关实现来学习一下,sqlite3的代码总共大概10W行,内容并不是很多,又考虑到其中注释可能至少有1/3左右,真正的逻辑代码可能更少;sqlite的实现使用虚拟机方法,代码看起来比较学术派,对于理解数据库的实现相对来说比较容易。
二、语法解析
sqlite对于mysql的解析实现并没有使用通用的bison/yacc来生成,而是在自己的工程中包含了一个语法生成器lemon,这个工具在浙江大学有一本书剖析了它的内部实现机制,和毛德操老师的《linux内核源代码情景分析》是一个系列的,我之前还买了一本,冲着对《情景分析》的品牌,不过后的比较少,用鲁迅先生的话说:“后来大半忘却了”。
这个lemon的词法表示和bison的表示方式也不同,比较明显的特点是:
1、非终端符号小写表示,终端符号使用大写字符表示。例如在parse.y中
id(A) ::= ID(X).         {A = X;}
id(A) ::= INDEXED(X).    {A = X;}
这里的id就是非终端符号,而ID为终端符号。
2、符号引用。在bison中,动作中通过 $1之类的编号来引用语法中的标识符数值,lemon中则使用在标识符的后面一个括号内定义字符来表示对于这个标识符语法数值的引用。这两种实现的优缺点不好说,就像社会科学中大部分事件一样,有人叫好,有人拍砖。但是我们现在最后不要也不用关心这些形而上的东西。
语法文件中比较相关的内容罗列如下
cmd ::= select(X).  {
  SelectDest dest = {SRT_Output, 0, 0, 0, 0};
  sqlite3Select(pParse, X, &dest);在一个select语句被完整解析之后,执行这个语句完成sql语义
  sqlite3SelectDelete(pParse->db, X);
}
……
select(A) ::= oneselect(X).                      {A = X;}一个完整的select,也就是我们最为常见的select from where三元组
%ifndef SQLITE_OMIT_COMPOUND_SELECT
select(A) ::= select(X) multiselect_op(Y) oneselect(Z).  {
  if( Z ){
    Z->op = (u8)Y;
    Z->pPrior = X;
  }else{
    sqlite3SelectDelete(pParse->db, X);
  }
  A = Z;
}
%type multiselect_op {int}
multiselect_op(A) ::= UNION(OP).             {A = @OP;}
multiselect_op(A) ::= UNION ALL.             {A = TK_ALL;}
multiselect_op(A) ::= EXCEPT|INTERSECT(OP).  {A = @OP;}
%endif SQLITE_OMIT_COMPOUND_SELECT
oneselect(A) ::= SELECT distinct(D) selcollist(W) from(X) where_opt(Y)
                 groupby_opt(P) having_opt(Q) orderby_opt(Z) limit_opt(L). {
  A = sqlite3SelectNew(pParse,W,X,Y,P,Q,Z,D,L.pLimit,L.pOffset);
}
……
// A complete FROM clause.
//
from(A) ::= .                {A = sqlite3DbMallocZero(pParse->db, sizeof(*A));}
from(A) ::= FROM seltablist(X). {
  A = X;
  sqlite3SrcListShiftJoinType(A);
}
……
// "seltablist" is a "Select Table List" - the content of the FROM clause注释明确说明stl是select table list的缩写。
// in a SELECT statement.  "stl_prefix" is a prefix of this list.
//
stl_prefix(A) ::= seltablist(X) joinop(Y).    {
   A = X;
   if( ALWAYS(A && A->nSrc>0) ) A->a[A->nSrc-1].jointype = (u8)Y;
}
stl_prefix(A) ::= .                           {A = 0;}
seltablist(A) ::= stl_prefix(X) nm(Y) dbnm(D) as(Z) indexed_opt(I) on_opt(N) using_opt(U). {
  A = sqlite3SrcListAppendFromTerm(pParse,X,&Y,&D,&Z,0,N,U);
  sqlite3SrcListIndexedBy(pParse, A, &I);
}
%ifndef SQLITE_OMIT_SUBQUERY
  seltablist(A) ::= stl_prefix(X) LP select(S) RP 其中LP和RP不是“老婆”和“人品”,而是“Left Parenthese”和“Right Parentheses”,这就是一对括号引导的自select表达式,也就是这个语法,允许在from中通过子表达式来临时生成一个新的内存table
                    as(Z) on_opt(N) using_opt(U). {
    A = sqlite3SrcListAppendFromTerm(pParse,X,0,0,&Z,S,N,U);
  }
  seltablist(A) ::= stl_prefix(X) LP seltablist(F) RP
                    as(Z) on_opt(N) using_opt(U). {
    if( X==0 && Z.n==0 && N==0 && U==0 ){
      A = F;
    }else{
      Select *pSubquery;
      sqlite3SrcListShiftJoinType(F);
      pSubquery = sqlite3SelectNew(pParse,0,F,0,0,0,0,0,0,0);
      A = sqlite3SrcListAppendFromTerm(pParse,X,0,0,&Z,pSubquery,N,U);对于from之后的内容,通过该函数添加到FromTerm列表中,
    }
  }
sqlite3SrcListAppendFromTerm函数将解析出来的table描述以及subquery追加到Src列表的最后,这个其实就是之后整个select中使用变量及标识符的数据库作用域,这里也是不同的select语句的定界域,和C语言的变量查找一样,同一个变量要从一个context中逐层向外查找,这样才能找到正确的标识符定义。在这里,这个src也定义了不同范围内select可以使用的table的范围,嵌套或者被包含的subselect只能向本层或者外层链式查找符号范围(如果这个符号是一个table列的时候)。
三、select语句的处理
在之前已经看到,select处理由sqlite3Select函数完成,在该函数中,对于表达式、table、database的处理在函数
 sqlite3SelectPrep(pParse, p, 0);
中完成。
该函数主要执行流程为
SQLITE_PRIVATE void sqlite3SelectPrep(
  Parse *pParse,         /* The parser context */
  Select *p,             /* The SELECT statement being coded. */
  NameContext *pOuterNC  /* Name context for container */
){
  sqlite3 *db;
  if( NEVER(p==0) ) return;
  db = pParse->db;
  if( p->selFlags & SF_HasTypeInfo ) return;
  sqlite3SelectExpand(pParse, p);  这里对select之后的内容进行展开,包括了对于所有的select以及subselect中引用到的table的定位(table的定位通过sqlite3LocateTable函数完成),该函数还完成对于select之后通配符'*'的展开,展开为一个table的所有列的名字
  if( pParse->nErr || db->mallocFailed ) return;
  sqlite3ResolveSelectNames(pParse, p, pOuterNC);该函数完成对于表达式中各个变量的解析,解析的时候就会涉及到对于变量所在table的作用域的查询。这里pOuterNC中的NC即为Name Context的缩写,这个链表并不在解析是链接,而是在最终执行之后动态创建并链接自己需要的NC列表
  if( pParse->nErr || db->mallocFailed ) return;
  sqlite3SelectAddTypeInfo(pParse, p);
}
四、NC链表的建立
1、单步骤遍历
在对select结构的遍历(walker)过程中,其中对于子select处理的回调函数为resolveSelectStep,在这个函数中创建了一个堆栈上的NameContext结构,在对这个结构初始化之后,将这个结果作为子select的OuterNC传递给subselect,从而在堆栈上建立一个链表结构。这种在堆栈上动态创建结构在之前也见过,就是Matt Piertrek大牛写的《A Crash Course on the Depths of Win32? Structured Exception Handling》,异常的处理也是在堆栈上建立;另外一个相似的例子就是内核中通过DEFINE_WAIT建立的等待结构,通常也是在堆栈中临时创建,函数返回之后销毁,环保可递归。
static int resolveSelectStep(Walker *pWalker, Select *p){
  NameContext *pOuterNC;  /* Context that contains this SELECT */
  NameContext sNC;        /* Name context of this SELECT */
……
    /* Resolve names in the result set. */
    pEList = p->pEList;
    assert( pEList!=0 );
    for(i=0; i<pEList->nExpr; i++){
      Expr *pX = pEList->a[i].pExpr;
      if( sqlite3ResolveExprNames(&sNC, pX) ){把自己作为新的链表上NC结构,传递给表达式解析
        return WRC_Abort;
      }
    }
……
    /* Recursively resolve names in all subqueries
    */
    for(i=0; i<p->pSrc->nSrc; i++){
      struct SrcList_item *pItem = &p->pSrc->a[i];
      if( pItem->pSelect ){
        const char *zSavedContext = pParse->zAuthContext;
        if( pItem->zName ) pParse->zAuthContext = pItem->zName;
        sqlite3ResolveSelectNames(pParse, pItem->pSelect, pOuterNC);这里对于subquery中的表达式,它只能使用外层的NameContext,而不能使用本层的NameContext
        pParse->zAuthContext = zSavedContext;
        if( pParse->nErr || db->mallocFailed ) return WRC_Abort;
      }
    }
……
    /* Add the expression list to the name-context before parsing the
    ** other expressions in the SELECT statement. This is so that
    ** expressions in the WHERE clause (etc.) can refer to expressions by
    ** aliases in the result set.
    **
    ** Minor point: If this is the case, then the expression will be
    ** re-evaluated for each reference to it.
    */
    sNC.pEList = p->pEList;
    if( sqlite3ResolveExprNames(&sNC, p->pWhere) ||
       sqlite3ResolveExprNames(&sNC, p->pHaving) 对于where和having的处理也是按照从当前NameContex中查找
    ){
      return WRC_Abort;
    }
2、sqlite3SelectPrep函数流程
SQLITE_PRIVATE void sqlite3SelectPrep(
  Parse *pParse,         /* The parser context */
  Select *p,             /* The SELECT statement being coded. */
  NameContext *pOuterNC  /* Name context for container */
){
  sqlite3 *db;
  if( NEVER(p==0) ) return;
  db = pParse->db;
  if( p->selFlags & SF_HasTypeInfo ) return;
  sqlite3SelectExpand(pParse, p);
  if( pParse->nErr || db->mallocFailed ) return;
  sqlite3ResolveSelectNames(pParse, p, pOuterNC);
  if( pParse->nErr || db->mallocFailed ) return;
  sqlite3SelectAddTypeInfo(pParse, p);
}
2、1 对于from之后整个select可见table和column的处理
这个函数包含了对整个select语句树形结构的两次完成遍历,第一次对所有的select按照深度优先方法遍历所有的select语句,这一次只是处理select语句from中的有名表格和无名表格(此时并没有处理select之后和where之后的表达式),整个过程相当于初始化了整个select中共享可见的table及column结构。
2、2 对于select之后和where之后表达式中处理
但是这里有个问题,就是它只是处理了from之后可能存在的subquery虚拟表,而select之后和where之后的表达式同样可以存在subquery,这个表格在sqlite3SelectExpand函数的这次遍历中并没有被处理到。
在sqlite3ResolveSelectNames对于语法树的遍历过程中,此时在对select之后和where之后的表达式进行变量的定位,这个函数将会首先对剩余的、可能存在的subquery进行展开,这一点在该函数的注释中也有说明

  /* Normally sqlite3SelectExpand() will be called first and will have             通常来说sqlite3SelectExpand函数之后select已经展开
  ** already expanded this SELECT.  However, if this is a subquery within 但是如果表达式中有subquery
  ** an expression, sqlite3ResolveExprNames() will be called without a 调用sqlite3ResolveExprNames函数之前并没有
  ** prior call to sqlite3SelectExpand().  When that happens, let              调用sqlite3SelectExpand,当这种情况发生时
  ** sqlite3SelectPrep() do all of the processing for this SELECT.  将会由sqlite3SelectPrep来完成对SELECT的所有处理。
  ** sqlite3SelectPrep() will invoke both sqlite3SelectExpand() and函数sqlite3SelectPrep将会正确同时调用sqlite3SelectExpand
  ** this routine in the correct order. 和本函数。
  */
  if( (p->selFlags & SF_Expanded)==0 ){
    sqlite3SelectPrep(pParse, p, pOuterNC);
    return (pParse->nErr || db->mallocFailed) ? WRC_Abort : WRC_Prune;
  }
2、3  sqlite3WalkSelect函数流程
该函数遍历表达式中所有并列的select语句,对于每个一select语句,首先执行一次xSelectCallback回调,在这个回调中用户可以提供方式来进行一些整个select的全局性的处理工作,例如之前看到的from之后table的定位;然后遍历select之后以及where之后以及各种group之类的所有表达式,最后再处理from之后的subquery。
我们看下显式的例子
sqlite> select first in (select first from noexit) as dupfirst from (select first from neigtherexit) as fromfirst;
Error: no such table: noexit
sqlite> 
可以看到 select之后表达式的错误提示要遭遇from之后错误错误表达式的提示。至于sqlite3WalkSelect为什么要把from中的subquery单独处理,可能是为了处理两种类型对from之后table的不同可见作用域吧,比方说from之后并列的table内subquery不可见其它table,待确定。

/*
** Call sqlite3WalkExpr() for every expression in Select statement p.
** Invoke sqlite3WalkSelect() for subqueries in the FROM clause and
** on the compound select chain, p->pPrior.
**
** Return WRC_Continue under normal conditions.  Return WRC_Abort if
** there is an abort request.
**
** If the Walker does not have an xSelectCallback() then this routine
** is a no-op returning WRC_Continue.
*/
SQLITE_PRIVATE int sqlite3WalkSelect(Walker *pWalker, Select *p){
  int rc;
  if( p==0 || pWalker->xSelectCallback==0 ) return WRC_Continue;
  rc = WRC_Continue;
  while( p  ){
    rc = pWalker->xSelectCallback(pWalker, p);
    if( rc ) break;
    if( sqlite3WalkSelectExpr(pWalker, p) ) return WRC_Abort;
    if( sqlite3WalkSelectFrom(pWalker, p) ) return WRC_Abort;
    p = p->pPrior;
  }
  return rc & WRC_Abort;
}
五、表达式中变量的定位
resolveExprStep
    /* A lone identifier is the name of a column.
    */
    case TK_ID: {
      return lookupName(pParse, 0, 0, pExpr->u.zToken, pNC, pExpr);
    }
在接下来的lookupName函数中
  /* Start at the inner-most context and move outward until a match is found */
  while( pNC && cnt==0 ){
    ExprList *pEList;
    SrcList *pSrcList = pNC->pSrcList;

    if( pSrcList ){
      for(i=0, pItem=pSrcList->a; i<pSrcList->nSrc; i++, pItem++){ 对于from中的每一个table遍历
        Table *pTab;
        int iDb;
        Column *pCol;
  
        pTab = pItem->pTab;
        assert( pTab!=0 && pTab->zName!=0 );
        iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
        assert( pTab->nCol>0 );
        if( zTab ){
          if( pItem->zAlias ){ 存在alias名称
            char *zTabName = pItem->zAlias;
            if( sqlite3StrICmp(zTabName, zTab)!=0 ) continue;
          }else{真是table名
            char *zTabName = pTab->zName;
            if( NEVER(zTabName==0) || sqlite3StrICmp(zTabName, zTab)!=0 ){
              continue;
            }
            if( zDb!=0 && sqlite3StrICmp(db->aDb[iDb].zName, zDb)!=0 ){
              continue;
            }
          }
        }
        if( 0==(cntTab++) ){
          pExpr->iTable = pItem->iCursor;
          pExpr->pTab = pTab;
          pSchema = pTab->pSchema;
          pMatch = pItem;
        }
        for(j=0, pCol=pTab->aCol; j<pTab->nCol; j++, pCol++){遍历table的所有列,也就是所有field
          if( sqlite3StrICmp(pCol->zName, zCol)==0 ){列名称想等,认为找到一个匹配
            IdList *pUsing;
            cnt++;
            pExpr->iTable = pItem->iCursor;
            pExpr->pTab = pTab;
            pMatch = pItem;
            pSchema = pTab->pSchema;
            /* Substitute the rowid (column -1) for the INTEGER PRIMARY KEY */
            pExpr->iColumn = j==pTab->iPKey ? -1 : (i16)j;
            if( i<pSrcList->nSrc-1 ){
              if( pItem[1].jointype & JT_NATURAL ){
                /* If this match occurred in the left table of a natural join,
                ** then skip the right table to avoid a duplicate match */
                pItem++;
                i++;
              }else if( (pUsing = pItem[1].pUsing)!=0 ){
                /* If this match occurs on a column that is in the USING clause
                ** of a join, skip the search of the right table of the join
                ** to avoid a duplicate match there. */
                int k;
                for(k=0; k<pUsing->nId; k++){
                  if( sqlite3StrICmp(pUsing->a[k].zName, zCol)==0 ){
                    pItem++;
                    i++;
                    break;
                  }
                }
              }
            }
            break;
          }
        }
      }
    }
……
    /* Advance to the next name context.  The loop will exit when either
    ** we have a match (cnt>0) or when we run out of name contexts.
    */
    if( cnt==0 ){
      pNC = pNC->pNext;向更外层的NameContext查找变量的定义,这个地方使用循环即可,没有递归
    }
  }
六、一些验证
1、from之后table的解析早于select之后表达式的解析
这点其实不用验证,有脚趾头想一下就可以想到,先查找table是否存在,然后验证select的内容是否存在
sqlite> .schema
CREATE TABLE tsecer(first int, second int, third varchar(100));
sqlite> select first from nothistable;
Error: no such table: nothistable
sqlite> select nothiscom from tsecer;
Error: no such column: nothiscom
sqlite> 
2、select和where可以使用from中的table,而同级的from之后subquery不能
sqlite> select * from tsecer as harry where first in (select first from tsecer where first=harry.first); 在where中可以引用from中所有的table
sqlite> select * from tsecer as harry, (select * from tsecer where first=harry.first); 同级的from中不能引用同级的table
Error: no such column: harry.first
sqlite> 
3、union两侧在同一个src列表中且可递归
// "seltablist" is a "Select Table List" - the content of the FROM clause
// in a SELECT statement.  "stl_prefix" is a prefix of this list.
//
stl_prefix(A) ::= seltablist(X) joinop(Y).    {
   A = X;
   if( ALWAYS(A && A->nSrc>0) ) A->a[A->nSrc-1].jointype = (u8)Y;
}
stl_prefix(A) ::= .                           {A = 0;}
seltablist(A) ::= stl_prefix(X) nm(Y) dbnm(D) as(Z) indexed_opt(I) on_opt(N) using_opt(U). {
  A = sqlite3SrcListAppendFromTerm(pParse,X,&Y,&D,&Z,0,N,U);
  sqlite3SrcListIndexedBy(pParse, A, &I);
}
对于
select something otherthing
from table1 join table2 on table1.f1==table2.f2
这样的语句,
table1 join 被解析为
stl_prefix(A) ::= seltablist(X) joinop(Y).    
部分,而able1 join table2 on table1.f1==table2.f2 则被整体解析为
seltablist(A) ::= stl_prefix(X) nm(Y) dbnm(D) as(Z) indexed_opt(I) on_opt(N) using_opt(U). 
在后面一个seltablist对应的动作中,执行了
A = sqlite3SrcListAppendFromTerm(pParse,X,&Y,&D,&Z,0,N,U);
这也就是说,join串联起来的所有table的on中可以用所有的table名称:
sqlite> select * from tsecer as harry join tsecer as har join tsecer as ray on harry.first = ray.second;
sqlite> 
这个表达式并不会出现解析错误,虽然本身没有任何意义。在表达式的最后引用了join的第一个和最后一个table的alias。
4、from后的table如果是字符串,必须为物理表或临时表,而不能是alias表
sqlite3LocateTable--->>sqlite3FindTable,这个函数不会查找任何alias表示的表格,其核心代码为
SQLITE_PRIVATE Table *sqlite3FindTable(sqlite3 *db, const char *zName, const char *zDatabase){
  Table *p = 0;
  int i;
  int nName;
  assert( zName!=0 );
  nName = sqlite3Strlen30(zName);
  for(i=OMIT_TEMPDB; i<db->nDb; i++){
    int j = (i<2) ? i^1 : i;   /* Search TEMP before MAIN */
    if( zDatabase!=0 && sqlite3StrICmp(zDatabase, db->aDb[j].zName) ) continue;
    p = sqlite3HashFind(&db->aDb[j].pSchema->tblHash, zName, nName);
    if( p ) break;
  }
  return p;
}
所以下面句子错误,这一点在mysql中同样成立
sqlite> select * from tsecer as harry where harry.first in (select first from harry);
Error: no such table: harry
sqlite>
原文地址:https://www.cnblogs.com/tsecer/p/10487877.html