首页 > 代码库 > 淘宝数据库OceanBase SQL编译器部分 源代码阅读--生成逻辑计划
淘宝数据库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中的逻辑计划的结构(省略无关方法和变量).
class ObLogicalPlan { //... oceanbase::common::ObVector<ObStmt*> stmts_; //存储该逻辑计划的全部stmt oceanbase::common::ObVector<ObSqlRawExpr*> exprs_; //逻辑计划的全部表达式 oceanbase::common::ObVector<ObRawExpr*> raw_exprs_store_;//存储逻辑计划的全部表达式 uint64_t new_gen_tid_;//用于生成新的tid uint64_t new_gen_cid_;//用于生成新的cid uint64_t new_gen_qid_;//用于生成新的qid uint64_t new_gen_eid_;//用于生成新的eid };
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实在不知道中文该怎么称呼,就不改中文名了。
2.1 逻辑计划中表的定义
struct TableItem { uint64_t table_id_; common::ObString table_name_; common::ObString alias_name_; TableType type_; uint64_t ref_id_; };
table_id_
唯一标识一个关系表,其类型分为基本表,引用表和子查询关系。
对同一个实体表,ref_id_
与table_id_
同样;
假设是一个引用别名的表。则table_id_
是新生成的,ref_id_
与这个表真正的table_id_
同样;假设是一个子查询,则table_id_
是新生成的,ref_id_
是对子查询的引用。
对同一个实体表,它在全部线程使用的table_id_
都是同样的。假设是生成的标识。则仅在该线程会话期间是唯一的。
2.2 逻辑计划中列的定义
struct ColumnItem { uint64_t column_id_; common::ObString column_name_; uint64_t table_id_; uint64_t query_id_; bool is_name_unique_; bool is_group_based_; common::ObObjType data_type_; };
column_id_
唯一标识一个列,table_id_
和query_id_
为该列所属的关系表和stmt。is_name_unique_
仅用在解析逻辑计划期间。标记该列的名称是否在全部表的字段中都是唯一的。
is_group_based_
标记该列是否用于分组。
data_type_
标识该列的数据类型。
2.3 逻辑计划中的表达式的定义
逻辑计划的中表达式有多种类型。其基类为ObRawExpr.包含两个成员变量,type_
表示表达式的类型,result_type_
表示表达式值的类型。
class ObRawExpr { //省略其它方法 private: ObItemType type_; common::ObObjType result_type_; }
表达式分为常量表达式, 一元引用表达式,二元引用表达式,一元操作符表达式。二元操作符表达式,三元操作符表达式,多元操作符表达式。case操作符表达式,聚集函数表达式,系统函数表达式,SQL原生表达式等。
继承关系例如以下。
namespace sql { //原生表达式基类 class ObRawExpr //常量表达式 class ObConstRawExpr : public ObRawExpr //一元引用表达式 class ObUnaryRefRawExpr : public ObRawExpr //二元引用表达式 class ObBinaryRefRawExpr : public ObRawExpr //一元操作符表达式 class ObUnaryOpRawExpr : public ObRawExpr //二元操作符表达式 class ObBinaryOpRawExpr : public ObRawExpr //三元操作符表达式 class ObTripleOpRawExpr : public ObRawExpr //多元操作符表达式 class ObMultiOpRawExpr : public ObRawExpr //case操作符表达式 class ObCaseOpRawExpr : public ObRawExpr //聚集函数表达式 class ObAggFunRawExpr : public ObRawExpr //系统函数表达式 class ObSysFunRawExpr : public ObRawExpr //SQL原生表达式 class ObSqlRawExpr : public ObRawExpr }; class ObRawExpr { };
在ObLogicalPlan中。存储使用的是vector<ObRawExpr
*>
,使用时转为vector<ObSqlRawExpr *>
.
2.4 逻辑计划中的Stmt的定义
Stmt表示一个单独的查询所包括的内容,一个逻辑计划能够包括多个Stmt.
class ObStmt { /*省略部分内容...*/ protected: common::ObVector<TableItem> table_items_; common::ObVector<ColumnItem> column_items_; private: StmtType type_; uint64_t query_id_; //uint64_t where_expr_id_; common::ObVector<uint64_t> where_expr_ids_; };
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
三、 怎样制定逻辑计划?
3.1 reslove系列解析函数
制定逻辑计划的源代码在build_plan.h和build_plan.cpp中。在OceanBase0.4中,则添加了dml_build_plan.h和dml_build_plan.cpp。制定逻辑对外提供的接口仅仅有两个,解析函数resolove
和销毁函数destroy_plan
,其它的为自用,能够浏览下其函数声明及用途。主要的结构就是这样,由于眼下OceanBase中支持的SQL语句不多。对应的解析函数也比較少。另一些没有完毕,能够想见未来还会加入很多其它的函数。
//解析多重查询 int resolve_multi_stmt(ResultPlan* result_plan, ParseNode* node) //解析独立表达式 int resolve_independ_expr() //解析and表达式 int resolve_and_exprs() //解析表达式 int resolve_expr() //解析聚集函数 int resolve_agg_func() //解析join表连接 int resolve_joined_table() //解析表 int resolve_table() //解析from子句 int resolve_from_clause() //解析列 int resolve_table_columns() //解析* int resolve_star() //解析select的投影列表 int resolve_select_clause() //解析where子句 int resolve_where_clause() //解析group by子句 int resolve_group_clause() //解析having子句 int resolve_having_clause() //解析order子句 int resolve_order_clause() //解析limit子句 int resolve_limit_clause() //解析select查询 int resolve_select_stmt() //解析delete查询 int resolve_delete_stmt() //解析insert的插入列 int resolve_insert_columns() //解析intsert查询的插入值 int resolve_insert_values() //解析insert查询 int resolve_insert_stmt() //解析update查询 int resolve_update_stmt() //解析函数。对外提供 int resolve(ResultPlan* result_plan, ParseNode* node) //销毁函数。对外提供 extern void destroy_plan(ResultPlan* result_plan)
resolve函数依据语法树node的类型调用不同的查询解析实例。
下面是部分代码摘抄:
int resolve(ResultPlan* result_plan, ParseNode* node) { /*...*/ uint64_t query_id = OB_INVALID_ID; if (ret == OB_SUCCESS && node != NULL) { switch (node->type_) { case T_STMT_LIST: { ret = resolve_multi_stmt(result_plan, node); break; } case T_SELECT: { ret = resolve_select_stmt(result_plan, node, query_id); break; } case T_DELETE: { ret = resolve_delete_stmt(result_plan, node, query_id); break; } case T_INSERT: { ret = resolve_insert_stmt(result_plan, node, query_id); break; } case T_UPDATE: { ret = resolve_update_stmt(result_plan, node, query_id); break; } default: ret = OB_ERROR; break; }; } return ret; } int resolve_update_stmt(ResultPlan* result_plan, ParseNode* node, uint64_t& query_id) { int& 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 = new(logical_plan) ObLogicalPlan(name_pool); result_plan->plan_tree_ = logical_plan; update_stmt = new(update_stmt) ObUpdateStmt(name_pool); query_id = logical_plan->generate_query_id(); //为update_stmt设置新的标识qid update_stmt->set_query_id(query_id); logical_plan->add_query(update_stmt); ParseNode* table_node = node->children_[0]; //解析表 ret = resolve_table(result_plan, update_stmt, table_node, table_id); update_stmt->set_update_table(table_id); ParseNode* assign_list = node->children_[1]; uint64_t ref_id; ColumnItem *column_item = NULL; //解析要更新的列表,如:update student set sex="M",grade="2" where name = "xiaoming"; for (int32_t i = 0; ret == OB_SUCCESS && i < assign_list->num_child_; i++) { ParseNode* assgin_node = assign_list->children_[i]; /* resolve target column */ ParseNode* column_node = assgin_node->children_[0]; ObString column_name; column_name.assign_ptr( (char*)(column_node->str_value_), static_cast<int32_t>(strlen(column_node->str_value_)) ); //1 依据列名获取列 column_item = update_stmt->get_column_item(NULL, column_name); //2 解析列到vector<ColumnItem *> ret = update_stmt->add_column_item(*result_plan, column_name, NULL, &column_item); //3 添加列引用到update_stmt ret = update_stmt->add_update_column(column_item->column_id_); /* resolve new value expression */ //4 解析值表达式 ParseNode* expr = assgin_node->children_[1]; ret = resolve_independ_expr(result_plan, update_stmt, expr, ref_id, T_UPDATE_LIMIT); //5 加入值表达式引用到update_stmt ret = update_stmt->add_update_expr(ref_id) } //解析where子句 ret = resolve_where_clause(result_plan, update_stmt, node->children_[2]); return ret; }
我们仍旧以update语句为例。
上面是依据源代码整理的逻辑,不是源代码。主要是为了理清思路。
- 首先是创建一个新的查询update_stmt,并为其生成一个独立的查询标识qid
- 解析语句中的表。并将表的标识tid加入到update_stmt的引用列表
- 利用for循环逐个解析要更新的列-值对:
(1). 依据列名获取列;
(2). 将该列存储到update_stmt
的vector<ColumnItem *>
中,并将列引用id加入到update_stmt
的更新列列表ObArray<uint64_t> update_columns_
中;
(3). 解析值表达式;
(4). 将值表达式引用id加入到更新值列表ObArray<uint64_t> update_exprs_
中去; - 解析where子句.
3.2 怎样解析表和列?
通过上面我们知道,逻辑计划的解析的一个重要内容就是要确定查询stmt,表,列,表达式的标识.查询和表达式的标识id都能够在解析的时候生成。由于这两项不是线程共同拥有的,可是表和列是持久的数据,能够跨线程使用相同的id。这些表和列的信息由谁来管理?
3.2.1 使用Schema
追根溯源,你会发现实体表和列的id是在ob_schema.cpp
中获取的。
什么是schema?schema就是数据库对象的一个集合。
网上有一个非常形象的比喻,我略微做了点修改:
淘宝数据库OceanBase SQL编译器部分 源代码阅读--生成逻辑计划