OceanBase SQL解析源码分析(二)
2016-03-02 12:04
567 查看
**重点内容**_原创文章,转载请注明: 转载自 镜中影的技术博客_
本文链接地址: OceanBase SQL解析源码分析(二)
URL:http://blog.csdn.net/linkpark1904/article/details/50778795
其中ObItemType表示节点的具体类型,在OceanBase中将语法树节点分为多种多样的类型,在词法分析中可以看出节点类型有T_INT,T_NULL,T_DOUBLE等等,这些语法树节点具体的类型定义在ob_item_type.h文件中,这些类型均属于枚举类型。对于终结节点,int64_t value_以及const char* str_value_存储的是节点具体的值,而对于非终结节点int32_t num_child_和struct_ParseNode** children_分别存储子节点的个数和指向子节点的指针,这样就可以构建出一颗完整的树了。
另外一个相对重要的数据结构是ParseResult,这个是用来存储解析结果的数据结构,也就是解析出的语法树,具体其数据结构设计如下所示:
几个比较重要的成员是yyscan_info,result_tree_,malloc_pool_,这三个成员在代码里面反复出现:
yyscan_info: 这个代表的是词法解析器实例,在前面有说过,OceanBase词法解析采用可重入词法分析器,在多线程环境下,有多个词法分析器实例。
result_tree:这个是是整个语法树的根节点,代表整个语法树
malloc_pool_:这个成员和OceanBase内存管理机制相关,OceanBase采用自己独有的内存管理方式,而malloc_pool_为一个具体的内存池实例,在词法语法解析阶段,所有内存申请释放都需要通过这个成员进行,而在解析结果初始化阶段,会对这个成员进行初始化,具体代码在ob_sql.cpp:202 parse_result.malloc_pool_ = &parser_mem_pool;
OceanBase系统有一个全局的定长内存池,这个内存池维护了由64KB大小的定长内存块组成的空闲链表。但是全局内存池只能申请大块内存,不适合小内存的管理和维护,所以每个需要申请内存的模块,都只能从全局内存池中申请大块内存,每个模块内部再实现专用的内存池。从上述代码可以看出,在SQL解析模块,OceanBase使用的内存池为parser_mem_pool。
引用一个具体的实例来看看OceanBase是如何组织语法树的,例如SQL语句:
那么得到的语法树如下所示:
输入的语句Update student set sex = “M” where name = “小明”,首先会经过词法分析
Update 对应的词法规则如下:
所以返回UPDATE标记
Student对应的词法规则如下:
接下来是student,当遇到student后,在词法分析代码中会生成如下节点,
其中,返回的标记token是NAME(对应语法分析中的语句描述relation_factor)。
Set为关键字,返回SET标记,sex在词法解析中生成的节点如下:
返回的标记token是NAME,’=’返回的token是COMP_EQ,而”M”经过语法解析返回的节点如下:
返回的token为STRING。
Where为关键字,所以Where经过词法解析返回的标记也是WHERE,同样,name返回为:
其返回的token为NAME,’=’返回的token为COMP_EQ,字符串”小明”返回的节点类型为:
其返回的token也为STRING。所以,最终,经过词法分析,Update student set sex = “M” where name = “小明” 最终变换为
UPDATE NAME SET NAME COMP_EQ STRING WHERE NAME COMP_EQ STRING这样一系列的语法符号,作为语法分析的输入。
语法分析的BNF树如上图所示,这里面可以看到两个关键的函数,一个是malloc_non_terminal_node,同样,在前面的词法分析中可以看到malloc_terminal_node,这两个函数分别创建语法树的终结节点和语法树的非终结节点。这两个函数的定义均在parse_node.h文件中,具体实现代码如下所示:
这两个函数就将整个流程连接起来了,由于从词法分析输出的记号集合为:
UPDATE NAME SET NAME COMP_EQ STRING WHERE NAME COMP_EQ STRING
这些记号集合正好符合update_stmt这个BNF,所以语法解析的入口点就从这里开始,逐步生成语法树,从顶层语法规则可以看出,第一个生成的节点如下:
生成的是一个类型为T_UPDATE的节点,并且,这个节点有5个孩子节点,同时其第一个孩子节点就是NAME也就是student这个表的表名,第二个节点为一个由类型为LINK_NODE通过merge_node函数调用生成的类型为T_ASSIGN_LIST的节点。具体的merge_node函数定义如下所示:
本文链接地址: OceanBase SQL解析源码分析(二)
URL:http://blog.csdn.net/linkpark1904/article/details/50778795
三、OceanBase语法树描述
3.1 语法树节点的定义
OceanBase语法树节点的定义在parse_node.h文件中,具体语法树节点的定义如下代码所示:typedef struct _ParseNode { ObItemType type_; /* attributes for terminal node, it is real value */ int64_t value_; const char* str_value_; /* attributes for non-terninal node, which has children */ int32_t num_child_; struct _ParseNode** children_; // BuildPlanFunc m_fnBuildPlan; } ParseNode;
其中ObItemType表示节点的具体类型,在OceanBase中将语法树节点分为多种多样的类型,在词法分析中可以看出节点类型有T_INT,T_NULL,T_DOUBLE等等,这些语法树节点具体的类型定义在ob_item_type.h文件中,这些类型均属于枚举类型。对于终结节点,int64_t value_以及const char* str_value_存储的是节点具体的值,而对于非终结节点int32_t num_child_和struct_ParseNode** children_分别存储子节点的个数和指向子节点的指针,这样就可以构建出一颗完整的树了。
另外一个相对重要的数据结构是ParseResult,这个是用来存储解析结果的数据结构,也就是解析出的语法树,具体其数据结构设计如下所示:
typedef struct _ParseResult { void* yyscan_info_; ParseNode* result_tree_; const char* input_sql_; int input_sql_len_; void* malloc_pool_; // ObStringBuf char error_msg_[MAX_ERROR_MSG]; int start_col_; int end_col_; int line_; int yycolumn_; int yylineno_; char* tmp_literal_; } ParseResult;
几个比较重要的成员是yyscan_info,result_tree_,malloc_pool_,这三个成员在代码里面反复出现:
yyscan_info: 这个代表的是词法解析器实例,在前面有说过,OceanBase词法解析采用可重入词法分析器,在多线程环境下,有多个词法分析器实例。
result_tree:这个是是整个语法树的根节点,代表整个语法树
malloc_pool_:这个成员和OceanBase内存管理机制相关,OceanBase采用自己独有的内存管理方式,而malloc_pool_为一个具体的内存池实例,在词法语法解析阶段,所有内存申请释放都需要通过这个成员进行,而在解析结果初始化阶段,会对这个成员进行初始化,具体代码在ob_sql.cpp:202 parse_result.malloc_pool_ = &parser_mem_pool;
OceanBase系统有一个全局的定长内存池,这个内存池维护了由64KB大小的定长内存块组成的空闲链表。但是全局内存池只能申请大块内存,不适合小内存的管理和维护,所以每个需要申请内存的模块,都只能从全局内存池中申请大块内存,每个模块内部再实现专用的内存池。从上述代码可以看出,在SQL解析模块,OceanBase使用的内存池为parser_mem_pool。
引用一个具体的实例来看看OceanBase是如何组织语法树的,例如SQL语句:
Update student set sex = "M" where name = "小明",
那么得到的语法树如下所示:
3.2 词法分析具体流程
这里我可以通过OceanBase语法解析以及词法解析来分析一个详细的语法树的生成流程。输入的语句Update student set sex = “M” where name = “小明”,首先会经过词法分析
Update 对应的词法规则如下:
UPDATE { return UPDATE; }
所以返回UPDATE标记
Student对应的词法规则如下:
{identifer} {……}那么其返回的token为NAME,并且会生成一个语法树节点类型为T_IDENT即节点:
接下来是student,当遇到student后,在词法分析代码中会生成如下节点,
其中,返回的标记token是NAME(对应语法分析中的语句描述relation_factor)。
Set为关键字,返回SET标记,sex在词法解析中生成的节点如下:
返回的标记token是NAME,’=’返回的token是COMP_EQ,而”M”经过语法解析返回的节点如下:
返回的token为STRING。
Where为关键字,所以Where经过词法解析返回的标记也是WHERE,同样,name返回为:
其返回的token为NAME,’=’返回的token为COMP_EQ,字符串”小明”返回的节点类型为:
其返回的token也为STRING。所以,最终,经过词法分析,Update student set sex = “M” where name = “小明” 最终变换为
UPDATE NAME SET NAME COMP_EQ STRING WHERE NAME COMP_EQ STRING这样一系列的语法符号,作为语法分析的输入。
3.3 语法分析具体流程
接下来看一下语法分析是如何进行的。/***************************************************************************** * * update grammar * *****************************************************************************/ update_stmt: UPDATE opt_hint relation_factor SET update_asgn_list opt_where opt_when { ParseNode* assign_list = NULL; merge_nodes(assign_list, result->malloc_pool_, T_ASSIGN_LIST, $5); malloc_non_terminal_node($$, result->malloc_pool_, T_UPDATE, 5, $3, assign_list, $6, $7, $2); } ; update_asgn_list: update_asgn_factor { $$ = $1; } | update_asgn_list ',' update_asgn_factor { malloc_non_terminal_node($$, result->malloc_pool_, T_LINK_NODE, 2, $1, $3); } ; update_asgn_factor: column_name COMP_EQ expr { malloc_non_terminal_node($$, result->malloc_pool_, T_ASSIGN_ITEM, 2, $1, $3); } ;
语法分析的BNF树如上图所示,这里面可以看到两个关键的函数,一个是malloc_non_terminal_node,同样,在前面的词法分析中可以看到malloc_terminal_node,这两个函数分别创建语法树的终结节点和语法树的非终结节点。这两个函数的定义均在parse_node.h文件中,具体实现代码如下所示:
ParseNode* new_terminal_node(void *malloc_pool, ObItemType type) { return new_node(malloc_pool, type, 0); } ParseNode* new_non_terminal_node(void *malloc_pool, ObItemType node_tag, int num, ...) { assert(num>0); va_list va; int i; ParseNode* node = new_node(malloc_pool, node_tag, num); if (node != NULL) { va_start(va, num); for( i = 0; i < num; ++i) { node->children_[i] = va_arg(va, ParseNode*); } va_end(va); } return node; }
这两个函数就将整个流程连接起来了,由于从词法分析输出的记号集合为:
UPDATE NAME SET NAME COMP_EQ STRING WHERE NAME COMP_EQ STRING
这些记号集合正好符合update_stmt这个BNF,所以语法解析的入口点就从这里开始,逐步生成语法树,从顶层语法规则可以看出,第一个生成的节点如下:
生成的是一个类型为T_UPDATE的节点,并且,这个节点有5个孩子节点,同时其第一个孩子节点就是NAME也就是student这个表的表名,第二个节点为一个由类型为LINK_NODE通过merge_node函数调用生成的类型为T_ASSIGN_LIST的节点。具体的merge_node函数定义如下所示:
ParseNode* merge_tree(void *malloc_pool, ObItemType node_tag, ParseNode* source_tree) { int index = 0; int num = 0; ParseNode* node; if(source_tree == NULL) return NULL; num = count_child(source_tree); if ((node = new_node(malloc_pool, node_tag, num)) != NULL) { merge_child(node, source_tree, &index); assert(index == num); } return node; }