再读simpledb 之 SQL语句解析(1)
前面的几个部分,基本实现了数据的读写。对于数据库使用者来说,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所示,利用正则表达式,将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的解析完成了第一步的工作。
明天得早起,先到这里,待续。