再读simpledb 之 SQL语句解析(2)

(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,完成数据的查询。

posted @ 2012-09-12 23:59  郝玉琨  阅读(988)  评论(0编辑  收藏  举报