• 淘宝数据库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语句为例。

    上面是依据源代码整理的逻辑,不是源代码。主要是为了理清思路。

    1. 首先是创建一个新的查询update_stmt,并为其生成一个独立的查询标识qid
    2. 解析语句中的表。并将表的标识tid加入到update_stmt的引用列表
    3. 利用for循环逐个解析要更新的列-值对:
      (1). 依据列名获取列;
      (2). 将该列存储到update_stmtvector<ColumnItem *>中,并将列引用id加入到update_stmt的更新列列表ObArray<uint64_t> update_columns_中;
      (3). 解析值表达式;
      (4). 将值表达式引用id加入到更新值列表ObArray<uint64_t> update_exprs_中去;
    4. 解析where子句.

    3.2 怎样解析表和列?

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

    3.2.1 使用Schema

    追根溯源,你会发现实体表和列的id是在ob_schema.cpp中获取的。

    什么是schema?schema就是数据库对象的一个集合

    网上有一个非常形象的比喻,我略微做了点修改:

    相关阅读:
    Dos.ORM logo.Net轻量级开源ORM框架 Dos.ORM
    C# FUNC 应用
    WCF教程网址
    C#扩展方法实现 byte[] 输出为HEX字符串形式
    apache配置,禁止指定后缀访问
    IServerChannelSinkProvider
    在服务器端的远程对象中加上抽象工厂的接口和实现类
    pannel加载窗体
    权限框架
    工作周记
  • 原文地址:https://www.cnblogs.com/lxjshuju/p/7008463.html
Copyright © 2020-2023  润新知