OpenGuass开源代码评注赛-针对Executor的代码评注 #35
|
|
@ -1524,7 +1524,7 @@ void InitPlan(QueryDesc *queryDesc, int eflags)
|
|||
|
||||
if (plannedstmt->num_streams > 0 && !StreamThreadAmI() &&
|
||||
!(eflags & EXEC_FLAG_EXPLAIN_ONLY)) {
|
||||
/* init stream thread in parallel */
|
||||
/* 在并行中初始化流线程 */
|
||||
StartUpStreamInParallel(queryDesc->plannedstmt, queryDesc->estate);
|
||||
}
|
||||
|
||||
|
|
@ -2062,11 +2062,12 @@ static void ExecCollectMaterialForSubplan(EState *estate)
|
|||
foreach (lc, estate->es_material_of_subplan) {
|
||||
PlanState *node = (PlanState *)lfirst(lc);
|
||||
|
||||
/*
|
||||
* If the current materliaze node is recursive-union and the right tree has stream
|
||||
* node, we are skip the pre-materliaze the subplan as at current point the SyncPoint
|
||||
* on consumer side is not start yet in ExecRecursiveUnion()
|
||||
*/
|
||||
/*
|
||||
* 如果当前的物化节点是递归联合节点,并且右子树有流节点,
|
||||
* 我们将跳过对子查询计划的预物化,因为在当前时刻,
|
||||
* 在 ExecRecursiveUnion() 函数中,消费者端的同步点还未启动。
|
||||
*/
|
||||
|
||||
if (EXEC_IN_RECURSIVE_MODE(node->plan)) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -2075,13 +2076,15 @@ static void ExecCollectMaterialForSubplan(EState *estate)
|
|||
for (;;) {
|
||||
TupleTableSlot *slot = NULL;
|
||||
|
||||
/* Reset the per-output-tuple exprcontext each time */
|
||||
/* 每次重置输出元组的表达式上下文 */
|
||||
|
||||
ResetPerTupleExprContext(estate);
|
||||
|
||||
slot = ExecProcNode(node);
|
||||
|
||||
if (TupIsNull(slot)) {
|
||||
/* Reset Material so that its output can be re-scanned */
|
||||
/* 重置物化操作,以便可以重新扫描其输出 */
|
||||
|
||||
ExecReScan(node);
|
||||
break;
|
||||
}
|
||||
|
|
@ -2090,13 +2093,14 @@ static void ExecCollectMaterialForSubplan(EState *estate)
|
|||
for (;;) {
|
||||
VectorBatch *batch = NULL;
|
||||
|
||||
/*
|
||||
* Execute the plan and obtain a batch
|
||||
*/
|
||||
/*
|
||||
* 执行计划并获取一个批次
|
||||
*/
|
||||
|
||||
batch = VectorEngine(node);
|
||||
|
||||
if (BatchIsNull(batch)) {
|
||||
/* Reset Material so that its output can be re-scanned */
|
||||
/* 重置物化操作,以便可以重新扫描其输出 */
|
||||
VecExecReScan(node);
|
||||
break;
|
||||
}
|
||||
|
|
@ -2108,15 +2112,14 @@ static void ExecCollectMaterialForSubplan(EState *estate)
|
|||
/* ----------------------------------------------------------------
|
||||
* ExecutePlan
|
||||
*
|
||||
* Processes the query plan until we have retrieved 'numberTuples' tuples,
|
||||
* moving in the specified direction.
|
||||
* 处理查询计划,直到我们检索到 'numberTuples' 个元组,按照指定的方向移动。
|
||||
*
|
||||
* Runs to completion if numberTuples is 0
|
||||
* 如果 numberTuples 为 0,则运行到完成。
|
||||
*
|
||||
* Note: the ctid attribute is a 'junk' attribute that is removed before the
|
||||
* user can see it
|
||||
* 注意:ctid 属性是一个在用户可见之前被移除的 'junk' 属性。
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifdef ENABLE_MOT
|
||||
static void ExecutePlan(EState *estate, PlanState *planstate, CmdType operation, bool sendTuples, long numberTuples,
|
||||
ScanDirection direction, DestReceiver *dest, JitExec::JitContext* motJitContext)
|
||||
|
|
@ -2134,30 +2137,32 @@ static void ExecutePlan(EState *estate, PlanState *planstate, CmdType operation,
|
|||
bool motFinishedExecution = false;
|
||||
#endif
|
||||
|
||||
/* Mark sync-up step is required */
|
||||
/* 标记需要同步步骤 */
|
||||
|
||||
if (NeedSyncUpProducerStep(planstate->plan)) {
|
||||
need_sync_step = true;
|
||||
/*
|
||||
* (G)Distributed With-Recursive Support
|
||||
*
|
||||
* If current producer thread is under a recursive cte plan node, we need do
|
||||
* step sync-up across the whole cluster
|
||||
*/
|
||||
/*
|
||||
* (G)分布式带递归支持
|
||||
*
|
||||
* 如果当前的生产者线程位于递归CTE计划节点下,我们需要在整个集群中进行步骤同步。
|
||||
*/
|
||||
|
||||
u_sess->exec_cxt.global_iteration = 0;
|
||||
ExecutePlanSyncProducer(planstate, WITH_RECURSIVE_SYNC_NONERQ, &recursive_early_stop, ¤t_tuple_count);
|
||||
u_sess->exec_cxt.global_iteration = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the direction.
|
||||
*/
|
||||
/*
|
||||
* 设置方向。
|
||||
*/
|
||||
|
||||
estate->es_direction = direction;
|
||||
|
||||
if (IS_PGXC_DATANODE) {
|
||||
/* Collect Material for Subplan first */
|
||||
/* 首先收集子查询计划的物化数据 */
|
||||
ExecCollectMaterialForSubplan(estate);
|
||||
|
||||
/* Collect Executor run time including sending data time */
|
||||
/* 收集执行器运行时间,包括发送数据所用时间 */
|
||||
if (estate->es_instrument != INSTRUMENT_NONE && u_sess->instr_cxt.global_instr &&
|
||||
u_sess->instr_cxt.thread_instr) {
|
||||
stream_instrument = true;
|
||||
|
|
@ -2166,21 +2171,24 @@ static void ExecutePlan(EState *estate, PlanState *planstate, CmdType operation,
|
|||
}
|
||||
}
|
||||
|
||||
/* Change DestReceiver's tmpContext to PerTupleMemoryContext to avoid memory leak. */
|
||||
/* 将 DestReceiver 的 tmpContext 更改为 PerTupleMemoryContext,以避免内存泄漏。 */
|
||||
dest->tmpContext = GetPerTupleMemoryContext(estate);
|
||||
|
||||
// planstate->plan will be release if rollback excuted
|
||||
// 如果执行回滚,planstate->plan 将被释放
|
||||
bool is_saved_recursive_union_plan_nodeid = EXEC_IN_RECURSIVE_MODE(planstate->plan);
|
||||
/*
|
||||
* Loop until we've processed the proper number of tuples from the plan.
|
||||
*/
|
||||
/*
|
||||
* 循环直到我们从计划中处理了适当数量的元组。
|
||||
*/
|
||||
|
||||
for (;;) {
|
||||
/* Reset the per-output-tuple exprcontext */
|
||||
/* 重置每个输出元组的表达式上下文 */
|
||||
|
||||
ResetPerTupleExprContext(estate);
|
||||
|
||||
/*
|
||||
* Execute the plan and obtain a tuple
|
||||
*/
|
||||
/*
|
||||
* 执行计划并获取一个元组
|
||||
*/
|
||||
|
||||
#ifdef ENABLE_MOT
|
||||
if (unlikely(recursive_early_stop)) {
|
||||
slot = NULL;
|
||||
|
|
@ -2188,13 +2196,13 @@ static void ExecutePlan(EState *estate, PlanState *planstate, CmdType operation,
|
|||
// MOT LLVM
|
||||
int scanEnded = 0;
|
||||
if (!motFinishedExecution) {
|
||||
// previous iteration has not signaled end of scan
|
||||
// 前一次迭代尚未标志着扫描结束
|
||||
slot = planstate->ps_ResultTupleSlot;
|
||||
uint64_t tuplesProcessed = 0;
|
||||
int rc = JitExec::JitExecQuery(
|
||||
motJitContext, estate->es_param_list_info, slot, &tuplesProcessed, &scanEnded);
|
||||
if (scanEnded || (tuplesProcessed == 0) || (rc != 0)) {
|
||||
// raise flag so that next round we will bail out (current tuple still must be reported to user)
|
||||
// 设置标志,以便在下一轮迭代中退出(当前元组仍然必须向用户报告)
|
||||
motFinishedExecution = true;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -2207,24 +2215,25 @@ static void ExecutePlan(EState *estate, PlanState *planstate, CmdType operation,
|
|||
slot = unlikely(recursive_early_stop) ? NULL : ExecProcNode(planstate);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* ------------------------------------------------------------------------------
|
||||
* (G)Distributed With-Recursive Support
|
||||
*
|
||||
* If under recursive cte, we need check sync step and do rescan properly
|
||||
*/
|
||||
/*
|
||||
* ------------------------------------------------------------------------------
|
||||
* (G)分布式带递归支持
|
||||
*
|
||||
* 如果在递归CTE下,我们需要检查同步步骤并进行适当的重新扫描。
|
||||
*/
|
||||
|
||||
if (unlikely(need_sync_step) && TupIsNull(slot)) {
|
||||
if (!ExecutePlanSyncProducer(planstate, WITH_RECURSIVE_SYNC_RQSTEP, &recursive_early_stop,
|
||||
¤t_tuple_count)) {
|
||||
/* current iteration step is not finish, continue to the next iteration */
|
||||
/* 当前迭代步骤尚未完成,继续到下一次迭代 */
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* if the tuple is null, then we assume there is nothing more to
|
||||
* process so we just end the loop...
|
||||
/*
|
||||
* 如果元组为null,那么我们假定没有更多要处理的内容,所以我们结束循环...
|
||||
*/
|
||||
|
||||
if (TupIsNull(slot)) {
|
||||
if(!is_saved_recursive_union_plan_nodeid) {
|
||||
break;
|
||||
|
|
@ -2233,24 +2242,25 @@ static void ExecutePlan(EState *estate, PlanState *planstate, CmdType operation,
|
|||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we have a junk filter, then project a new tuple with the junk
|
||||
* removed.
|
||||
/*
|
||||
* 如果有垃圾过滤器,则生成一个去除垃圾的新元组。
|
||||
*
|
||||
* Store this new "clean" tuple in the junkfilter's resultSlot.
|
||||
* (Formerly, we stored it back over the "dirty" tuple, which is WRONG
|
||||
* because that tuple slot has the wrong descriptor.)
|
||||
* 将这个新的“干净”元组存储在垃圾过滤器的 resultSlot 中。
|
||||
* (以前,我们将其存储回“脏”元组,这是错误的,因为该元组槽具有错误的描述符。)
|
||||
*/
|
||||
|
||||
#ifdef ENABLE_MULTIPLE_NDOES
|
||||
if (estate->es_junkFilter != NULL && !StreamTopConsumerAmI() && !StreamThreadAmI()) {
|
||||
#else
|
||||
if (estate->es_junkFilter != NULL && !StreamThreadAmI()) {
|
||||
#endif
|
||||
/* If junkfilter->jf_resultSlot->tts_tupleDescriptor is different from slot->tts_tupleDescriptor,
|
||||
* and the datatype is not Compatible,
|
||||
* we reset junkfilter->jf_resultSlot->tts_tupleDescriptor by slot->tts_tupleDescriptor.
|
||||
* This just do only once.
|
||||
*/
|
||||
/*
|
||||
* 如果 junkfilter->jf_resultSlot->tts_tupleDescriptor 与 slot->tts_tupleDescriptor 不同,
|
||||
* 且数据类型不兼容,
|
||||
* 我们将 junkfilter->jf_resultSlot->tts_tupleDescriptor 重置为 slot->tts_tupleDescriptor。
|
||||
* 这只会执行一次。
|
||||
*/
|
||||
|
||||
if (current_tuple_count == 0) {
|
||||
ExecSetjunkFilteDescriptor(estate->es_junkFilter, slot->tts_tupleDescriptor);
|
||||
}
|
||||
|
|
@ -2262,10 +2272,10 @@ static void ExecutePlan(EState *estate, PlanState *planstate, CmdType operation,
|
|||
t_thrd.pgxc_cxt.GlobalNetInstr = planstate->instrument;
|
||||
}
|
||||
#endif
|
||||
/*
|
||||
* If we are supposed to send the tuple somewhere, do so. (In
|
||||
* practice, this is probably always the case at this point.)
|
||||
*/
|
||||
/*
|
||||
* 如果我们应该将元组发送到某个地方,就这样做。(实际上,在这一点上,这很可能总是发生的情况。)
|
||||
*/
|
||||
|
||||
#ifdef ENABLE_MULTIPLE_NDOES
|
||||
if (sendTuples && !u_sess->exec_cxt.executorStopFlag)
|
||||
#else
|
||||
|
|
@ -2278,29 +2288,28 @@ static void ExecutePlan(EState *estate, PlanState *planstate, CmdType operation,
|
|||
#ifdef ENABLE_MULTIPLE_NDOES
|
||||
t_thrd.pgxc_cxt.GlobalNetInstr = NULL;
|
||||
#endif
|
||||
/*
|
||||
* Count tuples processed, if this is a SELECT. (For other operation
|
||||
* types, the ModifyTable plan node must count the appropriate
|
||||
* events.)
|
||||
/*
|
||||
* 如果这是一个SELECT语句,计算已处理的元组数。(对于其他操作类型,ModifyTable计划节点必须计算相应的事件。)
|
||||
*/
|
||||
|
||||
if (operation == CMD_SELECT) {
|
||||
(estate->es_processed)++;
|
||||
}
|
||||
|
||||
/*
|
||||
* check our tuple count.. if we've processed the proper number then
|
||||
* quit, else loop again and process more tuples. Zero numberTuples
|
||||
* means no limit.
|
||||
*/
|
||||
/*
|
||||
* 检查我们的元组计数..如果我们已经处理了正确数量的元组则退出,否则再次循环并处理更多元组。numberTuples为零表示没有限制。
|
||||
*/
|
||||
|
||||
current_tuple_count++;
|
||||
if (numberTuples == current_tuple_count) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* if current plan is working for expression, no need to collect instrumentation.
|
||||
*/
|
||||
/*
|
||||
* 如果当前计划正在为表达式工作,则无需收集仪器数据。
|
||||
*/
|
||||
|
||||
if (estate->es_instrument != INSTRUMENT_NONE && u_sess->instr_cxt.global_instr && StreamTopConsumerAmI() &&
|
||||
u_sess->instr_cxt.thread_instr) {
|
||||
int64 peak_memory = (uint64)(t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->peakChunksQuery -
|
||||
|
|
@ -2313,15 +2322,14 @@ static void ExecutePlan(EState *estate, PlanState *planstate, CmdType operation,
|
|||
/* ----------------------------------------------------------------
|
||||
* ExecutePlan
|
||||
*
|
||||
* Processes the query plan until we have retrieved 'numberTuples' tuples,
|
||||
* moving in the specified direction.
|
||||
* 处理查询计划,直到我们检索到 'numberTuples' 个元组,按照指定的方向移动。
|
||||
*
|
||||
* Runs to completion if numberTuples is 0
|
||||
* 如果 numberTuples 为 0,则运行到完成。
|
||||
*
|
||||
* Note: the ctid attribute is a 'junk' attribute that is removed before the
|
||||
* user can see it
|
||||
* 注意:ctid 属性是一个在用户可见之前被移除的 'junk' 属性。
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
|
||||
static void ExecuteVectorizedPlan(EState *estate, PlanState *planstate, CmdType operation, bool sendTuples,
|
||||
long numberTuples, ScanDirection direction, DestReceiver *dest)
|
||||
{
|
||||
|
|
@ -2329,57 +2337,61 @@ static void ExecuteVectorizedPlan(EState *estate, PlanState *planstate, CmdType
|
|||
long current_tuple_count;
|
||||
bool stream_instrument = false;
|
||||
|
||||
/*
|
||||
* initialize local variables
|
||||
*/
|
||||
/*
|
||||
* 初始化局部变量
|
||||
*/
|
||||
|
||||
current_tuple_count = 0;
|
||||
|
||||
/*
|
||||
* Set the direction.
|
||||
*/
|
||||
/*
|
||||
* 设置方向。
|
||||
*/
|
||||
|
||||
estate->es_direction = direction;
|
||||
|
||||
if (IS_PGXC_DATANODE) {
|
||||
/* Collect Executor run time including sending data time */
|
||||
/* 收集执行器运行时间,包括发送数据所用时间 */
|
||||
|
||||
if (estate->es_instrument != INSTRUMENT_NONE && u_sess->instr_cxt.global_instr) {
|
||||
stream_instrument = true;
|
||||
int plan_id = planstate->plan->plan_node_id;
|
||||
u_sess->instr_cxt.global_instr->SetStreamSend(plan_id, true);
|
||||
}
|
||||
|
||||
/* Collect Material for Subplan first */
|
||||
/* 首先收集子查询计划的物化数据 */
|
||||
ExecCollectMaterialForSubplan(estate);
|
||||
}
|
||||
|
||||
/*
|
||||
* Loop until we've processed the proper number of tuples from the plan.
|
||||
*/
|
||||
/*
|
||||
* 循环直到我们从计划中处理了适当数量的元组。
|
||||
*/
|
||||
|
||||
for (;;) {
|
||||
/* Reset the per-output-tuple exprcontext */
|
||||
/* 重置每个输出元组的表达式上下文 */
|
||||
ResetPerTupleExprContext(estate);
|
||||
|
||||
/*
|
||||
* Execute the plan and obtain a tuple
|
||||
*/
|
||||
/*
|
||||
* 执行计划并获取一个元组
|
||||
*/
|
||||
|
||||
batch = VectorEngine(planstate);
|
||||
|
||||
/*
|
||||
* if the tuple is null, then we assume there is nothing more to
|
||||
* process so we just end the loop...
|
||||
*/
|
||||
/*
|
||||
* 如果元组为null,则我们假设没有更多要处理的内容,因此我们就结束循环...
|
||||
*/
|
||||
|
||||
if (BatchIsNull(batch)) {
|
||||
ExecEarlyFree(planstate);
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we have a junk filter, then project a new tuple with the junk
|
||||
* removed.
|
||||
/*
|
||||
* 如果我们有一个垃圾过滤器,那么投影一个新的元组,去除垃圾部分。
|
||||
*
|
||||
* Store this new "clean" tuple in the junkfilter's resultSlot.
|
||||
* (Formerly, we stored it back over the "dirty" tuple, which is WRONG
|
||||
* because that tuple slot has the wrong descriptor.)
|
||||
* 将这个新的“干净”元组存储在垃圾过滤器的 resultSlot 中。
|
||||
* (以前,我们将其存储回“脏”元组,这是错误的,因为该元组槽具有错误的描述符。)
|
||||
*/
|
||||
|
||||
#ifdef ENABLE_MULTIPLE_NDOES
|
||||
if (estate->es_junkFilter != NULL && !StreamTopConsumerAmI() && !StreamThreadAmI()) {
|
||||
#else
|
||||
|
|
@ -2393,39 +2405,38 @@ static void ExecuteVectorizedPlan(EState *estate, PlanState *planstate, CmdType
|
|||
t_thrd.pgxc_cxt.GlobalNetInstr = planstate->instrument;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we are supposed to send the tuple somewhere, do so. (In
|
||||
* practice, this is probably always the case at this point.)
|
||||
*/
|
||||
/*
|
||||
* 如果我们应该将元组发送到某个地方,就这样做。(实际上,在这一点上,这很可能总是发生的情况。)
|
||||
*/
|
||||
|
||||
if (sendTuples && !u_sess->exec_cxt.executorStopFlag) {
|
||||
(*dest->sendBatch)(batch, dest);
|
||||
}
|
||||
|
||||
t_thrd.pgxc_cxt.GlobalNetInstr = NULL;
|
||||
|
||||
/*
|
||||
* Count tuples processed, if this is a SELECT. (For other operation
|
||||
* types, the ModifyTable plan node must count the appropriate
|
||||
* events.)
|
||||
*/
|
||||
/*
|
||||
* 如果这是一个SELECT语句,计算已处理的元组数。(对于其他操作类型,ModifyTable计划节点必须计算相应的事件。)
|
||||
*/
|
||||
|
||||
if (operation == CMD_SELECT) {
|
||||
estate->es_processed += batch->m_rows;
|
||||
}
|
||||
|
||||
/*
|
||||
* check our tuple count.. if we've processed the proper number then
|
||||
* quit, else loop again and process more tuples. Zero numberTuples
|
||||
* means no limit.
|
||||
*/
|
||||
/*
|
||||
* 检查我们的元组计数..如果我们已经处理了正确数量的元组则退出,否则再次循环并处理更多元组。numberTuples为零表示没有限制。
|
||||
*/
|
||||
|
||||
current_tuple_count += batch->m_rows;
|
||||
if (numberTuples && numberTuples == current_tuple_count) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* if current plan is working for expression, no need to collect instrumentation.
|
||||
*/
|
||||
/*
|
||||
* 如果当前计划正在为表达式工作,则无需收集仪器数据。
|
||||
*/
|
||||
|
||||
if (estate->es_instrument != INSTRUMENT_NONE && u_sess->instr_cxt.global_instr && StreamTopConsumerAmI()) {
|
||||
int64 peak_memory = (uint64)(t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->peakChunksQuery -
|
||||
t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->initMemInChunks)
|
||||
|
|
@ -2435,8 +2446,9 @@ static void ExecuteVectorizedPlan(EState *estate, PlanState *planstate, CmdType
|
|||
}
|
||||
|
||||
/*
|
||||
* ExecRelCheck --- check that tuple meets constraints for result relation
|
||||
* ExecRelCheck --- 检查元组是否符合结果关系的约束条件
|
||||
*/
|
||||
|
||||
static const char *ExecRelCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate)
|
||||
{
|
||||
Relation rel = resultRelInfo->ri_RelationDesc;
|
||||
|
|
@ -2447,40 +2459,41 @@ static const char *ExecRelCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *sl
|
|||
List *qual = NIL;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* If first time through for this result relation, build expression
|
||||
* nodetrees for rel's constraint expressions. Keep them in the per-query
|
||||
* memory context so they'll survive throughout the query.
|
||||
*/
|
||||
/*
|
||||
* 如果这是处理该结果关系的第一次,构建关系约束表达式的表达式树。
|
||||
* 将它们保存在每个查询的内存上下文中,以便它们在整个查询过程中保持有效。
|
||||
*/
|
||||
|
||||
if (resultRelInfo->ri_ConstraintExprs == NULL) {
|
||||
oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
|
||||
resultRelInfo->ri_ConstraintExprs = (List **)palloc(ncheck * sizeof(List *));
|
||||
for (i = 0; i < ncheck; i++) {
|
||||
/* ExecQual wants implicit-AND form */
|
||||
/* ExecQual 需要隐式的 AND 形式 */
|
||||
qual = make_ands_implicit((Expr *)stringToNode(check[i].ccbin));
|
||||
resultRelInfo->ri_ConstraintExprs[i] = (List *)ExecPrepareExpr((Expr *)qual, estate);
|
||||
}
|
||||
(void)MemoryContextSwitchTo(oldContext);
|
||||
}
|
||||
|
||||
/*
|
||||
* We will use the EState's per-tuple context for evaluating constraint
|
||||
* expressions (creating it if it's not already there).
|
||||
*/
|
||||
/*
|
||||
* 我们将使用 EState 的每个元组上下文来评估约束表达式(如果尚不存在,则创建它)。
|
||||
*/
|
||||
|
||||
econtext = GetPerTupleExprContext(estate);
|
||||
|
||||
/* Arrange for econtext's scan tuple to be the tuple under test */
|
||||
/* 确保 econtext 的扫描元组是待测试的元组 */
|
||||
|
||||
econtext->ecxt_scantuple = slot;
|
||||
|
||||
/* And evaluate the constraints */
|
||||
/* 然后评估约束条件 */
|
||||
|
||||
for (i = 0; i < ncheck; i++) {
|
||||
qual = resultRelInfo->ri_ConstraintExprs[i];
|
||||
|
||||
/*
|
||||
* NOTE: SQL92 specifies that a NULL result from a constraint
|
||||
* expression is not to be treated as a failure. Therefore, tell
|
||||
* ExecQual to return TRUE for NULL.
|
||||
*/
|
||||
/*
|
||||
* 注意:SQL92规定,约束表达式的NULL结果不应视为失败。因此,告诉ExecQual对于NULL返回TRUE。
|
||||
*/
|
||||
|
||||
if (!ExecQual(qual, econtext, true)) {
|
||||
return check[i].ccname;
|
||||
}
|
||||
|
|
@ -2492,6 +2505,7 @@ static const char *ExecRelCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *sl
|
|||
|
||||
void ExecConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate)
|
||||
{
|
||||
// 获取关系描述符和约束信息
|
||||
Relation rel = resultRelInfo->ri_RelationDesc;
|
||||
TupleDesc tupdesc = RelationGetDescr(rel);
|
||||
TupleConstr *constr = tupdesc->constr;
|
||||
|
|
@ -2500,28 +2514,26 @@ void ExecConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState
|
|||
Bitmapset *updatedCols = NULL;
|
||||
int maxfieldlen = 64;
|
||||
|
||||
// 断言确保约束信息存在
|
||||
Assert(constr);
|
||||
|
||||
/* Get the Table Accessor Method*/
|
||||
Assert(slot != NULL && slot->tts_tupleDescriptor != NULL);
|
||||
// 检查是否有 NOT NULL 约束,并处理
|
||||
if (constr->has_not_null) {
|
||||
int natts = tupdesc->natts;
|
||||
int attrChk;
|
||||
|
||||
for (attrChk = 1; attrChk <= natts; attrChk++) {
|
||||
// 如果属性为 NOT NULL 且当前元组中的值为空,则报错
|
||||
if (tupdesc->attrs[attrChk - 1]->attnotnull && tableam_tslot_attisnull(slot, attrChk)) {
|
||||
char *val_desc = NULL;
|
||||
bool rel_masked = u_sess->attr.attr_security.Enable_Security_Policy &&
|
||||
is_masked_relation_enabled(RelationGetRelid(rel));
|
||||
|
||||
// 获取插入和更新的列集合,并构建错误信息
|
||||
insertedCols = GetInsertedColumns(resultRelInfo, estate);
|
||||
updatedCols = GetUpdatedColumns(resultRelInfo, estate);
|
||||
modifiedCols = bms_union(insertedCols, updatedCols);
|
||||
if (!rel_masked) {
|
||||
val_desc =
|
||||
ExecBuildSlotValueDescription(RelationGetRelid(rel), slot, tupdesc, modifiedCols, maxfieldlen);
|
||||
}
|
||||
|
||||
// 构建包含错误列值的错误描述
|
||||
char *val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel), slot, tupdesc, modifiedCols, maxfieldlen);
|
||||
|
||||
// 报告 NOT NULL 约束违反错误
|
||||
ereport(ERROR, (errcode(ERRCODE_NOT_NULL_VIOLATION),
|
||||
errmsg("null value in column \"%s\" violates not-null constraint",
|
||||
NameStr(tupdesc->attrs[attrChk - 1]->attname)),
|
||||
|
|
@ -2530,25 +2542,24 @@ void ExecConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState
|
|||
}
|
||||
}
|
||||
|
||||
// 检查其他约束条件,并处理
|
||||
if (constr->num_check == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查 CHECK 约束条件,并返回失败的 CHECK 约束
|
||||
const char *failed = ExecRelCheck(resultRelInfo, slot, estate);
|
||||
if (failed == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
char *val_desc = NULL;
|
||||
bool rel_masked = u_sess->attr.attr_security.Enable_Security_Policy &&
|
||||
is_masked_relation_enabled(RelationGetRelid(rel));
|
||||
// 获取插入和更新的列集合,并构建错误信息
|
||||
insertedCols = GetInsertedColumns(resultRelInfo, estate);
|
||||
updatedCols = GetUpdatedColumns(resultRelInfo, estate);
|
||||
modifiedCols = bms_union(insertedCols, updatedCols);
|
||||
if (!rel_masked) {
|
||||
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel), slot, tupdesc, modifiedCols, maxfieldlen);
|
||||
}
|
||||
/* client_min_messages < NOTICE show error details. */
|
||||
char *val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel), slot, tupdesc, modifiedCols, maxfieldlen);
|
||||
|
||||
// 根据 client_min_messages 设置,报告 CHECK 约束违反错误
|
||||
if (client_min_messages < NOTICE) {
|
||||
ereport(ERROR,
|
||||
(errmodule(MOD_EXECUTOR), errcode(ERRCODE_CHECK_VIOLATION),
|
||||
|
|
@ -2569,24 +2580,18 @@ void ExecConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState
|
|||
}
|
||||
|
||||
/*
|
||||
* ExecBuildSlotValueDescription -- construct a string representing a tuple
|
||||
* ExecBuildSlotValueDescription -- 构造表示元组的字符串
|
||||
*
|
||||
* This is intentionally very similar to BuildIndexValueDescription, but
|
||||
* unlike that function, we truncate long field values (to at most maxfieldlen
|
||||
* bytes). That seems necessary here since heap field values could be very
|
||||
* long, whereas index entries typically aren't so wide.
|
||||
* 这个函数故意与 BuildIndexValueDescription 非常相似,但是与该函数不同,我们截断长字段值(至多maxfieldlen字节)。
|
||||
* 这似乎是必要的,因为堆字段的值可能非常长,而索引条目通常不那么宽。
|
||||
*
|
||||
* Also, unlike the case with index entries, we need to be prepared to ignore
|
||||
* dropped columns. We used to use the slot's tuple descriptor to decode the
|
||||
* data, but the slot's descriptor doesn't identify dropped columns, so we
|
||||
* now need to be passed the relation's descriptor.
|
||||
* 另外,与索引条目的情况不同,我们需要准备忽略被删除的列。我们过去使用槽的元组描述符来解码数据,
|
||||
* 但是槽的描述符不识别被删除的列,所以我们现在需要传递关系的描述符。
|
||||
*
|
||||
* Note that, like BuildIndexValueDescription, if the user does not have
|
||||
* permission to view any of the columns involved, a NULL is returned. Unlike
|
||||
* BuildIndexValueDescription, if the user has access to view a subset of the
|
||||
* column involved, that subset will be returned with a key identifying which
|
||||
* columns they are.
|
||||
* 请注意,与BuildIndexValueDescription类似,如果用户没有权限查看涉及的任何列,则返回NULL。
|
||||
* 与BuildIndexValueDescription不同的是,如果用户有权查看涉及的列的子集,那么将返回一个带有标识哪些列的键的子集。
|
||||
*/
|
||||
|
||||
char *ExecBuildSlotValueDescription(Oid reloid, TupleTableSlot *slot, TupleDesc tupdesc, Bitmapset *modifiedCols,
|
||||
int maxfieldlen)
|
||||
{
|
||||
|
|
@ -2604,25 +2609,25 @@ char *ExecBuildSlotValueDescription(Oid reloid, TupleTableSlot *slot, TupleDesc
|
|||
|
||||
appendStringInfoChar(&buf, '(');
|
||||
|
||||
/*
|
||||
* Check if the user has permissions to see the row. Table-level SELECT
|
||||
* allows access to all columns. If the user does not have table-level
|
||||
* SELECT then we check each column and include those the user has SELECT
|
||||
* rights on. Additionally, we always include columns the user provided
|
||||
* data for.
|
||||
*/
|
||||
/*
|
||||
* 检查用户是否有权限查看行。表级别的SELECT允许访问所有列。如果用户没有表级别的SELECT权限,
|
||||
* 则我们检查每个列,并包括用户具有SELECT权限的列。此外,我们总是包括用户提供数据的列。
|
||||
*/
|
||||
|
||||
aclresult = pg_class_aclcheck(reloid, GetUserId(), ACL_SELECT);
|
||||
if (aclresult != ACLCHECK_OK) {
|
||||
/* Set up the buffer for the column list */
|
||||
/* 为列列表设置缓冲区 */
|
||||
initStringInfo(&collist);
|
||||
appendStringInfoChar(&collist, '(');
|
||||
} else {
|
||||
table_perm = any_perm = true;
|
||||
}
|
||||
|
||||
/* Make sure the tuple is fully deconstructed */
|
||||
/* 确保元组已完全解构 */
|
||||
|
||||
|
||||
/* 获取表的访问器方法 */
|
||||
|
||||
/* Get the Table Accessor Method*/
|
||||
Assert(slot != NULL && slot->tts_tupleDescriptor != NULL);
|
||||
tableam_tslot_getallattrs(slot);
|
||||
|
||||
|
|
@ -2631,18 +2636,18 @@ char *ExecBuildSlotValueDescription(Oid reloid, TupleTableSlot *slot, TupleDesc
|
|||
char *val = NULL;
|
||||
int vallen;
|
||||
|
||||
/* ignore dropped columns */
|
||||
/* 忽略被删除的列 */
|
||||
|
||||
if (tupdesc->attrs[i]->attisdropped) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!table_perm) {
|
||||
/*
|
||||
* No table-level SELECT, so need to make sure they either have
|
||||
* SELECT rights on the column or that they have provided the
|
||||
* data for the column. If not, omit this column from the error
|
||||
* message.
|
||||
*/
|
||||
/*
|
||||
* 没有表级别的SELECT权限,因此需要确保用户要么具有列的SELECT权限,要么提供了该列的数据。
|
||||
* 如果不满足条件,就从错误消息中省略该列。
|
||||
*/
|
||||
|
||||
aclresult = pg_attribute_aclcheck(reloid, tupdesc->attrs[i]->attnum, GetUserId(), ACL_SELECT);
|
||||
if (bms_is_member(tupdesc->attrs[i]->attnum - FirstLowInvalidHeapAttributeNumber, modifiedCols) ||
|
||||
aclresult == ACLCHECK_OK) {
|
||||
|
|
@ -2675,7 +2680,8 @@ char *ExecBuildSlotValueDescription(Oid reloid, TupleTableSlot *slot, TupleDesc
|
|||
write_comma = true;
|
||||
}
|
||||
|
||||
/* truncate if needed */
|
||||
/* 如果需要,进行截断 */
|
||||
|
||||
vallen = strlen(val);
|
||||
if (vallen <= maxfieldlen) {
|
||||
appendStringInfoString(&buf, val);
|
||||
|
|
@ -2687,7 +2693,8 @@ char *ExecBuildSlotValueDescription(Oid reloid, TupleTableSlot *slot, TupleDesc
|
|||
}
|
||||
}
|
||||
|
||||
/* If we end up with zero columns being returned, then return NULL. */
|
||||
/* 如果最终没有返回任何列,则返回NULL。 */
|
||||
|
||||
if (!any_perm) {
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -2705,8 +2712,9 @@ char *ExecBuildSlotValueDescription(Oid reloid, TupleTableSlot *slot, TupleDesc
|
|||
}
|
||||
|
||||
/*
|
||||
* ExecFindRowMark -- find the ExecRowMark struct for given rangetable index
|
||||
* ExecFindRowMark -- 查找给定范围表索引的ExecRowMark结构
|
||||
*/
|
||||
|
||||
ExecRowMark *ExecFindRowMark(EState *estate, Index rti)
|
||||
{
|
||||
ListCell *lc = NULL;
|
||||
|
|
@ -2724,12 +2732,12 @@ ExecRowMark *ExecFindRowMark(EState *estate, Index rti)
|
|||
}
|
||||
|
||||
/*
|
||||
* ExecBuildAuxRowMark -- create an ExecAuxRowMark struct
|
||||
* ExecBuildAuxRowMark -- 创建一个ExecAuxRowMark结构
|
||||
*
|
||||
* Inputs are the underlying ExecRowMark struct and the targetlist of the
|
||||
* input plan node (not planstate node!). We need the latter to find out
|
||||
* the column numbers of the resjunk columns.
|
||||
* 输入参数是底层的ExecRowMark结构和输入计划节点的目标列表(不是计划状态节点!)。
|
||||
* 我们需要后者来找出结果冗余列的列号。
|
||||
*/
|
||||
|
||||
ExecAuxRowMark *ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist)
|
||||
{
|
||||
ExecAuxRowMark *aerm = (ExecAuxRowMark *)palloc0(sizeof(ExecAuxRowMark));
|
||||
|
|
@ -2738,11 +2746,13 @@ ExecAuxRowMark *ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist)
|
|||
|
||||
aerm->rowmark = erm;
|
||||
|
||||
/* Look up the resjunk columns associated with this rowmark */
|
||||
/* 查找与此行标记关联的结果冗余列 */
|
||||
|
||||
if (erm->relation) {
|
||||
Assert(erm->markType != ROW_MARK_COPY);
|
||||
|
||||
/* if child rel, need tableoid */
|
||||
/* 如果是子关系,需要表OID */
|
||||
|
||||
if (erm->rti != erm->prti || RelationIsPartitioned(erm->relation)) {
|
||||
rc = snprintf_s(resname, sizeof(resname), sizeof(resname) - 1, "tableoid%u", erm->rowmarkId);
|
||||
securec_check_ss(rc, "\0", "\0");
|
||||
|
|
@ -2763,7 +2773,8 @@ ExecAuxRowMark *ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist)
|
|||
errmsg("could not find bucketid junk %s column when build RowMark", resname)));
|
||||
}
|
||||
}
|
||||
/* always need ctid for real relations */
|
||||
/* 对于真实关系,始终需要ctid */
|
||||
|
||||
rc = snprintf_s(resname, sizeof(resname), sizeof(resname) - 1, "ctid%u", erm->rowmarkId);
|
||||
securec_check_ss(rc, "\0", "\0");
|
||||
|
||||
|
|
@ -2788,93 +2799,110 @@ ExecAuxRowMark *ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist)
|
|||
return aerm;
|
||||
}
|
||||
|
||||
/*
|
||||
* EvalPlanQualUHeap - 在行级触发器上执行计划中的限制条件
|
||||
*
|
||||
* estate:EState结构,保存了执行环境的状态信息
|
||||
* epqstate:EPQState结构,保存了过程化计划限制条件的状态信息
|
||||
* relation:当前操作的关系的描述符
|
||||
* rti:范围表索引,标识当前关系在查询计划中的位置
|
||||
* tid:需要处理的tuple的ItemPointer
|
||||
* priorXmax:上一个事务的XID
|
||||
*
|
||||
* 返回值:一个TupleTableSlot,包含了符合限制条件的tuple的数据
|
||||
*/
|
||||
TupleTableSlot *EvalPlanQualUHeap(EState *estate, EPQState *epqstate, Relation relation, Index rti, ItemPointer tid,
|
||||
TransactionId priorXmax)
|
||||
{
|
||||
TupleTableSlot *slot = NULL;
|
||||
UHeapTuple copyTuple = NULL;
|
||||
TupleTableSlot *slot = NULL;
|
||||
UHeapTuple copyTuple = NULL;
|
||||
|
||||
// 断言确保范围表索引大于0
|
||||
Assert(rti > 0);
|
||||
|
||||
copyTuple =
|
||||
UHeapLockUpdated(estate->es_output_cid, relation, LockTupleExclusive, tid, priorXmax, estate->es_snapshot);
|
||||
// 获取锁并获取更新后的tuple
|
||||
copyTuple = UHeapLockUpdated(estate->es_output_cid, relation, LockTupleExclusive, tid, priorXmax, estate->es_snapshot);
|
||||
|
||||
// 如果找不到tuple,返回NULL
|
||||
if (copyTuple == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 断言确保获取的tuple为UHeap类型
|
||||
Assert(copyTuple->tupTableType = UHEAP_TUPLE);
|
||||
|
||||
// 更新传入的tid为获取到的tuple的ctid
|
||||
*tid = copyTuple->ctid;
|
||||
|
||||
// 开始处理过程化计划的限制条件
|
||||
EvalPlanQualBegin(epqstate, estate);
|
||||
|
||||
// 将获取到的tuple设置到过程化计划限制条件状态中
|
||||
EvalPlanQualSetTuple(epqstate, rti, copyTuple);
|
||||
|
||||
// 获取过程化计划的行标记信息
|
||||
EvalPlanQualFetchRowMarks(epqstate);
|
||||
|
||||
// 执行过程化计划限制条件,并获取符合条件的tuple的slot
|
||||
slot = EvalPlanQualNext(epqstate);
|
||||
|
||||
// materialize the slot
|
||||
// 将slot中的数据materialize,即将slot中的数据转换为可见的tuple
|
||||
if (!TupIsNull(slot)) {
|
||||
ExecGetUHeapTupleFromSlot(slot);
|
||||
}
|
||||
|
||||
// 将tuple从过程化计划限制条件状态中移除
|
||||
EvalPlanQualSetTuple(epqstate, rti, NULL);
|
||||
|
||||
return slot;
|
||||
}
|
||||
|
||||
/*
|
||||
* EvalPlanQual logic --- recheck modified tuple(s) to see if we want to
|
||||
* process the updated version under READ COMMITTED rules.
|
||||
* EvalPlanQual逻辑 —— 重新检查修改的元组,以确定是否按照READ COMMITTED规则处理更新版本。
|
||||
*
|
||||
* See gausskernel/runtime/executor/README for some info about how this works.
|
||||
* 有关此操作的详细信息,请参阅gausskernel/runtime/executor/README。
|
||||
*
|
||||
* Check a modified tuple to see if we want to process its updated version
|
||||
* under READ COMMITTED rules.
|
||||
* 检查修改过的元组,以确定是否按照READ COMMITTED规则处理其更新版本。
|
||||
*
|
||||
* estate - outer executor state data
|
||||
* epqstate - state for EvalPlanQual rechecking
|
||||
* relation - table containing tuple
|
||||
* rti - rangetable index of table containing tuple
|
||||
* lockmode - requested tuple lock mode
|
||||
* *tid - t_ctid from the outdated tuple (ie, next updated version)
|
||||
* priorXmax - t_xmax from the outdated tuple
|
||||
* estate - 外部执行器状态数据
|
||||
* epqstate - 用于EvalPlanQual重新检查的状态
|
||||
* relation - 包含元组的表
|
||||
* rti - 元组所在表的范围表索引
|
||||
* lockmode - 请求的元组锁定模式
|
||||
* *tid - 过时元组的t_ctid(即,下一个更新版本)
|
||||
* priorXmax - 过时元组的t_xmax
|
||||
*
|
||||
* *tid is also an output parameter: it's modified to hold the TID of the
|
||||
* latest version of the tuple (note this may be changed even on failure)
|
||||
* *tid也是一个输出参数:它被修改为持有元组的最新版本的TID(请注意,即使失败,它也可能被修改)
|
||||
*
|
||||
* Returns a slot containing the new candidate update/delete tuple, or
|
||||
* NULL if we determine we shouldn't process the row.
|
||||
* 返回一个包含新候选更新/删除元组的槽,如果确定不应处理该行,则返回NULL。
|
||||
*
|
||||
* Note: properly, lockmode should be declared as enum LockTupleMode,
|
||||
* but we use "int" to avoid having to include heapam.h in executor.h.
|
||||
* 注意:实际上,lockmode应该被声明为枚举类型LockTupleMode,
|
||||
* 但我们使用"int"来避免在executor.h中引入heapam.h。
|
||||
*/
|
||||
|
||||
|
||||
TupleTableSlot *EvalPlanQual(EState *estate, EPQState *epqstate, Relation relation, Index rti, int lockmode,
|
||||
ItemPointer tid, TransactionId priorXmax, bool partRowMoveUpdate)
|
||||
{
|
||||
TupleTableSlot *slot = NULL;
|
||||
Tuple copyTuple;
|
||||
|
||||
// 断言确保范围表索引大于0
|
||||
Assert(rti > 0);
|
||||
|
||||
/*
|
||||
* Get and lock the updated version of the row; if fail, return NULL.
|
||||
* 获取并锁定行的更新版本;如果失败,返回NULL。
|
||||
*/
|
||||
copyTuple = tableam_tuple_lock_updated(estate->es_output_cid, relation, lockmode, tid, priorXmax,
|
||||
estate->es_snapshot);
|
||||
|
||||
// 如果找不到tuple,返回NULL
|
||||
if (copyTuple == NULL) {
|
||||
/*
|
||||
* The tuple has been deleted or update in row movement case.
|
||||
* 在行移动更新的情况下,可能是一个删除了原始分区中的元组并将其插入新分区中的行移动更新操作,
|
||||
* 或者我们可以在将要删除或更新的元组上添加锁以避免抛出异常。
|
||||
*/
|
||||
if (partRowMoveUpdate) {
|
||||
/*
|
||||
* the may be a row movement update action which delete tuple from original
|
||||
* partition and insert tuple to new partition or we can add lock on the tuple
|
||||
* to be delete or updated to avoid throw exception.
|
||||
*/
|
||||
ereport(ERROR, (errcode(ERRCODE_TRANSACTION_ROLLBACK),
|
||||
errmsg("partition table update conflict"),
|
||||
errdetail("disable row movement of table can avoid this conflict")));
|
||||
|
|
@ -2883,47 +2911,41 @@ TupleTableSlot *EvalPlanQual(EState *estate, EPQState *epqstate, Relation relati
|
|||
}
|
||||
|
||||
/*
|
||||
* For UPDATE/DELETE we have to return tid of actual row we're executing
|
||||
* PQ for.
|
||||
* 对于UPDATE/DELETE操作,我们必须返回我们正在为其执行过程化计划限制条件的实际行的TID。
|
||||
*/
|
||||
*tid = ((HeapTuple)copyTuple)->t_self;
|
||||
|
||||
/*
|
||||
* Need to run a recheck subquery. Initialize or reinitialize EPQ state.
|
||||
* 需要运行一个重新检查的子查询。初始化或重新初始化EPQ状态。
|
||||
*/
|
||||
EvalPlanQualBegin(epqstate, estate);
|
||||
|
||||
/*
|
||||
* Free old test tuple, if any, and store new tuple where relation's scan
|
||||
* node will see it
|
||||
* 释放旧的测试元组(如果有的话),并将新元组存储在关系的扫描节点将要查看的位置
|
||||
*/
|
||||
EvalPlanQualSetTuple(epqstate, rti, copyTuple);
|
||||
|
||||
/*
|
||||
* Fetch any non-locked source rows
|
||||
* 获取任何未锁定的源行
|
||||
*/
|
||||
EvalPlanQualFetchRowMarks(epqstate);
|
||||
|
||||
/*
|
||||
* Run the EPQ query. We assume it will return at most one tuple.
|
||||
* 运行EPQ查询。我们假设它最多返回一个元组。
|
||||
*/
|
||||
slot = EvalPlanQualNext(epqstate);
|
||||
|
||||
/*
|
||||
* If we got a tuple, force the slot to materialize the tuple so that it
|
||||
* is not dependent on any local state in the EPQ query (in particular,
|
||||
* it's highly likely that the slot contains references to any pass-by-ref
|
||||
* datums that may be present in copyTuple). As with the next step, this
|
||||
* is to guard against early re-use of the EPQ query.
|
||||
* 如果获取到了一个元组,强制slot将其materialize,以便它不依赖于EPQ查询中的任何本地状态
|
||||
* (特别是,slot中高度可能包含在copyTuple中存在的传址引用数据)。与下一步骤一样,
|
||||
* 这是为了防止EPQ查询的早期重用。
|
||||
*/
|
||||
if (!TupIsNull(slot)) {
|
||||
(void)tableam_tslot_get_tuple_from_slot(relation, slot);
|
||||
}
|
||||
|
||||
/*
|
||||
* Clear out the test tuple. This is needed in case the EPQ query is
|
||||
* re-used to test a tuple for a different relation. (Not clear that can
|
||||
* really happen, but let's be safe.)
|
||||
* 清除测试元组。如果EPQ查询被重用以测试不同关系的元组,这是必要的。(不太清楚真的会发生,但是为了安全起见。)
|
||||
*/
|
||||
EvalPlanQualSetTuple(epqstate, rti, NULL);
|
||||
|
||||
|
|
@ -2931,22 +2953,20 @@ TupleTableSlot *EvalPlanQual(EState *estate, EPQState *epqstate, Relation relati
|
|||
}
|
||||
|
||||
/*
|
||||
* Fetch a copy of the newest version of an outdated tuple
|
||||
* 获取过时元组的最新版本的副本
|
||||
*
|
||||
* cid - command ID
|
||||
* relation - table containing tuple
|
||||
* lockmode - requested tuple lock mode
|
||||
* *tid - t_ctid from the outdated tuple (ie, next updated version)
|
||||
* priorXmax - t_xmax from the outdated tuple
|
||||
* cid - 命令ID
|
||||
* relation - 包含元组的表
|
||||
* lockmode - 请求的元组锁定模式
|
||||
* *tid - 过时元组的t_ctid(即,下一个更新版本)
|
||||
* priorXmax - 过时元组的t_xmax
|
||||
*
|
||||
* Returns a palloc'd copy of the newest tuple version, or NULL if we find
|
||||
* that there is no newest version (ie, the row was deleted not updated).
|
||||
* If successful, we have locked the newest tuple version, so caller does not
|
||||
* need to worry about it changing anymore.
|
||||
* 返回值:最新版本元组的palloc'd副本,如果发现没有最新版本(即,该行被删除而不是更新),则返回NULL。
|
||||
* 如果成功,我们已经锁定了最新版本的元组,所以调用者无需再担心它会再次改变。
|
||||
*
|
||||
* Note: properly, lockmode should be declared as enum LockTupleMode,
|
||||
* but we use "int" to avoid having to include heapam.h in executor.h.
|
||||
* 注意:正确的做法是,lockmode应该被声明为枚举类型LockTupleMode,但是我们使用"int"来避免在executor.h中引入heapam.h。
|
||||
*/
|
||||
|
||||
HeapTuple heap_lock_updated(CommandId cid, Relation relation, int lockmode, ItemPointer tid, TransactionId priorXmax)
|
||||
{
|
||||
HeapTuple copyTuple = NULL;
|
||||
|
|
|
|||
Loading…
Reference in New Issue