1、select s from f where w
這是一個最爲基礎的sql語句,至關於C語言的printf("hello world\n");對於一個簡單的應用來講,知道這個套路解決通常問題是沒有難度的,在一些複雜的場景,例如涉及到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的實現使用虛擬機方法,代碼看起來比較學術派,對於理解數據庫的實現相對來講比較容易。
2、語法解析
sqlite對於mysql的解析實現並無使用通用的bison/yacc來生成,而是在本身的工程中包含了一個語法生成器lemon,這個工具在浙江大學有一本書剖析了它的內部實現機制,和毛德操老師的《linux內核源代碼情景分析》是一個系列的,我以前還買了一本,衝着對《情景分析》的品牌,不事後的比較少,用魯迅先生的話說:「後來大半忘卻了」。
這個lemon的詞法表示和bison的表示方式也不一樣,比較明顯的特色是:
一、非終端符號小寫表示,終端符號使用大寫字符表示。例如在parse.y中
id(A) ::= ID(X). {A = X;}
id(A) ::= INDEXED(X). {A = X;}
這裏的id就是非終端符號,而ID爲終端符號。
二、符號引用。在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列的時候)。
3、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);
}
4、NC鏈表的創建
一、單步驟遍歷
在對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;
}
二、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);
}
二、1 對於from以後整個select可見table和column的處理
這個函數包含了對整個select語句樹形結構的兩次完成遍歷,第一次對全部的select按照深度優先方法遍歷全部的select語句,這一次只是處理select語句from中的有名錶格和無名錶格(此時並無處理select以後和where以後的表達式),整個過程至關於初始化了整個select中共享可見的table及column結構。
二、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;
}
二、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;
}
5、表達式中變量的定位
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查找變量的定義,這個地方使用循環便可,沒有遞歸。
}
}
6、一些驗證
一、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>
二、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>
三、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。
四、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>