前面的幾個部分,基本實現了資料的讀寫。對於資料庫使用者來說,SQL(Structured Query Language)是訪問資料庫的視窗,這一節就來看下simpledb對SQL語句的解析。
首先要說的是,simpledb實現了一個SQL-92的子集,具體支援的文法如下所示:
1:
2: <Query> := SELECT <SelectList> FROM <TableList> [ WHERE <Predicate> ]
3: <SelectList> := <Field> [ , <SelectList> ]
4: <TableList> := TableName [ , <TableList> ]
5: <Predicate> := <Term> [ AND <Predicate> ]
6: <Term> := <Expression> = <Expression>
7: <Expression> := <Field> | <Constant>
8: <Field> := FieldName
9: <Constant> := String | Integer
10:
11: <Modify> := <Insert> | <Delete> | <Update>
12: <Insert> := INSERT INTO TableName ( <FieldList> ) VALUES ( <ConstList> )
13: <FieldList> := <Field> [ , <FieldList> ]
14: <ConstList> := <Constant> [ , <Constant> ]
15: <Delete> := DELETE FROM TableName [ WHERE <Predicate> ]
16: <Update> := UPDATE TableName SET <Field> = <Expression> [ WHERE <Predicate> ]
17:
18: <Create> := <CreateTable> | <CreateView> | <CreateIndex>
19: <CreateTable>:= CREATE TABLE TableName ( <FieldDefs> )
20: <FieldDefs> := <FieldDef> [ , <FieldDefs> ]
21: <FieldDef> := FieldName <TypeDef>
22: <TypeDef> := INT | VARCHAR ( Integer )
23: <CreateView> := CREATE VIEW ViewName AS <Query>
24: <CreateIndex>:= CREATE INDEX IndexName ON TableName ( <Field> )
simpledb中的parse模組完成瞭解析的功能,類圖如下:
圖1 parse類圖
在開始之間,先想一下SQL語句的解析,主要的工作是:
通過對輸入SQL語句字串的分割等工作,
首先,對輸入SQL語句字串的分割等工作;
然後,識別出語句執行的是CRUD中的哪個動作,識別出資料庫操作關聯的表和相關的資料;
最後,把識別出的表和資料傳遞給系統的相關介面,完成查詢。
結合上面的類圖,可以分三類:lexer、parser工具類,完成了SQL的解析;*Data,SQL解析成果的載體;BadSyntaxException,語法錯誤異常類。
下面,先從工具類說起:
> lexer
用編譯原理的知識說來,這是個詞法分析器,主要負責把輸入的SQL字串切割開,
第一步,將SQL字串使用指定的分割符進行分割,將結果儲存在一個叫tokens的string數組中
圖2 lexer分割SQL
分割過程2所示,利用Regex,將SQL字串分割。
\s{1}|(,){1}|(=){1}|(\(){1}|(\)){1}|('[^']*'){1}
圖3 SQL to tokens分割過程用到的RE
圖4 實際運行時tokens結果
圖5 惡意輸入的結果
( 在面試的時候,問過我一個問題,如果有使用者而已輸入,輸入了N多個空格,隔斷了select語句的field-list,該怎麼辦?
如
select sid, sname from students where sid='10001'
實際上,在上面這個步驟,把所有的單元都獨立分出來,5所示,每個空格都佔一個tokens的位置。但是,lexer有一個nextToken方法,能有效地過濾掉這些幹擾:
private void nextToken()
{
position++;
while (position < tokens.Length && tokens[position].Length == 0)
position++;
}
之後,lexer通過以下的演算法,實現對各種字串片段的處理
(注意:lexer下有tokens數組的指標position,指向了lexer當前處理的tokens數組中的元素)
1、定位到下一個處理元素token,使用nextToken()方法
2、匹配token,使用match*()方法
3、處理token,使用eat*()方法
在match階段,用到了如下的RE來對字串片段進行匹配:
private const string intPattern = @"^[-+]?(0|[1-9]\d*)$";
private const string stringPattern = @"^'[^']*'$";
private const string idPattern = @"^[a-zA-Z_]\w*$"
private const string keywordPattern = @"^[a-zA-Z]*$";
simpledb支援的SQL關鍵字如下:
string[] keywords = {"select","from","where","and","insert","into","values",
"delete","update","set","create","table","varchar","view","as","index","on"};
在eat階段,
總共有5個eat方法:eatDelim(char) eatId() eatIntConstant() eatKeyword(string) eatStringConstant,其中eatDelim和eatKeyword,有參數輸入,根據參數的不懂,吃掉相應的分隔字元或關鍵字,其他的,只是利用position,將position指向的token解析對指定的int或string。
可以說,lexer為SQL的解析完成了第一步的工作。
明天得早起,先到這裡,待續。