再读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模块完成了解析的功能,类图如下:

image

image

图1 parse类图

在开始之间,先想一下SQL语句的解析,主要的工作是:

通过对输入SQL语句字符串的分割等工作,

首先,对输入SQL语句字符串的分割等工作;

然后,识别出语句执行的是CRUD中的哪个动作,识别出数据库操作关联的表和相关的数据;

最后,把识别出的表和数据传递给系统的相关接口,完成查询。

结合上面的类图,可以分三类:lexer、parser工具类,完成了SQL的解析;*Data,SQL解析成果的载体;BadSyntaxException,语法错误异常类。

下面,先从工具类说起:

> lexer

用编译原理的知识说来,这是个词法分析器,主要负责把输入的SQL字符串切割开,

第一步,将SQL字符串使用指定的分割符进行分割,将结果保存在一个叫tokens的string数组中

image

图2 lexer分割SQL示意图

分割过程如图2所示,利用正则表达式,将SQL字符串分割。

\s{1}|(,){1}|(=){1}|(\(){1}|(\)){1}|('[^']*'){1}

图3 SQL to tokens分割过程用到的RE

image

图4 实际运行时tokens结果

 

image

图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的解析完成了第一步的工作。

 

明天得早起,先到这里,待续。

posted @ 2012-09-11 00:46  郝玉琨  阅读(1906)  评论(1编辑  收藏  举报