您的位置:首页 > 数据库

OceanBase SQL解析源码分析(二)

2016-03-02 12:04 567 查看
**重点内容**_原创文章,转载请注明: 转载自 镜中影的技术博客_

本文链接地址: 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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息