在(1)裡面提到,lexer作為一個工具,完成了對SQL字串的切割,將語句轉化成一個tokens數組。
Parser完成了SQL解析的後序部分:使用一個lexer對象作為工具,切出tokens,然後解析語義,綁定相關的系統介面。
在這裡先要回顧下simpledb的支援的SQL的文法,這個影響了它在解析字串時使用的方法。
<Query> := SELECT <SelectList> FROM <TableList> [ WHERE <Predicate> ]
<SelectList> := <Field> [ , <SelectList> ]
<TableList> := TableName [ , <TableList> ]
<Predicate> := <Term> [ AND <Predicate> ]
<Term> := <Expression> = <Expression>
<Expression> := <Field> | <Constant>
<Field> := FieldName
<Constant> := String | Integer
<Modify> := <Insert> | <Delete> | <Update>
<Insert> := INSERT INTO TableName ( <FieldList> ) VALUES ( <ConstList> )
<FieldList> := <Field> [ , <FieldList> ]
<ConstList> := <Constant> [ , <Constant> ]
<Delete> := DELETE FROM TableName [ WHERE <Predicate> ]
<Update> := UPDATE TableName SET <Field> = <Expression> [ WHERE <Predicate> ]
<Create> := <CreateTable> | <CreateView> | <CreateIndex>
<CreateTable>:= CREATE TABLE TableName ( <FieldDefs> )
<FieldDefs> := <FieldDef> [ , <FieldDefs> ]
<FieldDef> := FieldName <TypeDef>
<TypeDef> := INT | VARCHAR ( Integer )
<CreateView> := CREATE VIEW ViewName AS <Query>
<CreateIndex>:= CREATE INDEX IndexName ON TableName ( <Field> )
Parser這裡實現的編譯技術中的“一趟編譯”,即通過lex順序掃描tokens數組,在掃描過程中完成對各個token元素的識別,按照
constant/field –> Expression –> Term –> Predicate
的層遞關係,完成參數封裝,輸出一個查詢關聯的資料類,如(1)中的類圖1所示。
首先,以(1)圖2的例子“select sid,sname from students where sid='10001' ”,給出一個parser解析過程中構建的文法樹:
圖1 樣本文法樹
以下是Query方法的代碼:
public QueryData query()
{
lex.eatKeyword("select");
List<string> fields = selectList();
lex.eatKeyword("from");
List<string> tables = tableList();
Predicate pred = new Predicate();
if (lex.matchKeyword("where"))
{
lex.eatKeyword("where");
pred = predicate();
}
return new QueryData(fields, tables, pred);
}
由代碼和上面的文法樹看出來,simpledb在解析SQL語句的時候,嚴格按照文法中支援的類型,“卡住”關鍵字,從中解析出欄位列表fieldlist,表名列表tablelist,以及謂詞列表predicates,然後將這些查詢中實際用到的資料,封裝成相應的對象,SQL語句的解析就初步完成。以上面的例子為例,QueryData對象封裝完之後,會傳遞給查詢處理模組,通過query下的一些方法,根據fieldlist,tablelist,和predicates來完成查詢資料的讀取。
parser中create(), delete(), insert(),query(), modify()方法,對應了上面的幾類文法
作為SQL語句解析的入口,有如下的updateCMD方法,根據SQL語句首個token的不同,進行了分支:
public object updateCmd()
{
if (lex.matchKeyword("insert"))
return insert();
else if (lex.matchKeyword("delete"))
return delete();
else if (lex.matchKeyword("update"))
return modify();
else
return create();
}
補充一點,注意下上面的query()的代碼,lex用match*()來檢測下一個token是否滿足匹配條件,用eat*()來講滿足條件的token處理掉:
public void eatDelim(char d);
public string eatId();
public int eatIntConstant()
public void eatKeyword(string w)
public string eatStringConstant()
資料相關的,均有傳回值,返回處理後的結果;資料無關的,沒有傳回值,實際只是利用nextToken移動position指標。
關於tablelist、fieldlist的構建,使用了遞迴的方式實現:
private List<string> fieldList()
{
List<string> l = new List<string>();
l.Add(field());
if (lex.matchDelim(','))
{
lex.eatDelim(',');
l.AddRange(fieldList());
}
return l;
}
謂詞 predicates也是用了遞迴的形式:
private Predicate predicate()
{
Predicate pred = new Predicate(term());
if (lex.matchKeyword("and"))
{
lex.eatKeyword("and");
pred.conjoinWith(predicate());
}
return pred;
}
略有不同的是,在謂詞串連的時候,使用了Predicate類的conjoinwith方法,實際上,predicate對象下,維護了一個條件列表terms,這個方法就是把謂詞中的各個term轉存到一起。
本文和前一節只是扼要地描述了SQL語句解析的思路和過程,可以看到解析的結果就是產生了各種查詢data,這些data會傳遞給query模組,由query模組利用這些得到的data,完成資料的查詢。