分析器阶段

分析器阶段( parser stage 包含两个部分:

分析器

分析器必须检查(以纯 ASCII 文本方式到来的)查询字串的语法。如果语法正确,则创建一个分析树并将之传回,否则,返回一个错误。在这个实现里我们使用了著名的 Unix 工具 lex 和 yacc

lexer (词法)在文件 scan.l 里定义,负责识别标识符SQL 关键字等。对于发现的每个关键字或者标识符都会生成一个记号并且传递给分析器。

分析器在文件 gram.y 里定义并且包含一套语法规则和触发规则时执行的动作。动作代码(实际上是 C 代码)用语建立分析树。

文件 scan.l 用 lex 转换成 C 源文件而 gram.y 用 yacc 转换成 gram.c。在完成这些转换后,一个通用的 C 编译器可以用于创建分析器。千万不要对生成的 C 源文件做修改,因为下一次调用 lex 或 yacc 会把它们覆盖。

注意:上面提到的转换和编译是使用跟随Postgres 发布的 makefiles 自动完成的。
对 yacc 或者 gram.y 里的语法规则的详细描述超出本文的范围。有很多关于lex 和 yacc 的书籍和文档。你在开始研究 gram.y 里给出的语法之前,你应该对 yacc 很熟悉,否则你是看不懂那里面的内容,理解不了发生了什么事情的。

为了更好地理解处理一个查询时 Postgres 里使用的数据结构,我们用一个简单的例子演示在每个阶段数据结构所做的改变。

例 54-1. 一个简单的选择(Select)

这个例子包含下面的简单的查询,这个例子将会在本章剩余各节的所有描述和图示里出现。该查询假设在Supplier Database 数据库里的表已经定义了。

select s.sname, se.pno
    from supplier s, sells se
    where s.sno > 2 and s.sno = se.sno;
图 \ref{parsetree} 显示了 gram.y 里的语法规则和动作为查询 一个简单的 Select 这个例子包含下面的简单查询,该查询将被用于随后各个阶段的各种描述和图片.这个查询假设在供应商数据库里的表已经定义了. select s.sname, se.pno from supplier s, sells se where s.sno > 2 and s.sno = se.sno; 构建的分析树(不带用于 where 子句操作符树,它在图\ref{where_clause}里显示,因为在一幅图里没有足够的位置把两部分的数据结构都放进去)。

树的顶端节点是 SelectStmt 节点。对每个在 SQL 查询的 from子句里出现的元素创建一个 RangeVar 节点,存放 alias别名)的名称和一个指向一个存放关系名称的RelExpr 节点的指针。所有 RangeVar 节点都收集到一个列表里,然后附加到 SelectStmt 节点的 fromClause 字段上。

对于 SQL 查询里面的 select列表 里出现的每个元素都创建一个 ResTarget 节点,存放一个指向 Attr 节点的指针。Attr 节点存放元素的关系名称和一个指向存放着字段名称的 Value 节点的指针。所有 ResTarget 节点都收集到一个列表里,然后链接到 SelectStmt 节点的 targetList 字段里。

图 \ref{where_clause} 显示了为例子  select s.sname, se.pno from supplier s, sells se where s.sno > 2 and s.sno = se.sno; 里 SQL 查询的 where 子句构建的操作符树,这个操作符树附加到了 SelectStmt 节点的字段 qual 上。操作符树的顶端节点是一个代表 AND 操作的 A_Expr 节点。这个节点有两个后继节点, lexprrexpr 分别指向两个子树。附加到 lexpr 的子树代表条件 s.sno > 2 而附加到 rexpr 的子树代表 s.sno = se.sno。为每个字段创建一个 Attr 节点,存放关系名和一个指向存放着字段名的 Value 节点的指针。为在查询里出现的常量条款创建一个 Const 节点,存放常量值。

转换处理

转换处理把分析器传递过来的分析树作为输入,然后递归地处理它。如果碰到一个 SelectStmt 节点,就把它转换成一个 Query 节点,这个节点将是新数据结构的最顶端节点。图 \ref{transformed} 显示了转换过后的数据结构(转换过后的 where 子句在图 \ref{transformed_where} 里给出,因为一幅图里放不下所有的部分)。

现在进行一个检查,看看 FROM 子句里面的关系名是否被系统所知。为每个系统表里面出现的关系名创建一个RTE 节点,该节点包含关系名,别名关系 id。从现在开始,关系 id (标识)用于指代查询里出现的关系。所有RTE 节点都收集到范围表项目列表range table entry list)里,然后该列表在链接到 Query 节点的 rtable 字段上。如果查询里面有一个不为系统所知的关系被检测到,那么将返回一个错误然后查询处理将退出。

下一步是检查所用的字段名是否包含在查询给出的关系里。为找到的每个字段创建一个TLE 节点,保存一个指向 Resdom 节点的(该节点里保存列名称)的指针和一个指向 VAR 节点的指针。在 VAR 节点里有两个重要的数字。数据域 varno 包含当前字段的关系在上面创建的范围表项目列表里面的位置。数据域 varattno 给出该字段在关系里的位置。如果无法找到一个字段的名称,则返回一个错误而且这个正在处理的查询将退出。