好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

淘宝数据库OceanBaseSQL编译器部分源码阅读

淘宝数据库OceanBase SQL编译器部分 源码阅读--生成逻辑计划 淘宝数据库OceanBase SQL编译器部分 源码阅读--解析SQL语法树里做了介绍,这篇博客主要研究第二步,生成逻辑计划。 一、 什么是逻辑计划? 我们已经知道,语法树就是一个树状的结构组织,每个节点

淘宝数据库OceanBase 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  stmts_;  //存储该逻辑计划的所有stmt 
      oceanbase::common::ObVector  exprs_;  //逻辑计划的所有表达式 
      oceanbase::common::ObVector  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 ,使用时转为 vector .

2.4 逻辑计划中的Stmt的定义

Stmt表示一个单独的查询所包含的内容,一个逻辑计划可以包含多个Stmt.

      class    ObStmt   
    {
     /*省略部分内容...*/ 
     protected: 
       common:  :ObVector      table_items _ ;
       common:  :ObVector     column_items _ ;

     private: 
       StmtType   type _ ;
      uint64_t  query_id _ ;
       //uint 64_t  where_expr_id _ ;
       common:  :ObVector       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  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  ( strlen (column_node->str_value_))
                );
       //1 根据列名获取列 
      column_item = update_stmt->get_column_item(NULL, column_name);
       //2 解析列到vector  
      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 中,并将列引用id添加到 update_stmt 的更新列列表 ObArray update_columns_ 中;
(3). 解析值表达式;
(4). 将值表达式引用id添加到更新值列表 ObArray update_exprs_ 中去; 解析where子句.

3.2 如何解析表和列?

通过上面我们知道,逻辑计划的解析的一个重要内容就是要确定查询stmt,表,列,表达式的标识.查询和表达式的标识id都可以在解析的时候生成。因为这两项不是线程共有的,但是表和列是持久的数据,可以跨线程使用同样的id。这些表和列的信息由谁来管理?

3.2.1 使用Schema

追根溯源,你会发现实体表和列的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。
如果阅读本文过程中有任何问题,请联系作者

查看更多关于淘宝数据库OceanBaseSQL编译器部分源码阅读的详细内容...

  阅读:40次