淘宝数据库OceanBase SQL编译器部分 源码阅读--生成逻辑计划 淘宝数据库OceanBase SQL编译器部分 源码阅读--解析SQL语法树里做了介绍,这篇博客主要研究第二步,生成逻辑计划。 一、 什么是逻辑计划? 我们已经知道,语法树就是一个树状的结构组织,每个节点
淘宝数据库OceanBase SQL编译器部分 源码阅读--解析SQL语法树里做了介绍,这篇博客主要研究第二步,生成逻辑计划。
我们已经知道,语法树就是一个树状的结构组织,每个节点代表一种类型的语法含义。如
update student set sex="M" where name
="小明";
这条SQL的语法树形状为:
|Update Stmt
|----Table:student
|----TargeList:
|--------sex = "M"
|----Qualifications:
|--------name="小明"
但是仅仅语法树并不能知道数据库中是否存在student这张表,这张表是否有sex,name这两个字段,我们是否有权限修改这条记录等。语法树只能判断这条SQL的写法是否正确,不能确定这条SQL是否可以执行。
逻辑计划需要明确SQL语句中所涉及到的表,字段,表达式等是否有效。这个的逻辑计划与在《数据库系统实现》等书中描述的逻辑查询计划不同。逻辑查询计划将SQL语句直接转为可运算的关系表达式。在OceanBase中,逻辑计划则只是查找或生成涉及到的表的ID,涉及字段的ID,涉及表达式的ID等,逻辑计划是不可运算的。
简单来说,逻辑计划要弄清楚,这条SQL可以分解为几条stmt,每条stmt包含了哪些表,字段和表达式。在此基础上,如果是insert的Stmt,要加上设置哪些值;如果是update的stmt,要加上需要更新的列和对应的值,等等。
在一个逻辑计划中,每一个查询有一个唯一标识qid,每一张表有一个唯一的标识tid,每一个列有一个唯一的标识cid,每一个表达式有一个唯一的标识eid。
来看OceanBase中的逻辑计划的结构(省略无关方法和变量).
<code> <span>class</span> ObLogicalPlan { <span>//... </span> oceanbase::common::ObVector<obstmt> stmts_; <span>//存储该逻辑计划的所有stmt</span> oceanbase::common::ObVector<obsqlrawexpr> exprs_; <span>//逻辑计划的所有表达式</span> oceanbase::common::ObVector<obrawexpr> raw_exprs_store_;<span>//存储逻辑计划的所有表达式</span> uint64_t new_gen_tid_;<span>//用于生成新的tid</span> uint64_t new_gen_cid_;<span>//用于生成新的cid</span> uint64_t new_gen_qid_;<span>//用于生成新的qid</span> uint64_t new_gen_eid_;<span>//用于生成新的eid</span> }; </obrawexpr></obsqlrawexpr></obstmt></code>
oceanbase::common::ObVector
是OceanBase中自己实现的泛型容器之一,作用与STL的vector相同。
stmts_
存储该逻辑计划的所有stmt;
raw_exprs_store_
仅仅用于存储表达式,exprs_
则引用raw_exprs_store_
中的内容。
new_gen_tid_
等4个变量是用来生成新的标识时使用,一个逻辑是可以用多个tid,多个cid,多个eid,多个qid的。这些标识分布于存储的stmt和表达式中。
注:stmt实在不知道中文该怎么称呼,就不改中文名了。
<code> <span>struct</span> TableItem { uint64_t table_id_; common::ObString table_name_; common::ObString alias_name_; TableType type_; uint64_t ref_id_; }; </code>
table_id_
唯一标识一个关系表,其类型分为基本表,引用表和子查询关系。
对同一个实体表,ref_id_
与table_id_
相同;
如果是一个引用别名的表,则table_id_
是新生成的,ref_id_
与这个表真正的table_id_
相同;如果是一个子查询,则table_id_
是新生成的,ref_id_
是对子查询的引用。
对同一个实体表,它在所有线程使用的table_id_
都是相同的;如果是生成的标识,则仅在该线程会话期间是唯一的。
<code><span>struct</span> ColumnItem { uint64_t column_id_; common::ObString column_name_; uint64_t table_id_; uint64_t query_id_; <span>bool</span> is_name_unique_; <span>bool</span> is_group_based_; common::ObObjType data_type_; }; </code>
column_id_
唯一标识一个列,table_id_
和query_id_
为该列所属的关系表和stmt。is_name_unique_
仅用在解析逻辑计划期间,标记该列的名称是否在所有表的字段中都是唯一的。is_group_based_
标记该列是否用于分组。data_type_
标识该列的数据类型。
逻辑计划的中表达式有多种类型,其基类为ObRawExpr.包括两个成员变量,type_
表示表达式的类型,result_type_
表示表达式值的类型。
<code> <span>class</span> <span>ObRawExpr</span> <span>{ <span>//省略其他方法 <span>private</span>:<span> ObItemType type_</span></span>; <span><span>common</span>:<span>:ObObjType result_type_</span></span>; <span>}</span></span> </code>
表达式分为常量表达式, 一元引用表达式,二元引用表达式,一元操作符表达式,二元操作符表达式,三元操作符表达式,多元操作符表达式,case操作符表达式,聚集函数表达式,系统函数表达式,SQL原生表达式等。继承关系如下。
<code>namespace sql { <span>//原生表达式基类</span> class ObRawExpr <span>//常量表达式</span> class ObConstRawExpr : <span>public</span> ObRawExpr //一元引用表达式 <span>class</span> ObUnaryRefRawExpr : <span>public</span> ObRawExpr //二元引用表达式 <span>class</span> ObBinaryRefRawExpr : <span>public</span> ObRawExpr //一元操作符表达式 <span>class</span> ObUnaryOpRawExpr : <span>public</span> ObRawExpr //二元操作符表达式 <span>class</span> ObBinaryOpRawExpr : <span>public</span> ObRawExpr //三元操作符表达式 <span>class</span> ObTripleOpRawExpr : <span>public</span> ObRawExpr //多元操作符表达式 <span>class</span> ObMultiOpRawExpr : <span>public</span> ObRawExpr //<span>case</span>操作符表达式 <span>class</span> ObCaseOpRawExpr : <span>public</span> ObRawExpr //聚集函数表达式 <span>class</span> ObAggFunRawExpr : <span>public</span> ObRawExpr //系统函数表达式 <span>class</span> ObSysFunRawExpr : <span>public</span> ObRawExpr //SQL原生表达式 <span>class</span> ObSqlRawExpr : <span>public</span> ObRawExpr }; class ObRawExpr { }; </code>
在ObLogicalPlan中,存储使用的是vector<obrawexpr></obrawexpr>
,使用时转为vector<obsqlrawexpr></obsqlrawexpr>
.
Stmt表示一个单独的查询所包含的内容,一个逻辑计划可以包含多个Stmt.
<code> <span><span>class</span> <span><em>ObStmt</em></span></span> { <span>/*省略部分内容...*/</span> <span>protected:</span> <span>common:</span><span>:ObVector<tableitem></tableitem></span> table_items<span>_</span>; <span>common:</span><span>:ObVector<columnitem></columnitem></span> column_items<span>_</span>; <span>private:</span> <span>StmtType</span> type<span>_</span>; uint64_t query_id<span>_</span>; <span>//uint</span>64_t where_expr_id<span>_</span>; <span>common:</span><span>:ObVector<uint64_t></uint64_t></span> where_expr_ids<span>_</span>; }; </code>
Stmt包括了一个查询所有的表table_items_
,列column_items_
,表达式where_expr_ids_
和一个唯一的查询标识query_id_
。注意这里存储的只有表达式的id,而不是表达式的实际内容。
从上述的定义总结来看,一个逻辑计划拥有多条查询实例Stmt和多个表达式,一个查询实例Stmt包含了多个表和多个列及所需表达式的引用。表,列,表达式,查询实例都有唯一的标识符进行标记。
ObLogicalPlan
----ObStmt : 1...n
--------TableItem : 0...n
--------ColnumItem : 0...n
--------expr_id_ref : 0...n
----ObRawExpr : 0...n
制定逻辑计划的源码在build_plan.h和build_plan.cpp中,在OceanBase0.4中,则增加了dml_build_plan.h和dml_build_plan.cpp。制定逻辑对外提供的接口只有两个,解析函数resolove
和销毁函数destroy_plan
,其他的为自用,可以浏览下其函数声明及用途,基本的结构就是这样,因为目前OceanBase中支持的SQL语句不多,相应的解析函数也比较少,还有一些没有完成,可以想见未来还会添加更多的函数。
<code><span>//解析多重查询</span> <span>int</span> resolve_multi_stmt(ResultPlan* result_plan, ParseNode* node) <span>//解析独立表达式</span> <span>int</span> resolve_independ_expr() <span>//解析and表达式</span> <span>int</span> resolve_and_exprs() <span>//解析表达式</span> <span>int</span> resolve_expr() <span>//解析聚集函数</span> <span>int</span> resolve_agg_func() <span>//解析join表连接</span> <span>int</span> resolve_joined_table() <span>//解析表</span> <span>int</span> resolve_table() <span>//解析from子句</span> <span>int</span> resolve_from_clause() <span>//解析列</span> <span>int</span> resolve_table_columns() <span>//解析*</span> <span>int</span> resolve_star() <span>//解析select的投影列表</span> <span>int</span> resolve_select_clause() <span>//解析where子句</span> <span>int</span> resolve_where_clause() <span>//解析group by子句</span> <span>int</span> resolve_group_clause() <span>//解析having子句</span> <span>int</span> resolve_having_clause() <span>//解析order子句</span> <span>int</span> resolve_order_clause() <span>//解析limit子句</span> <span>int</span> resolve_limit_clause() <span>//解析select查询</span> <span>int</span> resolve_select_stmt() <span>//解析delete查询</span> <span>int</span> resolve_delete_stmt() <span>//解析insert的插入列</span> <span>int</span> resolve_insert_columns() <span>//解析intsert查询的插入值</span> <span>int</span> resolve_insert_values() <span>//解析insert查询</span> <span>int</span> resolve_insert_stmt() <span>//解析update查询</span> <span>int</span> resolve_update_stmt() <span>//解析函数。对外提供</span> <span>int</span> resolve(ResultPlan* result_plan, ParseNode* node) <span>//销毁函数,对外提供</span> <span>extern</span> <span>void</span> destroy_plan(ResultPlan* result_plan) </code>
resolve函数根据语法树node的类型调用不同的查询解析实例。以下是部分代码摘抄:
<code><span>int</span> resolve(ResultPlan* result_plan, ParseNode* node) { <span>/*...*/</span> uint64_t query_id = OB_INVALID_ID; <span>if</span> (ret == OB_SUCCESS && node != NULL) { <span>switch</span> (node->type_) { <span>case</span> T_STMT_LIST: { ret = resolve_multi_stmt(result_plan, node); <span>break</span>; } <span>case</span> T_SELECT: { ret = resolve_select_stmt(result_plan, node, query_id); <span>break</span>; } <span>case</span> T_DELETE: { ret = resolve_delete_stmt(result_plan, node, query_id); <span>break</span>; } <span>case</span> T_INSERT: { ret = resolve_insert_stmt(result_plan, node, query_id); <span>break</span>; } <span>case</span> T_UPDATE: { ret = resolve_update_stmt(result_plan, node, query_id); <span>break</span>; } <span>default</span>: ret = OB_ERROR; <span>break</span>; }; } <span>return</span> ret; } <span>int</span> resolve_update_stmt(ResultPlan* result_plan, ParseNode* node, uint64_t& query_id) { <span>int</span>& ret = result_plan->err_stat_.err_code_ = OB_SUCCESS; uint64_t table_id = OB_INVALID_ID; query_id = OB_INVALID_ID; ObLogicalPlan* logical_plan logical_plan = <span>new</span>(logical_plan) ObLogicalPlan(name_pool); result_plan->plan_tree_ = logical_plan; update_stmt = <span>new</span>(update_stmt) ObUpdateStmt(name_pool); query_id = logical_plan->generate_query_id(); <span>//为update_stmt设置新的标识qid</span> update_stmt->set_query_id(query_id); logical_plan->add_query(update_stmt); ParseNode* table_node = node->children_[<span>0</span>]; <span>//解析表 </span> ret = resolve_table(result_plan, update_stmt, table_node, table_id); update_stmt->set_update_table(table_id); ParseNode* assign_list = node->children_[<span>1</span>]; uint64_t ref_id; ColumnItem *column_item = NULL; <span>//解析要更新的列表,如:update student set sex="M",grade="2" where name = "xiaoming";</span> <span>for</span> (int32_t i = <span>0</span>; ret == OB_SUCCESS && i num_child_; i++) { ParseNode* assgin_node = assign_list->children_[i]; <span>/* resolve target column */</span> ParseNode* column_node = assgin_node->children_[<span>0</span>]; ObString column_name; column_name.assign_ptr( (<span>char</span>*)(column_node->str_value_), <span>static_cast</span><int32_t>(<span>strlen</span>(column_node->str_value_)) ); <span>//1 根据列名获取列</span> column_item = update_stmt->get_column_item(NULL, column_name); <span>//2 解析列到vector<columnitem></columnitem></span> ret = update_stmt->add_column_item(*result_plan, column_name, NULL, &column_item); <span>//3 增加列引用到update_stmt </span> ret = update_stmt->add_update_column(column_item->column_id_); <span>/* resolve new value expression */</span> <span>//4 解析值表达式</span> ParseNode* expr = assgin_node->children_[<span>1</span>]; ret = resolve_independ_expr(result_plan, update_stmt, expr, ref_id, T_UPDATE_LIMIT); <span>//5 添加值表达式引用到update_stmt</span> ret = update_stmt->add_update_expr(ref_id) } <span>//解析where子句</span> ret = resolve_where_clause(result_plan, update_stmt, node->children_[<span>2</span>]); <span>return</span> ret; } </int32_t></code>
我们仍旧以update语句为例。上面是根据源代码整理的逻辑,不是源码,主要是为了理清思路。
update_stmt
的vector<columnitem></columnitem>
中,并将列引用id添加到update_stmt
的更新列列表ObArray<uint64_t>
update_columns_</uint64_t>
中; ObArray<uint64_t>
update_exprs_</uint64_t>
中去; 通过上面我们知道,逻辑计划的解析的一个重要内容就是要确定查询stmt,表,列,表达式的标识.查询和表达式的标识id都可以在解析的时候生成。因为这两项不是线程共有的,但是表和列是持久的数据,可以跨线程使用同样的id。这些表和列的信息由谁来管理?
追根溯源,你会发现实体表和列的id是在ob_schema.cpp
中获取的。什么是schema?schema就是数据库对象的一个集合。网上有一个很形象的比喻,我稍微做了点改动:
什么是Database,什么是Schema,什么是Table,什么是列,什么是行,什么是User?我们可以可以把Database看作是一个大仓库,仓库分了很多很多的房间,Schema就是其中的房间,一个Schema代表一个房间,Table可以看作是每个Schema中的柜子,行和列就是柜子中的格子。User就是房间的主人。
OceanBase要求schema强类型约束,也就是要预先定义好schema。这样可以方便的进行各种online ddl操作。
OceanBase的表和列信息存储在Schema的一个hash_map中。关于Schema 以后再深入了解。
制定逻辑计划,最关键的是要理解逻辑计划的设计目标,其中最主要的内容就是确定逻辑计划中使用到的查询Stmt,表,列,表达式,并为它们生成或设置唯一标识,确保在同一个逻辑计划中是不相同的。制定逻辑计划的函数只要是reslove系列函数。而表和列的信息存储在Schema中一个hash_map中。
欢迎光临我的网站----我的博客园----我的CSDN。
如果阅读本文过程中有任何问题,请联系作者