对rewriteHandler.cpp的解析(一)

源码链接

https://www.gitlink.org.cn/Eao3piq4e/openGauss-server/tree/master/src%2Fgausskernel%2Foptimizer%2Frewrite%2FrewriteHandler.cpp

概述

        上一篇博客:对postgres.cpp的解析,我们主要解析了查询重写模块中十分重要的入口函数 pg_rewrite_query(),这篇博客我来介绍一下其在重写查询树时用到的 QueryRewrite() 函数以及不重写查询树时用到的函数 list_make1()。

解析

QueryRewrite()

//代码清单1
//src/gausskernel/optimizer/rewrite/rewriteHandler.cpp
List* QueryRewrite(Query* parsetree)
{
    uint64 input_query_id = parsetree->queryId;
    List* querylist = NIL;
    List* results = NIL;
    ListCell* l = NULL;
    CmdType origCmdType;
    bool foundOriginalQuery = false;
    Query* lastInstead = NULL;
······
    /*
     * Step 1
     *
     * Apply all non-SELECT rules possibly getting 0 or many queries
     */
    querylist = RewriteQuery(parsetree, NIL);
    /*
     * Step 2
     *
     * Apply all the RIR rules on each query
     *
     * This is also a handy place to mark each query with the original queryId
     */
    results = NIL;
    foreach (l, querylist) {
        Query* query = (Query*)lfirst(l);
        query = fireRIRrules(query, NIL, false);
        query->queryId = input_query_id;
        results = lappend(results, query);
    }
    /*
     * Step 3
     *
     * Determine which, if any, of the resulting queries is supposed to set
     * the command-result tag; and update the canSetTag fields accordingly.
     *
     * If the original query is still in the list, it sets the command tag.
     * Otherwise, the last INSTEAD query of the same kind as the original is
     * allowed to set the tag.  (Note these rules can leave us with no query
     * setting the tag.  The tcop code has to cope with this by setting up a
     * default tag based on the original un-rewritten query.)
     *
     * The Asserts verify that at most one query in the result list is marked
     * canSetTag.  If we aren't checking asserts, we can fall out of the loop
     * as soon as we find the original query.
     */
    origCmdType = parsetree->commandType;
    foundOriginalQuery = false;
    lastInstead = NULL;
······
    return results;
}

        对于不那么重要的部分我已进行省略,接下来我们看一看这个函数的结构。当处理一棵可重写的查询树时,有三个步骤。

        第一步,先调用 RewriteQuery() 来应用非 SELECT 规则,这是什么意思呢?首先,你得先明确一个意识,能被函数 QueryRewrite() 重写的查询树是由查询语句构造而来的,查询语句包括 SELECT/INSERT/DELETE/UPDATE 语句,这也就是说,我们将用 RewriteQuery() 只对由 INSERT/DELETE/UPDATE 语句构造而来的查询树进行重写

        第二步,遍历第一步中产生的查询树链表,我们将利用 fireRIRrules() 对这个链表中的每一棵查询树都应用 RIR 规则。那么,什么叫 RIR 规则呢?它的全称为 "Retrieve-Instead-Retrieve",这样的命名是有历史渊源的,我们已经知道 openGauss 由 PostgreSQL 改进而来,而 PostgreSQL 的前身是 Postgres,而 Postgres 用的是 QUEL/PostQUEL 语言,而 SQL 自身作为语言,一直到1992年才形成真正的国际标准,RETRIEVE 是我们所知道的 SQL 语言中的关键字 SELECT 的 PostQUEL 关键字。对 RETRIEVE 事件触发的规则,即只有一个 RETRIEVE 操作的无条件 INSTEAD 规则,便称其为 RIR 规则。综上,我们把以上操作换到 SQL 语言里面来说,其实就是使用 fireRIRrules() 对由 SELECT 语句构造而来的查询树进行重写

        第三步,就是将查询重写后的查询树链表返回,代码清单1第49行将原始查询树记录在 commandType 字段的命令类型记录下来,第50行用 foundOriginalQuery 变量记录是否找到原始查询树。

        另外,关于 RewriteQuery() 和 fireRIRrules() 的解析,我就不在这一篇博客继续讲了,日后再进行解析。

list_make1()

//代码清单2
//src/include/nodes/pg_list.h
#define list_make1(x1) lcons(x1, NIL)

        这个函数是一个宏函数,实际调用的是函数 lcons(),lcons() 函数如下:

//代码清单3
//src/common/backend/nodes/list.cpp
List* lcons(void* datum, List* list)
{
    Assert(IsPointerList(list));

    if (list == NIL) {
        list = new_list(T_List);
    } else {
        new_head_cell(list);
    }

    lfirst(list->head) = datum;
    check_list_invariants(list);
    return list;
}

        由代码清单2可知传递给 lcons() 的参数 list 为 NIL,那么将调用 new_list(),其源码如下:

//代码清单4
//src/common/backend/nodes/list.cpp
static List* new_list(NodeTag type)
{
    List* newList = NIL;
    ListCell* newHead = NULL;

    /* new_head->data is left undefined! */
    newHead = (ListCell*)palloc(sizeof(*newHead));
    newHead->next = NULL;

    newList = (List*)palloc(sizeof(*newList));
    newList->type = type;
    newList->length = 1;
    newList->head = newHead;
    newList->tail = newHead;

    return newList;
}

不难看出,这个函数的作用其实十分简单,无非是创建一条只有一个元素的链表,然后用一个 List* 类型的指针指向它的头和尾,最后返回这个指针。接着返回到代码清单3,到了第13行,lfirst() 函数如下:

//代码清单5
//src/include/nodes/pg_list.h
#define lfirst(lc) ((lc)->data.ptr_value)

这就涉及到了 List 结构体和 ListCell 结构体的字段了,我们来看一看:

//代码清单6
//src/include/nodes/pg_list.h
typedef struct ListCell ListCell;
typedef struct List {
    NodeTag type; /* T_List, T_IntList, or T_OidList */
    int length;
    ListCell* head;
    ListCell* tail;
} List;

struct ListCell {
    union {
        void* ptr_value;
        int int_value;
        Oid oid_value;
    } data;
    ListCell* next;
};

可以看到,ListCell 结构体内部包含了一个联合体,而 List 结构体内部又包含了两个 ListCell* 类型的指针,head 和 tail 分别指向由 ListCell 结构体构造出来的链表的头和尾。当调用 lfirst() 时,我们实际上是将原始的查询树的地址存入了链表的首个元素的 ptr_value 字段中,当然,这是就上一篇博客中解析的主调函数 pg_rewrite_query() 而言的。至此,对 pg_rewrite_query() 而言,其实当它以原始查询树为 list_make1() 的参数时,实际并未对原始查询树做处理,只是将它进行了降级。

总结

        上一篇博客我们解析了 pg_rewrite_query(),而这篇博客我解析了其在重写查询树时主要用到的 QueryRewrite() 函数以及不重写查询树时用到的函数 list_make1(),它们的分界线就是是否要对原始查询树进行重写操作,根据这一点就能合理地选择了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奔走的月光

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值