源码链接
概述
上一篇博客:对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(),它们的分界线就是是否要对原始查询树进行重写操作,根据这一点就能合理地选择了。