示例中的Update语句在OceanBase中可以表示为如下形式:
|--ParseNode
|--type: T_UPDATE
|--num_child: 3
|--children[0]:
|--type: T_IDENT
|--str_value: student
|--children[1]:
|--type: T_ASSIGN_LIST
|--num_child:1
|--children[0]:
|--type: T_ASSIGN_ITEM
|--children[0]:
|--type: T_IDENT
|--str_value: sex
|children[1]:
|--type: T_EXPR
|--children[0]:
|--type: T_STRING
|--str_value: "M"
|--children[2]:
|--type: T_OP_EQ
|--num_child: 2
|--children[0]:
|--type: T_IDENT
|--str_value: name
|--children[1]:
|--type: T_IDENT
|--str_value: "小明"
OceanBase中采用的这种方式缺点很明显,就是使用这些结构体时必须要仔细辨别各个子节点代表的意义,否则容易出错。优点同样也很明显,可以非常灵活的构建出语法树。
语法树的节点的设计,主要是为了解决如何表达语法结构。不同的数据库有不同的具体实现。OceanBase采用终止符和非终止符分类,使用OceanBase的设计极具灵活性,但使用时需要仔细验证,避免出错。
构建语法树
SQL的语法规则很多 , SELECT , INSERT , UPDATE , DELETE , CREATE TABLE 等 dml , ddl 等语句都有对应的语法树。在上一节节中我们已经看到了UPDATE语句在内存中的语法树形状。本节需要些Flex和Bison的基础知识,如果之前没有接触过Flex与Bison的话,可以阅读《Flex与Bison中文版》或者GNU Bison 2.1 中文手册。
SQL全称为结构化查询语言,有独立于各数据库厂家的SQL标准,各数据库基本上都会大体上遵循该标准。像PostgreSQL数据库兼容某些版本的SQL标准,同时也有些自己独有的语句。每个数据库都有自己的擅长之处,这些细微的区别有时候就体现在查询语言的细微区别上。OceanBase在0.3版本仅支持有限的几条SQL语句,按照其官方的介绍,其目标之一是兼容MySQL的语法,让我们拭目以待。
词法分析
利用Flex进行词法分析是构建语法树的第一个。Flex源文件包含3部分:选项定义、词法规则、代码部分。选项定义部分定义了该词法分析器使用的特性和一些自定义的函数。词法规则部分为匹配语法+匹配动作。匹配语法为正则表达式,flex从上往下按顺序对每个规则进行匹配,一旦匹配成功则执行该项后面大括号内对应的动作代码;代码部分则是C语言代码。
OceanBase的词法文件为sql_parser.l。其中定义了一些函数,选取部分来看。
下面这两个函数可以使得OceanBase支持转义字符,包括:\b,\f,\n,\r,\t.
inline unsigned char escaped_char(unsigned char c); /* quote_type: 0 - single quotes; 1 - double quotation marks */ int64_t parse_string(const char* src, char* dest, int64_t len, int quote_type);
OceanBase中的字符串用双引号括起来的表示,而且需要进行转义处理。
\"(\\.|\"\"|[^"\\\n])*\" {
ParseNode* node = NULL;
malloc_new_node(node, ((ParseResult*)yyextra)->malloc_pool_, T_IDENT, 0);
yylval->node = node;
char* src = yytext+1;
int len = strlen(src) - 1; //remove last quote charactor
char* dest = (char*) parse_malloc(len + 1, ((ParseResult*)yyextra)->malloc_pool_);
check_value(dest);
node->str_value_ = dest;
node->value_ = parse_string(src, dest, len, 1);/*字符串转义处理*/
return NAME;
}
当匹配到NULL时,返回的值类型不能为NULL,因为NULL是c语言的保留字,所以需要换成其他名字,此处将NULL的类型定义为NULLX。
NULL {
/* yylval->node = new_node(((ParseResult*)yyextra)->malloc_pool_, T_NULL, 0); */
malloc_new_node(yylval->node, ((ParseResult*)yyextra)->malloc_pool_, T_NULL, 0);
return NULLX;
}
再来看关系符号的规则代码,每个符号分别返回一个值。
"||" {return CNNOP;}
"=" {return COMP_EQ;}
">=" {return COMP_GE;}
">" {return COMP_GT;}
"<=" {return COMP_LE;}
"<" {return COMP_LT;}
"!="|"<>" {return COMP_NE;}
在《Flex与Bison》一书中,作者让所有关系符号返回同一个值,通过yylval的成员值来标记不同符号。代码类型如下这段。
"=" { yylval.subtok = EXPR_EQ; return COMPARISON; }
"<=>" { yylval.subtok = EXPR_COMP; return COMPARISON; }
">=" { yylval.subtok = EXPR_GE; return COMPARISON; }
">" { yylval.subtok = EXPR_GT; return COMPARISON; }
"<=" { yylval.subtok = EXPR_LE; return COMPARISON; }
"<" { yylval.subtok = EXPR_LT; return COMPARISON; }
"!=" |
"<>" { yylval.subtok = EXPR_NE; return COMPARISON; }
虽然都能完成相同的功能,但是从个人喜好来讲,我更喜欢OceanBase这种做法,因为够直接。
整数的值保留在node->value_中返回,为long long 类型 ( 64位 ) 。 而浮点数的值字面值则保存在node->str_value_ 中。
接下来,看日期时间的提取。
Date{whitespace} '[0-9]{4}(-[0-9]{2}){2}' {
int year, month, day;
struct tm time;
int ret = 0;
/* ParseNode* node = new_node(((ParseResult*)yyextra)->malloc_pool_, T_DATE, 0); */
ParseNode* node = N