对pquery.cpp的解析(一)

源码链接

https://www.gitlink.org.cn/Eao3piq4e/openGauss-server/tree/master/src%2Fgausskernel%2Fprocess%2Ftcop%2Fpquery.cpp

概述

        该文件中有 Portal 模块中最重要的两个函数,一个是 PortalStart() ,另一个是 PortalRun() 。在本篇博客我会解析 PortalStart() 函数以及其它相关的函数和一些重要的结构体。

解析

PortalStart()

//代码清单1
//src/gausskernel/process/tcop/pquery.cpp
void PortalStart(Portal portal, ParamListInfo params, int eflags, Snapshot snapshot)
{
    gstrace_entry(GS_TRC_ID_PortalStart);
    Portal saveActivePortal;
    ResourceOwner saveResourceOwner;
    MemoryContext savePortalContext;
    MemoryContext oldContext;
    QueryDesc* queryDesc = NULL;
    int myeflags;
    PlannedStmt* ps = NULL;
    int instrument_option = 0;
······
    PG_TRY();
    {
······
    portal->strategy = ChoosePortalStrategy(portal->stmts);
        switch (portal->strategy) {
            case PORTAL_ONE_SELECT: {
                ps = (PlannedStmt*)linitial(portal->stmts);
······
#ifdef ENABLE_MOT
······
                queryDesc = CreateQueryDesc(
                    ps, portal->sourceText, tempSnap, InvalidSnapshot, None_Receiver, params, 0, mot_jit_context);
#else
······
                queryDesc = CreateQueryDesc(
                    ps, portal->sourceText, GetActiveSnapshot(), InvalidSnapshot, None_Receiver, params, 0);
#endif
······
                ExecutorStart(queryDesc, myeflags);
                portal->queryDesc = queryDesc;
                portal->tupDesc = queryDesc->tupDesc;
                portal->atStart = true;
                portal->atEnd = false;
                portal->portalPos = 0;
                portal->posOverflow = false;
······
                break;
            }
            case PORTAL_ONE_RETURNING:
            case PORTAL_ONE_MOD_WITH:
                {
                    PlannedStmt* pstmt = NULL;

                    pstmt = (PlannedStmt*)PortalGetPrimaryStmt(portal);
                    AssertEreport(IsA(pstmt, PlannedStmt), MOD_EXECUTOR, "pstmt is not a PlannedStmt");
                    portal->tupDesc = ExecCleanTypeFromTL(pstmt->planTree->targetlist, false, TAM_HEAP);
                }
                portal->atStart = true;
                portal->atEnd = false;
                portal->portalPos = 0;
                portal->posOverflow = false;
                break;

            case PORTAL_UTIL_SELECT:
                {
                    Node* ustmt = PortalGetPrimaryStmt(portal);

                    AssertEreport(!IsA(ustmt, PlannedStmt), MOD_EXECUTOR, "ustmt can not be a PlannedStmt");
                    portal->tupDesc = UtilityTupleDescriptor(ustmt);

                    if (portal->tupDesc != NULL)
                    {
                        portal->tupDesc->tdTableAmType = TAM_HEAP;
                    }
                }
                portal->atStart = true;
                portal->atEnd = false; /* allow fetches */
                portal->portalPos = 0;
                portal->posOverflow = false;
                break;

            case PORTAL_MULTI_QUERY:
                portal->tupDesc = NULL;

                if (ENABLE_WORKLOAD_CONTROL && IS_PGXC_DATANODE) {
                    WLMCreateDNodeInfoOnDN(NULL);
                    WLMCreateIOInfoOnDN();
                }
                break;
            default:
                break;
        }
        portal->stmtMemCost = 0;
    }
······
}

        不难看到,该函数的主体就是一个 switch case 分支结构,位于代码清单1第19~86行。有效的分支有4个:

1、PORTAL_ONE_SELECT、

2、PORTAL_ONE_RETURNING 和 PORTAL_ONE_MOD_WITH

3、PORTAL_UTIL_SELECT

4、PORTAL_MULTI_QUERY

每一个分支各自代表着特定的执行策略,均属于 PortalStrategy 枚举体中的成员常量,往前溯源,它们各自对应着特定的 SQL 语句:

//代码清单2
//src/include/utils/portal.h
typedef enum PortalStrategy
 {
     PORTAL_ONE_SELECT,
     PORTAL_ONE_RETURNING,
     PORTAL_ONE_MOD_WITH,
     PORTAL_UTIL_SELECT,
     PORTAL_MULTI_QUERY
 } PortalStrategy;

        PORTAL_ONE_SELECT 表明用户提交的 SQL 语句仅包含一个 SELECT 类型查询,PORTAL_ONE_RETURNING 表明用户提交的 SQL 语句包含一个带有 RETURNING 子句的 INSERT/UPDATE/DELETE 语句。PORTAL_ONE_MOD_WITH 表明用户提交的 SQL 语句不仅包含一个 SELECT 类型查询,并且在这个 SELECT 类型查询中它还用到了 CTEs (common table expressions),通俗点说,就是我们先用 CTEs 创建出一个临时表,然后再用 SELECT 类型查询去利用这个临时表获取我们想要的结果,这样做的好处是 SQL 代码的可读性会更强,不过它也仍然相当于 PORTAL_ONE_RETURNING 的简化,所以我们将 PORTAL_ONE_RETURNING 和  PORTAL_ONE_RETURNING 归为一类。PORTAL_UTIL_SELECT 表明用户提交的 SQL 语句是一个功能类型语句,即数据定义语句,但是其返回结果类似 SELECT 语句,例如 EXPLAIN 和 SHOW 。PORTAL_MULTI_QUERY 则表明用户提交的 SQL 语句不仅有可优化语句 (INSERT/DELETE/UPDATE/SELECT) ,还有数据定义语句,是前面几种类型的混合体。另外,关于 CTEs 的介绍,我建议看一下这篇文章:T-SQL—理解CTEs - 码农教程

        我们是通过 portal->strategy 的值来匹配的,自然地,我们看一下 Portal 结构体:

//代码清单3
//src/include/utils/portal.h
typedef struct PortalData* Portal;
typedef struct PortalData {
······
    const char* sourceText; /* text of query (as of 8.4, never NULL) */
    const char* commandTag; /* command tag for original query */
    List* stmts;            /* PlannedStmts and/or utility statements */
······
    PortalStrategy strategy;
······
    PortalStatus status;
······
    QueryDesc* queryDesc; /* info needed for executor invocation */
    TableScanDesc scanDesc;  // info needed for reducing memory allocation
                            // when reusing the portal for too many times
                            //(e.g., FETCH/MOVE cursor in a loop)
    TupleDesc tupDesc; /* descriptor for result tuples */
······
} PortalData;

看一看代码清单3中的代码,我们就对  portal->strategy 更清楚了,成员变量 strategy 的类型是枚举类型 PortalStrategy ,它的取值正是上述5种策略中的一种。再看一下其它的成员变量,sourceText 代表着用户提交的 SQL 语句,stmts 则是查询优化模块输出的查询计划树链表,status 指示了 Portal 的状态,queryDesc 是存储了执行查询计划所需信息的查询描述符,tupleDesc 用来描述可能返回的元组的结构。

        回到代码清单1,PORTAL_ONE_SELECT 策略下,我们首先要调用 CreateQueryDesc() 函数创建一个查询描述符,可以看到存在两个 CreateQueryDesc() 函数,这模拟了 C++ 中的函数重载,实际上是通过 #if 等条件编译命令实现了同时定义两个函数的目标,最好是能够去看一下CreateQueryDesc() 的源代码,也在 pquery.cpp 文件中。之后要做的,就是调用 ExecutorStart()对 Executor 模块初始化,并且为 Portal 模块保留必要的信息。PORTAL_ONE_RETURNING 和 PORTAL_ONE_MOD_WITH 策略下,调用 ExecCleanTypeFromTL() 为 Portal 模块创建返回元组的描述符并为 Portal 模块保留必要的信息。PORTAL_UTIL_SELECT 策略下,同样是要为 Portal 模块创建返回元组的描述符,不过用的函数是 UtilityTupleDescriptor() ,最后也要为 Portal 模块保留必要的信息。最后一种是 PORTAL_MULTI_QUERY 策略,这种情况下在 PortalStart() 中没有很多的执行步骤,而是集中在 PortalRun() 函数中,下一篇博客会有解析。

总结

        PortalStart() 是初始化 Portal 模块的函数,执行了 Portal 模块主要的功能,即策略的选择。相关的调用关系为:

我们可以很清楚地看到 Portal 模块中的初始化函数 PortalStart() 的很明显的功能就是分流,当执行语句的类别确定之后,我们才能确定是把计划树传送到 Executor 模块还是 ProcessUtility 模块,而这都是后话了,后续 PortalRun() 这是根据语句类别才实行相应的操作的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

奔走的月光

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

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

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

打赏作者

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

抵扣说明:

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

余额充值