源码链接
概述
该文件中有 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() 这是根据语句类别才实行相应的操作的。