源码链接
概述
在我之前的这篇对查询分析模块的总结性博客:查询分析模块总结,我提到的pg_parse_query()、pg_analyze_and_rewrite() 和 exec_simple_query() 函数均在 postgres.cpp 文件中,关于这几个函数的作用,我想之前这篇博客的一张图就能很好地说明了:
可以看到,exec_simple_query() 是主调函数,pg_parse_query() 利用查询语句得到分析树链表,pg_analyze_and_rewrite() 则处理分析树链表并返回重写后的查询树链表。实际上,当pg_analyze_and_rewrite() 接收到要处理分析树链表的任务时,它麾下的 parse_analyze() 就负责将分析树链表转化为查询树链表,之后的重写工作自然就落到了 pg_rewrite_query() 肩上。在这篇博客,我要介绍的的正是 pg_rewrite_query() 所做的工作。
解析
pg_rewrite_query()
//代码清单1
//src/gausskernel/process/tcop/postgres.cpp
static List* pg_rewrite_query(Query* query)
{
List* querytree_list = NIL;
······
if (query->commandType == CMD_UTILITY) {
#ifdef ENABLE_MULTIPLE_NODES
if (IsA(query->utilityStmt, CreateTableAsStmt))
{
/*
* CREATE TABLE AS SELECT and SELECT INTO are rewritten so that the
* target table is created first. The SELECT query is then transformed
* into an INSERT INTO statement
*/
/* handle materilized view. */
CreateTableAsStmt *stmt = (CreateTableAsStmt *)query->utilityStmt;
if (IS_PGXC_COORDINATOR && stmt->relkind == OBJECT_MATVIEW)
{
/* Check if OK to create matview */
check_basetable((Query *)stmt->query, true, stmt->into->ivm);
/* Check for select privilege */
(void)ExecCheckRTPerms(query->rtable, true);
······
}
if (stmt->relkind == OBJECT_MATVIEW && IS_PGXC_DATANODE)
{
querytree_list = list_make1(query);
}
else
{
/*
* CREATE TABLE AS SELECT and SELECT INTO are rewritten so that the
* target table is created first. The SELECT query is then transformed
* into an INSERT INTO statement
*/
querytree_list = QueryRewriteCTAS(query);
}
}
else if (IsA(query->utilityStmt, CopyStmt))
{
querytree_list = query_rewrite_copy(query);
}
else if(IsA(query->utilityStmt, RefreshMatViewStmt))
{
RefreshMatViewStmt *stmt = (RefreshMatViewStmt *)query->utilityStmt;
if (!stmt->incremental && !isIncMatView(stmt->relation))
{
querytree_list = QueryRewriteRefresh(query);
}
else
{
querytree_list = list_make1(query);
}
}
else if (IsA(query->utilityStmt, AlterTableStmt))
{
querytree_list = query_rewrite_alter_table(query);
}
else
{
/* don't rewrite utilities, just dump them into result list */
querytree_list = list_make1(query);
}
#else
if (IsA(query->utilityStmt, CreateTableAsStmt))
{
CreateTableAsStmt *stmt = (CreateTableAsStmt *)query->utilityStmt;
if (stmt->relkind == OBJECT_MATVIEW)
{
/* Check if OK to create matview */
check_basetable((Query *)stmt->query, true, stmt->into->ivm);
/* Check for select privilege */
(void)ExecCheckRTPerms(query->rtable, true);
if (stmt->into && stmt->into->ivm)
{
check_matview_op_supported(stmt);
}
querytree_list = list_make1(query);
}
else
{
querytree_list = QueryRewriteCTAS(query);
}
}
else
{
querytree_list = list_make1(query);
}
#endif
}
else
{
/* rewrite regular queries */
querytree_list = QueryRewrite(query);
}
······
return querytree_list;
}
这个函数分支挺多的,总的分支有两条,一条用于由功能性语句构造的查询树(有些分支是可以重写的),另一条则用于所有分支均可重写的查询树,比如由 SELECT 语句构造的查询树。对于由非功能性语句构造出来的查询树,在代码清单1第99行将调用 QueryRewrite() 来对查询树进行重写操作,而对于由功能性语句构造出来的查询树,即由建表、创建用户、备份等功能性语句构造出来的查询树,就会出现很多分支了,这些分支在代码清单1第8~94行十分清楚。
代码清单1中很多语句块的判断条件用到了 IsA() 函数,在先前的博客中我也讲解了它的作用,它被用来判断语句的类型,源码如下:
//代码清单2
//src/include/nodes/nodes.h
#define IsA(nodeptr, _type_) (nodeTag(nodeptr) == T_##_type_)
这个宏定义后半部分的 "##" 是起连接作用的,是将 _type_ 连接到 T_ 后面的,至于 nodeTag() 函数的用处我在这篇博客已经讲过了:对analyze.cpp的解析(一),在此不做赘述。
那么接下来讲回代码清单1对于由功能性语句构造出来的查询树的操作分支,在 ENABLE_MULTIPLE_NODES 模式下,又分为建表语句( CreateTableAsStmt )、备份语句( CopyStmt )、物化视图刷新语句( RefreshMatViewStmt )、更改表语句( AlterTableStmt ),在其它模式下,则只有建表语句,接下来继续分析。
由代码清单1第27~39和第70~88行,无论是不是在 ENABLE_MULTIPLE_NODES 模式下,如果查询树由建表语句构造而来,那么只有两种选择,一是利用 list_make1() 不做修改地将最终返回的链表设置为原先的查询树链表,而是利用 QueryRewriteCTAS() 对由 CREATE TABLE AS SELECT 和 SELECT INTO 语句构造的查询树重写为 INSERT INTO 语句,这三种语句的功能都是从旧表中拷贝一些数据到新表中,推荐这个网站进行浏览:SQL SELECT INTO 语句 | 菜鸟教程。
在 ENABLE_MULTIPLE_NODES 模式下,如果等待处理的查询树由备份语句构造而来,那么就调用 query_rewrite_copy() 进行重写;如果由物化视图刷新语句构造而来,就调用 QueryRewriteRefresh() 或者 list_make1() ;如果由更改表语句构造而来,那么就调用query_rewrite_alter_table() 进行重写,最后的一种情况就是不对原先的查询树重写,而是将它放入结果链表中去。在其它模式下,流程没有区别。
总结
这个函数内部的分支其实看代码就可以看出来,其结构是十分的清晰明了的。这个函数的代码结构确实简单,但是它起到的作用仍然是十分大的,比如它可以支持更多的语句,我们可以将CREATE TABLE AS SELECT 和 SELECT INTO 语句重写为 INSERT INTO 语句,这样就能够支持更多的语句类型。