对analyze.cpp的解析(二)

源码链接

https://www.gitlink.org.cn/Eao3piq4e/openGauss-server/tree/master/src%2Fcommon%2Fbackend%2Fparser%2Fanalyze.cpp

概述

        在该文件中定义了很多用于语义解析的函数,可以说该文件在生成查询树的过程中不可或缺。在上一篇博客里我解析了 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() 是因为它此时还没有对语法树做真正的语义分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

奔走的月光

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

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

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

打赏作者

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

抵扣说明:

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

余额充值