2.3. 分析器阶段

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

2.3.1. 分析器

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

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

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

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

注意: 上面提到的转换和编译是使用跟随 PostgreSQL 发布的 makefiles 自动完成的。

yacc 或者 gram.y 里的语法规则的详细描述超出本文的范围。 有很多关于 lexyacc的书籍和文档。你在开始研究 gram.y 里给出的语法之前,你应该对 yacc 很熟悉,否则你是看不懂那里面的内容,理解不了发生了什么事情的。

为了更好地理解处理一个查询时 PostgreSQL 里使用的数据结构, 我们用一个简单的例子演示在每个阶段数据结构所做的改变。 这个例子包含下面的简单的查询, 这个例子将会在本章剩余各节的所有描述和图示里出现。 该查询假设在 供应商数据库 的表已经定义了。

Example 2-1. 一个简单的选择

select s.sname, se.pno
    from supplier s, sells se
    where s.sno > 2 and s.sno = se.sno;
      

图 \ref{parsetree} 显示了 gram.y 里的语法规则和动作为查询 一个简单的选择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 节点。 这个节点有两个后继节点,叫 lexpr rexpr, 分别指向两个 子树。 附加到 lexpr 的子树代表条件 s.sno > 2 而附加到 rexpr 的子树代表 s.sno = se.sno。 为每个字段创建一个 Attr 节点,存放关系名和一个指向存放着字段名的 Value 节点的指针。 为在查询里出现的常量项创建一个 Const 节点, 存放常量值。

(译注:图片待标)

2.3.2. 转换处理

转换处理 以分析器传递过来的分析树作为输入, 然后递归地处理它。如果碰到一个 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 给出该字段在关系里的位置。 如果有一个字段的名称无法找到, 则返回一个错误而且这个正在处理的查询将退出。