1. 程式人生 > >淘寶數據庫OceanBase SQL編譯器部分 源代碼閱讀--生成邏輯計劃

淘寶數據庫OceanBase SQL編譯器部分 源代碼閱讀--生成邏輯計劃

多重 做了 return int32 ack sun 數據庫系統 unary left

淘寶數據庫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就是數據庫對象的一個集合

網上有一個非常形象的比喻,我略微做了點修改:

淘寶數據庫OceanBase SQL編譯器部分 源代碼閱讀--生成邏輯計劃