standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
queryDesc 是一个指向 QueryDesc 结构的指针。QueryDesc 结构包含了执行查询所需的上下文信息,包括查询计划、参数值、结果集等。
eflags 是一个整数参数,代表执行标志(execution flags)。这个参数用于指定执行查询时的一些选项和行为。通过指定不同的标志,可以影响查询的执行方式和结果。
一些常见的 eflags 标志包括:
EXEC_FLAG_EXPLAIN_ONLY:表示仅执行查询计划的解释,并不实际执行查询。
EXEC_FLAG_WITH_NO_DATA:表示查询执行不需要返回结果集。
EXEC_FLAG_WITHOUT_OIDS:表示查询执行不需要返回对象标识符。
EXEC_FLAG_REWIND:表示执行查询前需要将游标或扫描器重置到起始位置。
通过指定不同的标志,可以根据具体需求对查询的执行行为进行控制和调整。
/* 如果事务是只读的,我们需要检查是否计划对非临时表进行写操作。EXPLAIN 被视为只读。
不允许在并行模式下进行写操作。支持 UPDATE 和 DELETE 将需要
(a)在共享内存中存储组合 CID 哈希,而不是仅在并行性开始时同步它一次
(b)为了互斥,heap_update() 对 xmax 的依赖性的替代。
INSERT 可能没有这些问题,但我们禁止它以简化检查。
在 CommandCounterIncrement 和其他地方,我们对在并行模式下执行不安全操作有更低级别的防御措施,但这样可以提供更用户友好的错误消息。 */
if ((XactReadOnly || IsInParallelMode()) && !(eflags & EXEC_FLAG_EXPLAIN_ONLY))
ExecCheckXactReadOnly(queryDesc->plannedstmt);
Estate是一个执行状态的结构,用于跟踪和管理查询执行的状态和上下文。它包含了执行查询所需的各种信息,如查询计划、参数值、结果集等。构建 EState 是为了在执行过程中管理和操作这些信息。
// 构建 EState,切换到每个查询的内存上下文以进行启动阶段。
estate = CreateExecutorState();
queryDesc->estate = estate;
在启动阶段,需要构建 EState 对象,并将内存上下文切换到每个查询的内存上下文,以便为查询执行提供独立的内存环境和隔离性。这有助于确保查询的执行过程在正确的上下文环境中进行,并避免与其他查询的冲突。
//其作用是切换当前的内存上下文到 estate->es_query_cxt 上下文。
oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
通过切换内存上下文,当前的内存分配和回收操作将在 estate->es_query_cxt 上下文中进行,这意味着在查询执行期间分配的内存将在该上下文中进行管理。同时,原来的内存上下文会被保存到 oldcontext 变量中,以便在适当的时候可以恢复回来。
// 从 queryDesc 填充外部参数(如果有),并为内部参数分配工作空间
estate->es_param_list_info = queryDesc->params;
外部参数是由外部环境传递给查询的参数,如客户端传递的查询参数。
内部参数是在查询执行期间内部使用的参数,通常用于在查询计划的不同阶段传递信息或状态。
// 如果 queryDesc->plannedstmt->paramExecTypes 不是空列表(NIL)
if (queryDesc->plannedstmt->paramExecTypes != NIL)
{
int nParamExec;
// 获取 queryDesc->plannedstmt->paramExecTypes 列表的长度,即外部参数的数量,并将结果保存在 nParamExec 变量中。
nParamExec = list_length(queryDesc->plannedstmt->paramExecTypes);
// 分配一个大小为 nParamExec * sizeof(ParamExecData) 的内存空间,并将其初始化为零。
// 这里使用了 palloc0 函数来分配内存,并确保所分配的内存中的内容为零。
// 将分配的内存空间的起始地址赋值给 estate->es_param_exec_vals,即执行状态结构 estate 中的 es_param_exec_vals 成员。
// es_param_exec_vals 是一个指向 ParamExecData 结构的指针,用于存储外部参数的执行数据。
estate->es_param_exec_vals = (ParamExecData *) palloc0(nParamExec * sizeof(ParamExecData));
}
通过执行以上操作,为执行状态结构 estate 中的 es_param_exec_vals 成员分配了足够的内存空间,用于存储外部参数的执行数据。这样,在查询执行过程中可以使用这些数据来替换查询计划中的参数,并传递给执行器进行实际的查询操作。
/* 现在我们要求所有的调用者提供 sourceText */
Assert(queryDesc->sourceText != NULL);
// estate 结构中的 es_sourceText 将包含调用者提供的查询文本。
estate->es_sourceText = queryDesc->sourceText;
确保了调用者在使用这段代码或函数时必须提供有效的 sourceText 参数。这可以防止潜在的错误或信息不完整的情况,并在需要时提供准确的查询文本信息。
// 从 queryDesc 填充查询环境(如果有的话)
estate->es_queryEnv = queryDesc->queryEnv;
查询环境(query environment)是一组与查询相关的环境变量或上下文信息的集合。它包括一些查询执行所需的上下文信息,如当前数据库、当前用户、事务状态等。
// 如果是非只读查询,设置命令ID以标记输出元组
switch (queryDesc->operation)
{
case CMD_SELECT:
// CTE 公共表达式
/*
* 对于带有 SELECT FOR [KEY] UPDATE/SHARE 或修改 CTE 的 SELECT 语句,
* 需要标记元组。
* 通过调用 GetCurrentCommandId(true) 获取当前命令的命令ID,并将其赋值给 estate 结构中的 es_output_cid 成员。
*/
if (queryDesc->plannedstmt->rowMarks != NIL ||
queryDesc->plannedstmt->hasModifyingCTE)
estate->es_output_cid = GetCurrentCommandId(true);
/*
* 没有修改 CTE 的 SELECT 语句不可能有触发器队列,
* 所以强制跳过触发器模式。这只是一个边缘的效率优化技巧,
* 因为 AfterTriggerBeginQuery/AfterTriggerEndQuery 并不是非常耗费资源,
* 但是我们可以这样做。
*/
/* 对于没有修改 CTE 的 SELECT 语句,由于不可能有触发器队列的情况,
为了提高效率,将执行标志 eflags 设置为 EXEC_FLAG_SKIP_TRIGGERS,
跳过触发器模式。
*/
if (!queryDesc->plannedstmt->hasModifyingCTE)
eflags |= EXEC_FLAG_SKIP_TRIGGERS;
break;
/*
* 对于 CMD_INSERT(插入)、CMD_DELETE(删除)和 CMD_UPDATE(更新)操作,
* 同样将当前命令的命令ID赋值给 estate 结构中的 es_output_cid 成员
*/
case CMD_INSERT:
case CMD_DELETE:
case CMD_UPDATE:
estate->es_output_cid = GetCurrentCommandId(true);
break;
// 对于其他未知的操作类型,将会抛出一个错误,报告无法识别的操作代码
default:
elog(ERROR, "unrecognized operation code: %d",
(int) queryDesc->operation);
break;
}
/*
设置一个 AFTER 触发器语句上下文,除非被告知不要设置,或者在仅进行 EXPLAIN 的模式下(此时不会调用 ExecutorFinish)。
*/
if (!(eflags & (EXEC_FLAG_SKIP_TRIGGERS | EXEC_FLAG_EXPLAIN_ONLY)))
AfterTriggerBeginQuery();
/*
初始化计划状态树
*/
// 调用 InitPlan(queryDesc, eflags) 函数来初始化查询计划的状态树。
// 在执行查询之前,需要准备好执行计划所需的状态和数据结构,以便后续的执行操作。
InitPlan(queryDesc, eflags);
// 通过 MemoryContextSwitchTo(oldcontext) 切换回之前的内存上下文,将执行上下文切换回原始的内存上下文。
// 这是为了确保在初始化过程中使用的内存上下文不会影响后续的执行过程,避免内存泄漏或上下文混乱的问题。
MemoryContextSwitchTo(oldcontext);
这段代码用于初始化查询计划的状态树。并将上下文信息切换到oldcontext。
standard_ExecutorStart 函数的内容可以总结如下: