作者:王东
就像是我们使用编译器编译c, cpp文件一样, 对查询语句的编译过程也分为了如下几部分:
l 词法分析;
l 语法分析;
l 语义分析和检测;
l 查询重写;
l 生成查询计划;
l 挑选和优化查询计划;
这里主要是针对 前三步 进行说明。
当我们输入一个SQL语句时, CUBRID的查询编译器会按照类似代码编译器的方式进行工作。
1. 首先进行词法分析,词法分析的目标是将SQL语句进行扫描并分割成为一系列记号(Token),在CUBRID中,是使用Flex生成的文件进行词法分析的。Flex(fast lexical analyser generator)是一个自动的词法分析器生成器(参考附1)。在CUBRID中,有一个描述SQL词法的.l 文件,该文件描述了SQL语句中用的词汇。通过使用Flex,并输入.l 文件,将生成对应的可以用的编译的.c文件。词法分析的结果产生了各种记号(Token), 例如:关键字,标识符,字符串等等;
2. 接下来进行语法分析。语法分析是将词法分析产生的记号(Token)按照语法要求生成语法树(Syntax Tree)。CUBRID 中采用bison来进行语法分析(参考附录2)。类似于编译器中的表达式一样,不同的SQL语句,将被看作为不同表达式(Expression)。CUBRID中有一个描述语法的.y文件,该文件中包含表达式树是如何组织起来的代码。通过bison,并输入.y文件,生成对应可以用于编译的.c和.h文件。这样SQL就被组织成语法树的结构了。
3. 接下来就是对语法树进行语义检测(Semantic analysis and check)。因为语法分析中只是完成了对表达式语法层面的分析,并没有对真正这条语句的内容是否合法进行检测。 CUBRID 里面的Semantic check, 是对生成的语法树进行遍历和检查。由于树的节点不同,对每个节点的遍历算法也是不同的。语义检测的结果是对不满足输入的SQL语句进行过滤(提示报错),对语法树进行适当的扩充和删减,便与接下来的生成查询计划等工作。
接下来就进行查询重写,生成查询计划其他了操作。这里暂且不提。
下面就这三部分给出CUBRID中实现的三个例子。
例子1 CUBRID词法分析
.l 文件是由正则表达式和c语法组成的。
//定义段代码
%{
//定义include文件,宏,辅助函数,flex会跳过对这些的处理。
#include "csql_grammar.h"
#include "parse_tree.h"
#define CSQL_MAXNAME 256
static int parser_yyinput_single_line(char* buff, int max_size);
extern int yyline;
//……
%}
// 词法规则段代码
%%
[sS][eE][lL][eE][cC][tT] { begin_token(yytext); return SELECT; }
([a-zA-Z_#]|(/xa1[/xa2-/xee/xf3-/xfe])|([/xa2-/xfe][/xa1-/xfe])|(/x8e[/xa1-/xfe]))([a-zA-Z_#0-9]|(/xa1[/xa2-/xfe])|([/xa2-/xfe][/xa1-/xfe])|(/x8e[/xa1-/xfe]))*
{ begin_token(yytext);
if (strlen(yytext) >= 254)
yytext[254] = 0;
csql_yylval.cptr = pt_makename(yytext);
return IdName;
}
%%
通过编辑.l文件,我们可以定义我们感兴趣的token。
生成的c代码,如下:
case 338:
YY_RULE_SETUP
#line 537 "../../src/parser/csql_lexer.l"
{ begin_token(yytext); return SELECT; }
YY_BREAK
例子2 CUBRID语法分析
.y 文件定义语法树表达方式, 其中包含了Token, rule type, rule的组织方式. 例子中,select 语句的节点中就包含很多内容。
/* Token define */
/*{{{*/
%token SELECT
%token IdName
/* define rule type (node) */
/*{{{*/
%type <node> stmt_
%type <node> create_stmt
%type <node> select_stmt
stmt_
: create_stmt
{ $$ = $1; }
…
| select_stmt
{ $$ = $1; }
…
;
select_stmt
: SELECT /* $1 */
{{ /* $2 发现是select语句,就创建select节点*/
PT_NODE *node = parser_new_node (this_parser, PT_SELECT);
…
DBG_PRINT}}
…
select_list /* $5 */
{{ /* $6 创建select 选项的列表节点*/
PT_NODE *node = parser_top_select_stmt_node ();
if (node)
{
node->info.query.q.select.list = $5;
}
DBG_PRINT}}
opt_select_param_list /* $7 */
FROM /* $8 */
…
opt_where_clause /* $11 where语句对应的节点*/
…
opt_groupby_clause /* $14 group语句对应的节点*/
opt_having_clause /* $16 having语句对应的节点*/
…
{{
if (node)
{
node->info.query.into_list = $7; /* param_list */
node->info.query.q.select.where = $11;