源码链接
概述
在该文件中定义了很多用于语义解析的函数,可以说该文件在生成查询树的过程中不可或缺。在上一篇博客里我解析了 transformStmt() 函数:对analyze.cpp的解析(一)
,这篇博客我来说说这个文件下其它函数的机理。
解析
transformTopLevelStmt()
//代码清单1
//src/common/backend/parser/analyze.cpp
Query* transformTopLevelStmt(ParseState* pstate, Node* parseTree, bool isFirstNode, bool isCreateView)
{
if (IsA(parseTree, SelectStmt)) {
SelectStmt* stmt = (SelectStmt*)parseTree;
/* If it's a set-operation tree, drill down to leftmost SelectStmt */
while (stmt != NULL && stmt->op != SETOP_NONE)
stmt = stmt->larg;
AssertEreport(stmt && IsA(stmt, SelectStmt) && stmt->larg == NULL, MOD_OPT, "failure to check parseTree");
if (stmt->intoClause) {
CreateTableAsStmt* ctas = makeNode(CreateTableAsStmt);
ctas->query = parseTree;
ctas->into = stmt->intoClause;
ctas->relkind = OBJECT_TABLE;
ctas->is_select_into = true;
/*
* Remove the intoClause from the SelectStmt. This makes it safe
* for transformSelectStmt to complain if it finds intoClause set
* (implying that the INTO appeared in a disallowed place).
*/
stmt->intoClause = NULL;
parseTree = (Node*)ctas;
}
}
if (u_sess->hook_cxt.transformStmtHook != NULL) {
return
((transformStmtFunc)(u_sess->hook_cxt.transformStmtHook))(pstate, parseTree, isFirstNode, isCreateView);
}
return transformStmt(pstate, parseTree, isFirstNode, isCreateView);
}
首先在代码清单1第36行,我们就可以看到该函数调用了 transformStmt() 函数,这说明该函数处于 transformStmt() 函数的上一层。然后回到开头,在第5行可以看到调用了 IsA() 函数,该函数原型为:
//代码清单2
//src/include/nodes/nodes.h
#define IsA(nodeptr, _type_) (nodeTag(nodeptr) == T_##_type_)
对于这个宏函数我讲一下符号连接操作符 ## ,它的作用是将宏定义的多个形参转换成一个实际参数名。这是个需要注意的点,T_##_type_ 并不是作为一个最终被传递的参数的,最终处理的结果是在 _type_ 前面加了 T_ 前缀。
根据 nodeTag() 和 IsA() 的执行过程,可知在代码清单1第5~30行的 if 语句执行的条件是parseTree->type = T_SelectStmt。如果需要执行,那么我们需要先将 parseTree 强制转换为 SelectStmt 结构体类型,这就是代码清单1第6行代码干的事。那问题来了,要继续了解后面的代码,就需要知道 SelectStmt 结构体,它的样式为:
//代码清单3
//src/include/nodes/parsenodes_common.h
typedef struct SelectStmt {
NodeTag type;
/*
* These fields are used only in "leaf" SelectStmts.
*/
List *distinctClause; /* NULL, list of DISTINCT ON exprs, or
* lcons(NIL,NIL) for all (SELECT DISTINCT) */
IntoClause *intoClause; /* target for SELECT INTO */
List *targetList; /* the target list (of ResTarget) */
List *fromClause; /* the FROM clause */
Node *startWithClause; /* START WITH...CONNECT BY clause */
Node *whereClause; /* WHERE qualification */
List *groupClause; /* GROUP BY clauses */
Node *havingClause; /* HAVING conditional-expression */
List *windowClause; /* WINDOW window_name AS (...), ... */
WithClause *withClause; /* WITH clause */
/*
* In a "leaf" node representing a VALUES list, the above fields are all
* null, and instead this field is set. Note that the elements of the
* sublists are just expressions, without ResTarget decoration. Also note
* that a list element can be DEFAULT (represented as a SetToDefault
* node), regardless of the context of the VALUES list. It's up to parse
* analysis to reject that where not valid.
*/
List *valuesLists; /* untransformed list of expression lists */
/*
* These fields are used in both "leaf" SelectStmts and upper-level
* SelectStmts.
*/
List *sortClause; /* sort clause (a list of SortBy's) */
Node *limitOffset; /* # of result tuples to skip */
Node *limitCount; /* # of result tuples to return */
List *lockingClause; /* FOR UPDATE (list of LockingClause's) */
HintState *hintState;
/*
* These fields are used only in upper-level SelectStmts.
*/
SetOperation op; /* type of set op */
bool all; /* ALL specified? */
struct SelectStmt *larg; /* left child */
struct SelectStmt *rarg; /* right child */
/*
* These fields are used by operator "(+)"
*/
bool hasPlus;
/* Eventually add fields for CORRESPONDING spec here */
} SelectStmt;
各个 Stmt 结构体都是一个特殊的节点,SelectStmt 结构体也不例外,它们由 NodeTag 来标识。如果用面向对象的思维来理解的话,可以简单的看成是很多的特殊子节点继承自一个同一个父节点,这个父节点就是我之前提到的 Node 结构体。这种设计并没有减少代码量,但是可以使函数拥有统一的对外接口,更容易编写函数。相比万能指针 void* ,更能够提高调试能力。
在代码清单1第9、10行,我们使用了 stmt 的成员变量 op 和 larg ,而相应的注释是这些成员变量只能在语法树中上层的 SelectStmt 节点被使用,这也就是说,代码清单1中的 stmt 是上层的 SelectStmt 定义出来的变量,而 transformTopLevelStmt() 也确实调用了 transformStmt() 函数,在transformStmt() 的上层,这变相地证明了这一点。
在代码清单1第13~29行,如果这条 SELECT 语句是 SELECT INTO 语句,那么就执行这个语句块。SELECT INTO 语句从一个表复制数据,然后把数据插入到另一个新表中。它的基本语法像这样:
SELECT column_name INTO newtable FROM oldtable;
而 SELECT INTO 语句的功能和 CREATE TABLE AS 语句的功能是一样的,这才有了13~29行的节点类型转换,以便将 SELECT INTO 语句转化为对应的 CREATE TABLE AS 语句,方便后续的处理。CREATE TABLE AS 语句的基本语法像这样:
CREATE TABLE newtable AS (SELECT column_name FROM oldtable);
CreateTableAsStmt 结构体是这样的:
//代码清单4
//src/include/nodes/parsenodes_common.h
typedef struct CreateTableAsStmt {
NodeTag type;
Node* query; /* the query (see comments above) */
IntoClause* into; /* destination table */
ObjectType relkind; /* type of object */
bool is_select_into; /* it was written as SELECT INTO */
#ifdef PGXC
Oid groupid;
void* parserSetup;
void* parserSetupArg;
#endif
} CreateTableAsStmt;
在代码清单1第14行利用 makeNode() 函数创建了一个 CreateTableAsStmt 类型的节点,指针变量 ctas 指向它,同时 ctas->type 已经被被设置为了 T_CreateTableAsStmt ,具体原理请参见我的上一篇博客。第16行把原先的查询树的地址保存到 query 成员变量中,第17行用 into 成员变量保存了目标表即 newtable 的信息,第18行用 relkind 指定了对象为表类型,第19行用 is_select_into 表明该 CREATE TABLE AS 语句确实是由 SELECT INTO 语句转换而来的,第26行将变量 stmt 的成员变量 intoClause 置为 NULL 以便在之后能够安全地间接调用 transformSelectStmt() ,第28行将经过类型强制转换的 ctas 赋给指向原先的查询树的指针 parseTree 。
总结
通过这篇博客,我又解析了语义分析中最为重要的函数之一,即 transformTopLevelStmt() ,它在内部又调用了 transformStmt() ,而通过上一篇博客我们已经知道 transformStmt() 是语义分析中最后的一个关键函数,所以 transformTopLevelStmt() 在内部调用 transformStmt() 是因为它此时还没有对语法树做真正的语义分析。