2357 lines
82 KiB
C++
2357 lines
82 KiB
C++
/* -------------------------------------------------------------------------
|
||
*
|
||
* execUtils.cpp
|
||
* 杂项执行器实用程序函数
|
||
*
|
||
* 版权所有 (c) 2020 华为技术有限公司
|
||
* 版权所有 (c) 1996-2012,PostgreSQL全球开发团队
|
||
* 版权所有 (c) 1994,加州大学监管机构
|
||
* 版权所有 (c) 2021,openGauss 社区贡献者
|
||
*
|
||
*
|
||
* 识别码
|
||
* src/gausskernel/runtime/executor/execUtils.cpp
|
||
*
|
||
* -------------------------------------------------------------------------
|
||
|
||
* 接口函数
|
||
* CreateExecutorState 创建/删除执行器工作状态
|
||
* FreeExecutorState
|
||
* CreateExprContext
|
||
* CreateStandaloneExprContext
|
||
* FreeExprContext
|
||
* ReScanExprContext
|
||
*
|
||
* ExecAssignExprContext 计划节点初始化例程的通用代码。
|
||
* ExecAssignResultType
|
||
* 等等
|
||
*
|
||
* ExecOpenScanRelation 扫描节点初始化例程的通用代码。
|
||
* ExecCloseScanRelation
|
||
*
|
||
* ExecOpenIndices \
|
||
* ExecCloseIndices | 被 InitPlan、EndPlan、ExecInsert、ExecUpdate 引用
|
||
* ExecInsertIndexTuples /
|
||
*
|
||
* RegisterExprContextCallback 注册函数关机回调
|
||
* UnregisterExprContextCallback 注销函数关机回调
|
||
*
|
||
* 注意
|
||
* 这个文件传统上是放置一些其他地方不太适合的杂项执行器支持代码的地方。
|
||
|
||
|
||
#include "postgres.h"
|
||
#include "knl/knl_variable.h"
|
||
|
||
#include "access/relscan.h"
|
||
#include "access/sysattr.h"
|
||
#include "access/transam.h"
|
||
#include "access/tableam.h"
|
||
#include "catalog/index.h"
|
||
#include "catalog/heap.h"
|
||
#include "catalog/namespace.h"
|
||
#include "catalog/pg_partition_fn.h"
|
||
#include "executor/exec/execdebug.h"
|
||
#include "nodes/nodeFuncs.h"
|
||
#include "parser/parsetree.h"
|
||
#include "storage/lmgr.h"
|
||
#include "storage/tcap.h"
|
||
#include "utils/memutils.h"
|
||
#include "utils/snapmgr.h"
|
||
#include "utils/partitionmap.h"
|
||
#include "utils/partitionmap_gs.h"
|
||
#include "optimizer/var.h"
|
||
#include "utils/resowner.h"
|
||
#include "miscadmin.h"
|
||
|
||
static bool get_last_attnums(Node* node, ProjectionInfo* projInfo);
|
||
static bool index_recheck_constraint(
|
||
Relation index, Oid* constr_procs, Datum* existing_values, const bool* existing_isnull, Datum* new_values);
|
||
static void ShutdownExprContext(ExprContext* econtext, bool isCommit);
|
||
static bool check_violation(Relation heap, Relation index, IndexInfo *indexInfo, ItemPointer tupleid, Datum *values,
|
||
const bool *isnull, EState *estate, bool newIndex, bool errorOK, CheckWaitMode waitMode,
|
||
ConflictInfoData *conflictInfo, Oid partoid = InvalidOid, int2 bucketid = InvalidBktId,
|
||
Oid *conflictPartOid = NULL, int2 *conflictBucketid = NULL);
|
||
|
||
/* ----------------------------------------------------------------
|
||
* 执行器状态和内存管理函数
|
||
* ----------------------------------------------------------------
|
||
|
||
|
||
|
||
/* ----------------
|
||
* CreateExecutorState
|
||
*
|
||
* 创建并初始化一个 EState 节点,它是整个执行器调用的工作存储的根节点。
|
||
*
|
||
* 主要来说,这个函数创建了一个用于存储整个查询期间持续存在的所有工作数据的每个查询内存上下文。
|
||
* 需要注意的是,每个查询上下文将成为调用者的 CurrentMemoryContext 的子上下文。
|
||
* ----------------
|
||
*/
|
||
|
||
EState* CreateExecutorState(MemoryContext saveCxt)
|
||
{
|
||
EState* estate = NULL;
|
||
MemoryContext qcontext;
|
||
MemoryContext oldcontext;
|
||
|
||
/*
|
||
* 为此 Executor 运行创建每个查询的上下文。
|
||
*/
|
||
|
||
if (saveCxt != NULL) {
|
||
qcontext = saveCxt;
|
||
} else {
|
||
qcontext = AllocSetContextCreate(CurrentMemoryContext,
|
||
"ExecutorState",
|
||
ALLOCSET_DEFAULT_MINSIZE,
|
||
ALLOCSET_DEFAULT_INITSIZE,
|
||
ALLOCSET_DEFAULT_MAXSIZE);
|
||
}
|
||
|
||
/*
|
||
* 在每个查询的上下文中创建 EState 节点。这样,我们在关闭时不需要单独的 pfree_ext() 操作。
|
||
*/
|
||
|
||
oldcontext = MemoryContextSwitchTo(qcontext);
|
||
|
||
estate = makeNode(EState);
|
||
|
||
/*
|
||
* 初始化 Executor 状态结构的所有字段
|
||
*/
|
||
estate->es_direction = ForwardScanDirection;
|
||
estate->es_snapshot = SnapshotNow;
|
||
estate->es_crosscheck_snapshot = InvalidSnapshot; /* no crosscheck */
|
||
estate->es_range_table = NIL;
|
||
estate->es_plannedstmt = NULL;
|
||
|
||
estate->es_junkFilter = NULL;
|
||
|
||
estate->es_output_cid = (CommandId)0;
|
||
|
||
estate->es_result_relations = NULL;
|
||
estate->es_num_result_relations = 0;
|
||
estate->es_result_relation_info = NULL;
|
||
#ifdef PGXC
|
||
estate->es_result_remoterel = NULL;
|
||
#endif
|
||
estate->esCurrentPartition = NULL;
|
||
estate->esfRelations = NULL;
|
||
estate->es_trig_target_relations = NIL;
|
||
estate->es_trig_tuple_slot = NULL;
|
||
estate->es_trig_oldtup_slot = NULL;
|
||
estate->es_trig_newtup_slot = NULL;
|
||
|
||
estate->es_param_list_info = NULL;
|
||
estate->es_param_exec_vals = NULL;
|
||
|
||
estate->es_query_cxt = qcontext;
|
||
estate->es_const_query_cxt = qcontext;/* 查询上下文的上下文,它不会被更改 */
|
||
|
||
|
||
estate->es_tupleTable = NIL;
|
||
estate->es_epqTupleSlot = NULL;
|
||
|
||
estate->es_rowMarks = NIL;
|
||
|
||
estate->es_modifiedRowHash = NIL;
|
||
estate->es_processed = 0;
|
||
estate->es_last_processed = 0;
|
||
estate->es_lastoid = InvalidOid;
|
||
|
||
estate->es_top_eflags = 0;
|
||
estate->es_instrument = INSTRUMENT_NONE;
|
||
estate->es_finished = false;
|
||
|
||
estate->es_exprcontexts = NIL;
|
||
|
||
estate->es_subplanstates = NIL;
|
||
|
||
estate->es_auxmodifytables = NIL;
|
||
estate->es_remotequerystates = NIL;
|
||
|
||
estate->es_per_tuple_exprcontext = NULL;
|
||
|
||
estate->es_epqTuple = NULL;
|
||
estate->es_epqTupleSet = NULL;
|
||
estate->es_epqScanDone = NULL;
|
||
|
||
estate->es_subplan_ids = NIL;
|
||
estate->es_skip_early_free = false;
|
||
estate->es_skip_early_deinit_consumer = false;
|
||
estate->es_under_subplan = false;
|
||
estate->es_material_of_subplan = NIL;
|
||
estate->es_recursive_next_iteration = false;
|
||
|
||
estate->pruningResult = NULL;
|
||
|
||
/*
|
||
* 返回执行器状态结构
|
||
*/
|
||
|
||
MemoryContextSwitchTo(oldcontext);
|
||
|
||
return estate;
|
||
}
|
||
|
||
/*
|
||
* 释放EState及其所有剩余的工作存储空间。
|
||
*
|
||
* 注意:这不负责释放非内存资源,如打开的关系或缓冲区引用。但它会关闭EState中的任何仍处于活动状态的ExprContext。
|
||
* 这足够清理仅用于表达式求值而不用于运行完整计划的情况。
|
||
*
|
||
* 这可以在任何内存上下文中调用...只要不是要释放的那些之一。
|
||
*/
|
||
|
||
void FreeExecutorState(EState* estate)
|
||
{
|
||
/*
|
||
* 关闭和释放任何剩余的ExprContexts。我们明确执行此操作以确保调用任何剩余的关闭回调(因为它们可能需要释放的资源不仅仅是在每个查询内存上下文中的内存)。
|
||
*/
|
||
|
||
while (estate->es_exprcontexts) {
|
||
/*
|
||
* XXX:似乎应该有一种比反复使用list_delete()更快的方法来实现这个操作,不是吗?
|
||
*/
|
||
|
||
FreeExprContext((ExprContext*)linitial(estate->es_exprcontexts), true);
|
||
/* FreeExprContext 为我们移除了链表的链接 */
|
||
|
||
}
|
||
|
||
/*
|
||
* 释放 per-query 内存上下文,从而释放所有的工作内存,包括 EState 结构本身。
|
||
*/
|
||
|
||
MemoryContextDelete(estate->es_query_cxt);
|
||
}
|
||
|
||
/*
|
||
* 创建一个在 EState 内部用于表达式评估的上下文。
|
||
*
|
||
* 执行程序运行可能需要多个 ExprContexts(通常我们为每个计划节点创建一个,还有一个单独的用于每个输出元组处理,例如约束检查)。
|
||
* 每个 ExprContext 都有它自己的 "每个元组" 内存上下文。
|
||
*
|
||
* 注意,我们不对调用者的内存上下文做任何假设。
|
||
*/
|
||
|
||
ExprContext* CreateExprContext(EState* estate)
|
||
{
|
||
ExprContext* econtext = NULL;
|
||
MemoryContext oldcontext;
|
||
|
||
/* 在每个查询的内存上下文中创建 ExprContext 节点 */
|
||
|
||
oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
|
||
|
||
econtext = makeNode(ExprContext);
|
||
|
||
/* 初始化 ExprContext 的字段 */
|
||
|
||
econtext->ecxt_scantuple = NULL;
|
||
econtext->ecxt_innertuple = NULL;
|
||
econtext->ecxt_outertuple = NULL;
|
||
|
||
econtext->ecxt_per_query_memory = estate->es_query_cxt;
|
||
|
||
/* 在该上下文中为表达式评估创建工作内存。 */
|
||
|
||
econtext->ecxt_per_tuple_memory = AllocSetContextCreate(estate->es_query_cxt,
|
||
"ExprContext",
|
||
ALLOCSET_DEFAULT_MINSIZE,
|
||
ALLOCSET_DEFAULT_INITSIZE,
|
||
ALLOCSET_DEFAULT_MAXSIZE);
|
||
|
||
econtext->ecxt_param_exec_vals = estate->es_param_exec_vals;
|
||
econtext->ecxt_param_list_info = estate->es_param_list_info;
|
||
|
||
econtext->ecxt_aggvalues = NULL;
|
||
econtext->ecxt_aggnulls = NULL;
|
||
|
||
econtext->caseValue_datum = (Datum)0;
|
||
econtext->caseValue_isNull = true;
|
||
|
||
econtext->domainValue_datum = (Datum)0;
|
||
econtext->domainValue_isNull = true;
|
||
|
||
econtext->ecxt_estate = estate;
|
||
|
||
econtext->ecxt_callbacks = NULL;
|
||
econtext->plpgsql_estate = NULL;
|
||
|
||
/* 将ExprContext链接到EState,以确保在释放EState时关闭它。由于我们使用lcons(),关闭将按照创建的相反顺序发生,这可能不是必需的,但不会有害。 */
|
||
estate->es_exprcontexts = lcons(econtext, estate->es_exprcontexts);
|
||
|
||
MemoryContextSwitchTo(oldcontext);
|
||
|
||
return econtext;
|
||
}
|
||
|
||
/* ----------------
|
||
* CreateStandaloneExprContext
|
||
*
|
||
* 创建一个用于独立表达式评估的上下文。
|
||
*
|
||
* 通过这种方式创建的ExprContext可用于评估不包含Params、子计划或Var引用的表达式(可能将元组引用放入scantuple字段是可行的,但似乎不明智)。
|
||
*
|
||
* ExprContext结构在调用者的当前内存上下文中分配,该内存上下文也成为其“每个查询”的上下文。
|
||
*
|
||
* 在完成后,调用者有责任释放ExprContext,或者至少确保已调用任何关闭回调函数(ReScanExprContext()是合适的)。否则,可能会泄漏非内存资源。
|
||
* ----------------
|
||
*/
|
||
|
||
ExprContext* CreateStandaloneExprContext(void)
|
||
{
|
||
ExprContext* econtext = NULL;
|
||
|
||
/* 在调用者的内存上下文中创建ExprContext节点 */
|
||
econtext = makeNode(ExprContext);
|
||
|
||
/* 初始化ExprContext的字段 */
|
||
econtext->ecxt_scantuple = NULL;
|
||
econtext->ecxt_innertuple = NULL;
|
||
econtext->ecxt_outertuple = NULL;
|
||
|
||
econtext->ecxt_per_query_memory = CurrentMemoryContext;
|
||
|
||
/* 在这个上下文中为表达式评估创建工作内存 */
|
||
econtext->ecxt_per_tuple_memory = AllocSetContextCreate(CurrentMemoryContext,
|
||
"ExprContext",
|
||
ALLOCSET_DEFAULT_MINSIZE,
|
||
ALLOCSET_DEFAULT_INITSIZE,
|
||
ALLOCSET_DEFAULT_MAXSIZE);
|
||
|
||
econtext->ecxt_param_exec_vals = NULL;
|
||
econtext->ecxt_param_list_info = NULL;
|
||
|
||
econtext->ecxt_aggvalues = NULL;
|
||
econtext->ecxt_aggnulls = NULL;
|
||
|
||
econtext->caseValue_datum = (Datum)0;
|
||
econtext->caseValue_isNull = true;
|
||
|
||
econtext->domainValue_datum = (Datum)0;
|
||
econtext->domainValue_isNull = true;
|
||
|
||
econtext->ecxt_estate = NULL;
|
||
|
||
econtext->ecxt_callbacks = NULL;
|
||
|
||
return econtext;
|
||
}
|
||
|
||
/* 释放表达式上下文,包括调用任何剩余的关闭回调函数。
|
||
|
||
由于我们释放了用于表达式评估的临时上下文,任何先前计算的传递引用表达式结果都会被清除!
|
||
|
||
如果 isCommit 为 false,则我们是在错误清理中调用,不应调用回调函数,只能释放内存。
|
||
(可能更好的方法是调用回调函数并将 isCommit 标志传递给它们,但这需要比当前看起来合理的更深入的代码更改。)
|
||
|
||
注意,我们不对调用者的内存上下文作任何假设。
|
||
*/
|
||
|
||
void FreeExprContext(ExprContext* econtext, bool isCommit)
|
||
{
|
||
EState* estate = NULL;
|
||
|
||
/* 调用所有已注册的回调函数 */
|
||
ShutdownExprContext(econtext, isCommit);
|
||
/* 然后清理使用的内存 */
|
||
MemoryContextDelete(econtext->ecxt_per_tuple_memory);
|
||
/* 如果有的话,从拥有它的 EState 中解除链接 */
|
||
estate = econtext->ecxt_estate;
|
||
if (estate != NULL)
|
||
estate->es_exprcontexts = list_delete_ptr(estate->es_exprcontexts, econtext);
|
||
/* 然后删除 ExprContext 节点 */
|
||
pfree_ext(econtext);
|
||
}
|
||
|
||
/*
|
||
* ReScanExprContext
|
||
*
|
||
* 在重新扫描计划节点之前,重置表达式上下文。这需要调用任何已注册的关闭回调,
|
||
* 因为任何部分完成的返回集函数必须被取消。
|
||
*
|
||
* 注意,我们不对调用者的内存上下文做任何假设。
|
||
*/
|
||
|
||
void ReScanExprContext(ExprContext* econtext)
|
||
{
|
||
/* 调用任何已注册的回调函数 */
|
||
ShutdownExprContext(econtext, true);
|
||
/* 清理使用的内存 */
|
||
MemoryContextReset(econtext->ecxt_per_tuple_memory);
|
||
}
|
||
|
||
/*
|
||
* 为 EState 构建一个每个输出元组的 ExprContext。
|
||
*
|
||
* 通常通过 GetPerTupleExprContext() 宏调用,而不是直接调用。
|
||
*/
|
||
|
||
ExprContext* MakePerTupleExprContext(EState* estate)
|
||
{
|
||
if (estate->es_per_tuple_exprcontext == NULL)
|
||
estate->es_per_tuple_exprcontext = CreateExprContext(estate);
|
||
|
||
return estate->es_per_tuple_exprcontext;
|
||
}
|
||
|
||
/*
|
||
* 杂项节点初始化支持函数
|
||
*
|
||
* 注意:所有这些函数都期望在当前内存上下文为每个查询的内存上下文时调用。
|
||
*/
|
||
|
||
/*
|
||
* ExecAssignExprContext
|
||
*
|
||
* 初始化 ps_ExprContext 字段。只有使用 ExecQual 或 ExecProject 的节点需要这样做,
|
||
* 因为这些例程需要一个表达式上下文(econtext)。不需要评估表达式的其他节点不需要执行此操作。
|
||
*/
|
||
|
||
void ExecAssignExprContext(EState* estate, PlanState* planstate)
|
||
{
|
||
planstate->ps_ExprContext = CreateExprContext(estate);
|
||
}
|
||
|
||
/* ----------------
|
||
* ExecAssignResultType
|
||
* ----------------
|
||
*/
|
||
void ExecAssignResultType(PlanState* planstate, TupleDesc tupDesc)
|
||
{
|
||
TupleTableSlot* slot = planstate->ps_ResultTupleSlot;
|
||
|
||
ExecSetSlotDescriptor(slot, tupDesc);
|
||
}
|
||
|
||
/* ----------------
|
||
* ExecAssignResultTypeFromTL
|
||
* ----------------
|
||
*/
|
||
void ExecAssignResultTypeFromTL(PlanState* planstate, TableAmType tam)
|
||
{
|
||
bool hasoid = false;
|
||
TupleDesc tupDesc;
|
||
|
||
if (ExecContextForcesOids(planstate, &hasoid)) {
|
||
/* context 强制 OID 选择;现在 hasoid 被正确设置 */
|
||
} else {
|
||
/* 在给定自由选择的情况下,不要在结果元组中留出 OID 的空间 */
|
||
hasoid = false;
|
||
}
|
||
|
||
/* ExecTypeFromTL 需要 tlist 的解析时表示,而不是 ExprStates 的列表。
|
||
* 这很好,因为某些计划节点不会费心设置 planstate->targetlist ...
|
||
*/
|
||
tupDesc = ExecTypeFromTL(planstate->plan->targetlist, hasoid, false, tam);
|
||
ExecAssignResultType(planstate, tupDesc);
|
||
}
|
||
|
||
/* ----------------
|
||
* ExecGetResultType
|
||
* ----------------
|
||
*/
|
||
TupleDesc ExecGetResultType(PlanState* planstate)
|
||
{
|
||
TupleTableSlot* slot = NULL;
|
||
/* 如果子节点是 PartIteratorState,则将开销传递给其子节点 */
|
||
if (IsA(planstate, PartIteratorState) || IsA(planstate, VecPartIteratorState)) {
|
||
planstate = outerPlanState(planstate);
|
||
}
|
||
slot = planstate->ps_ResultTupleSlot;
|
||
|
||
return slot->tts_tupleDescriptor;
|
||
}
|
||
|
||
void ExecAssignVectorForExprEval(ExprContext* econtext)
|
||
{
|
||
Assert(econtext != NULL);
|
||
|
||
ScalarDesc unknownDesc;
|
||
ScalarDesc boolDesc;
|
||
|
||
boolDesc.typeId = BOOLOID;
|
||
boolDesc.encoded = false;
|
||
|
||
econtext->qual_results = New(CurrentMemoryContext) ScalarVector();
|
||
econtext->qual_results->init(CurrentMemoryContext, boolDesc);
|
||
|
||
econtext->boolVector = New(CurrentMemoryContext) ScalarVector();
|
||
econtext->boolVector->init(CurrentMemoryContext, boolDesc);
|
||
|
||
econtext->caseValue_vector = New(CurrentMemoryContext) ScalarVector();
|
||
econtext->caseValue_vector->init(CurrentMemoryContext, unknownDesc);
|
||
}
|
||
|
||
/* 用于列存储的支持信息。 */
|
||
|
||
/* targetList 是从 ExprState 树中获取的,qual 是从 Expr 节点树中获取的。 */
|
||
static void GetAccessedVarNumbers(ProjectionInfo* projInfo, List* targetList, List* qual)
|
||
{
|
||
List* vars = NIL;
|
||
List* varattno_list = NIL;
|
||
List* lateAccessVarNoList = NIL;
|
||
List* sysVarList = NIL;
|
||
List* qualVarNoList = NIL;
|
||
bool isConst = false;
|
||
ListCell* l = NULL;
|
||
List* PackLateAccessList = NIL;
|
||
|
||
foreach (l, targetList) {
|
||
ListCell* vl = NULL;
|
||
GenericExprState* gstate = (GenericExprState*)lfirst(l);
|
||
TargetEntry* tle = (TargetEntry*)gstate->xprstate.expr;
|
||
|
||
/* 从目标列表中提取变量。 */
|
||
vars = pull_var_clause((Node*)tle, PVC_RECURSE_AGGREGATES, PVC_RECURSE_PLACEHOLDERS);
|
||
|
||
foreach (vl, vars) {
|
||
Var* var = (Var*)lfirst(vl);
|
||
int varattno = (int)var->varattno;
|
||
if (!list_member_int(varattno_list, varattno)) {
|
||
if (varattno >= 0) {
|
||
varattno_list = lappend_int(varattno_list, varattno);
|
||
} else {
|
||
sysVarList = lappend_int(sysVarList, varattno);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 用于 PackT 优化:PackTCopyVarsList 记录需要移动的那些列。
|
||
*/
|
||
List* PackTCopyVarsList = list_copy(varattno_list);
|
||
|
||
/* 现在考虑条件表达式(quals) */
|
||
vars = pull_var_clause((Node*)qual, PVC_RECURSE_AGGREGATES, PVC_RECURSE_PLACEHOLDERS);
|
||
foreach (l, vars) {
|
||
Var* var = (Var*)lfirst(l);
|
||
int varattno = (int)var->varattno;
|
||
|
||
if (var->varattno >= 0) {
|
||
if (!list_member_int(varattno_list, varattno))
|
||
varattno_list = lappend_int(varattno_list, varattno);
|
||
qualVarNoList = lappend_int(qualVarNoList, varattno);
|
||
} else
|
||
sysVarList = lappend_int(sysVarList, varattno);
|
||
}
|
||
|
||
if ((list_length(varattno_list) == 0) && (list_length(sysVarList) == 0)) {
|
||
isConst = true;
|
||
}
|
||
|
||
// 现在我们需要确定哪些变量可以被延迟访问。
|
||
// 换句话说,这些列可以在过滤后加载。
|
||
// 我们可以尽可能晚地读取这些列。
|
||
if (qualVarNoList != NIL) {
|
||
lateAccessVarNoList = list_difference_int(varattno_list, qualVarNoList);
|
||
list_free_ext(qualVarNoList);
|
||
}
|
||
|
||
if (PackTCopyVarsList != NIL) {
|
||
PackLateAccessList = list_difference_int(PackTCopyVarsList, lateAccessVarNoList);
|
||
}
|
||
|
||
/*
|
||
* 这里,projInfo->pi_PackTCopyVars 记录了我们想要的特定列数据。
|
||
*/
|
||
projInfo->pi_PackTCopyVars = PackTCopyVarsList;
|
||
projInfo->pi_acessedVarNumbers = varattno_list;
|
||
projInfo->pi_lateAceessVarNumbers = lateAccessVarNoList;
|
||
projInfo->pi_sysAttrList = sysVarList;
|
||
projInfo->pi_const = isConst;
|
||
projInfo->pi_PackLateAccessVarNumbers = PackLateAccessList;
|
||
}
|
||
|
||
List* GetAccessedVarnoList(List* targetList, List* qual)
|
||
{
|
||
ProjectionInfo tmp_pi;
|
||
|
||
/* 获取此查询语句的已访问的属性号(列号) */
|
||
GetAccessedVarNumbers(&tmp_pi, targetList, qual);
|
||
if (tmp_pi.pi_PackTCopyVars) {
|
||
list_free_ext(tmp_pi.pi_PackTCopyVars);
|
||
}
|
||
return list_concat(tmp_pi.pi_acessedVarNumbers, tmp_pi.pi_lateAceessVarNumbers);
|
||
}
|
||
|
||
ProjectionInfo* ExecBuildVecProjectionInfo(
|
||
List* targetList, List* nt_qual, ExprContext* econtext, TupleTableSlot* slot, TupleDesc inputDesc)
|
||
{
|
||
ProjectionInfo* projInfo = makeNode(ProjectionInfo);
|
||
int len = ExecTargetListLength(targetList);
|
||
int* workspace = NULL;
|
||
int* varSlotOffsets = NULL;
|
||
int* varNumbers = NULL;
|
||
int* varOutputCols = NULL;
|
||
List* exprlist = NIL;
|
||
int numSimpleVars;
|
||
bool directMap = false;
|
||
ListCell* tl = NULL;
|
||
|
||
// 保护零长度投影
|
||
//
|
||
if (len == 0)
|
||
return NULL;
|
||
|
||
projInfo->pi_exprContext = econtext;
|
||
projInfo->pi_slot = slot;
|
||
// 由于这些都是整数数组,我们只需要执行一次 palloc 操作
|
||
workspace = (int*)palloc(len * 3 * sizeof(int));
|
||
projInfo->pi_varSlotOffsets = varSlotOffsets = workspace;
|
||
projInfo->pi_varNumbers = varNumbers = workspace + len;
|
||
projInfo->pi_varOutputCols = varOutputCols = workspace + len * 2;
|
||
projInfo->pi_lastInnerVar = 0;
|
||
projInfo->pi_lastOuterVar = 0;
|
||
projInfo->pi_lastScanVar = 0;
|
||
/* 列存储的支持信息 */
|
||
GetAccessedVarNumbers(projInfo, targetList, nt_qual);
|
||
|
||
// 为当前的投影操作分配批处理内存。
|
||
//
|
||
projInfo->pi_batch = New(CurrentMemoryContext) VectorBatch(CurrentMemoryContext, slot->tts_tupleDescriptor);
|
||
|
||
/*
|
||
* 我们将目标列表元素分为简单的 Var 引用和需要完整 ExecTargetList 机制的表达式。
|
||
* 要成为一个简单的 Var,Var 必须是用户属性,并且不与输入描述不匹配。
|
||
* (注意:如果存在类型不匹配,那么 ExecEvalVar 在运行时可能会引发错误,但我们将其留给它处理。)
|
||
*/
|
||
exprlist = NIL;
|
||
numSimpleVars = 0;
|
||
directMap = true;
|
||
foreach (tl, targetList) {
|
||
GenericExprState* gstate = (GenericExprState*)lfirst(tl);
|
||
Var* variable = (Var*)gstate->arg->expr;
|
||
bool isSimpleVar = false;
|
||
|
||
if (variable != NULL && IsA(variable, Var) && variable->varattno > 0) {
|
||
if (!inputDesc)
|
||
isSimpleVar = true; /* 无法检查类型,假设是没问题的 */
|
||
else if (variable->varattno <= inputDesc->natts) {
|
||
Form_pg_attribute attr;
|
||
|
||
attr = inputDesc->attrs[variable->varattno - 1];
|
||
if (!attr->attisdropped && variable->vartype == attr->atttypid)
|
||
isSimpleVar = true;
|
||
}
|
||
}
|
||
|
||
if (isSimpleVar) {
|
||
TargetEntry* tle = (TargetEntry*)gstate->xprstate.expr;
|
||
AttrNumber attnum = variable->varattno;
|
||
|
||
varNumbers[numSimpleVars] = attnum;
|
||
varOutputCols[numSimpleVars] = tle->resno;
|
||
if (tle->resno != numSimpleVars + 1)
|
||
directMap = false;
|
||
|
||
switch (variable->varno) {
|
||
case INNER_VAR:
|
||
varSlotOffsets[numSimpleVars] = offsetof(ExprContext, ecxt_innerbatch);
|
||
if (projInfo->pi_lastInnerVar < attnum)
|
||
projInfo->pi_lastInnerVar = attnum;
|
||
break;
|
||
|
||
case OUTER_VAR:
|
||
varSlotOffsets[numSimpleVars] = offsetof(ExprContext, ecxt_outerbatch);
|
||
if (projInfo->pi_lastOuterVar < attnum)
|
||
projInfo->pi_lastOuterVar = attnum;
|
||
break;
|
||
|
||
default:
|
||
varSlotOffsets[numSimpleVars] = offsetof(ExprContext, ecxt_scanbatch);
|
||
if (projInfo->pi_lastScanVar < attnum)
|
||
projInfo->pi_lastScanVar = attnum;
|
||
break;
|
||
}
|
||
numSimpleVars++;
|
||
} else {
|
||
/* 不是一个简单的变量,将其添加到通用目标列表中 */
|
||
exprlist = lappend(exprlist, gstate);
|
||
/* 检查表达式以包括在 lastXXXVar 计数中包含的变量 */
|
||
get_last_attnums((Node*)variable, projInfo);
|
||
}
|
||
}
|
||
projInfo->pi_targetlist = exprlist;
|
||
projInfo->pi_numSimpleVars = numSimpleVars;
|
||
projInfo->pi_directMap = directMap;
|
||
|
||
if (projInfo->pi_exprContext != NULL) {
|
||
projInfo->pi_exprContext->vec_fun_sel = NULL;
|
||
projInfo->pi_exprContext->current_row = 0;
|
||
}
|
||
|
||
if (exprlist == NIL) {
|
||
projInfo->pi_itemIsDone = NULL; /* not needed */
|
||
} else {
|
||
projInfo->pi_itemIsDone = (ExprDoneCond*)palloc0(len * sizeof(ExprDoneCond));
|
||
|
||
if (projInfo->pi_exprContext != NULL && projInfo->pi_exprContext->have_vec_set_fun) {
|
||
projInfo->pi_setFuncBatch =
|
||
New(CurrentMemoryContext) VectorBatch(CurrentMemoryContext, slot->tts_tupleDescriptor);
|
||
projInfo->pi_exprContext->vec_fun_sel = (bool*)palloc0(BatchMaxSize * sizeof(bool));
|
||
for (int i = 0; i < BatchMaxSize; i++) {
|
||
projInfo->pi_exprContext->vec_fun_sel[i] = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
return projInfo;
|
||
}
|
||
|
||
/* 构建 ProjectionInfo 结构,用于在给定的 econtext 中计算给定的 tlist,并将结果存储到元组槽中。
|
||
* (调用者必须确保元组槽具有与 tlist 匹配的描述符!)注意,给定的 tlist 应该是 ExprState 节点的列表,而不是 Expr 节点。
|
||
|
||
* inputDesc 可以为 NULL,但如果不为 NULL,则我们会检查 tlist 中的简单变量是否与描述符匹配。
|
||
* 为了关系扫描计划节点,提供 inputDesc 是很重要的,因为它是检查关系在计划生成后是否发生了更改的交叉检查。
|
||
* 在计划的更高级别,无需重新检查。
|
||
*/
|
||
ProjectionInfo* ExecBuildProjectionInfo(
|
||
List* targetList, ExprContext* econtext, TupleTableSlot* slot, TupleDesc inputDesc)
|
||
{
|
||
ProjectionInfo* projInfo = makeNode(ProjectionInfo);
|
||
int len = ExecTargetListLength(targetList);
|
||
int* workspace = NULL;
|
||
int* varSlotOffsets = NULL;
|
||
int* varNumbers = NULL;
|
||
int* varOutputCols = NULL;
|
||
List* exprlist = NULL;
|
||
int numSimpleVars;
|
||
bool directMap = false;
|
||
ListCell* tl = NULL;
|
||
|
||
projInfo->pi_exprContext = econtext;
|
||
projInfo->pi_slot = slot;
|
||
/* 由于这些都是 int 数组,我们只需要进行一次内存分配(palloc) */
|
||
workspace = (int*)palloc(len * 3 * sizeof(int));
|
||
projInfo->pi_varSlotOffsets = varSlotOffsets = workspace;
|
||
projInfo->pi_varNumbers = varNumbers = workspace + len;
|
||
projInfo->pi_varOutputCols = varOutputCols = workspace + len * 2;
|
||
projInfo->pi_lastInnerVar = 0;
|
||
projInfo->pi_lastOuterVar = 0;
|
||
projInfo->pi_lastScanVar = 0;
|
||
|
||
/*
|
||
* 我们将目标列表元素分为简单的 Var 引用和需要完整的 ExecTargetList 机制的表达式。
|
||
* 要成为简单的 Var,Var 必须是用户属性并且不与 inputDesc 不匹配。
|
||
* (注意:如果存在类型不匹配,则 ExecEvalScalarVar 可能会在运行时引发错误,但我们将其交给它处理。)
|
||
*/
|
||
exprlist = NIL;
|
||
numSimpleVars = 0;
|
||
directMap = true;
|
||
foreach (tl, targetList) {
|
||
GenericExprState* gstate = (GenericExprState*)lfirst(tl);
|
||
Var* variable = (Var*)gstate->arg->expr;
|
||
bool isSimpleVar = false;
|
||
|
||
if (variable != NULL && IsA(variable, Var) && variable->varattno > 0) {
|
||
if (!inputDesc)
|
||
isSimpleVar = true; /* can't check type, assume OK */
|
||
else if (variable->varattno <= inputDesc->natts) {
|
||
Form_pg_attribute attr;
|
||
|
||
attr = inputDesc->attrs[variable->varattno - 1];
|
||
if (!attr->attisdropped && variable->vartype == attr->atttypid)
|
||
isSimpleVar = true;
|
||
}
|
||
}
|
||
|
||
if (isSimpleVar) {
|
||
TargetEntry* tle = (TargetEntry*)gstate->xprstate.expr;
|
||
AttrNumber attnum = variable->varattno;
|
||
|
||
varNumbers[numSimpleVars] = attnum;
|
||
varOutputCols[numSimpleVars] = tle->resno;
|
||
if (tle->resno != numSimpleVars + 1)
|
||
directMap = false;
|
||
|
||
switch (variable->varno) {
|
||
case INNER_VAR:
|
||
varSlotOffsets[numSimpleVars] = offsetof(ExprContext, ecxt_innertuple);
|
||
if (projInfo->pi_lastInnerVar < attnum)
|
||
projInfo->pi_lastInnerVar = attnum;
|
||
break;
|
||
|
||
case OUTER_VAR:
|
||
varSlotOffsets[numSimpleVars] = offsetof(ExprContext, ecxt_outertuple);
|
||
if (projInfo->pi_lastOuterVar < attnum)
|
||
projInfo->pi_lastOuterVar = attnum;
|
||
break;
|
||
|
||
/* INDEX_VAR is handled by default case */
|
||
default:
|
||
varSlotOffsets[numSimpleVars] = offsetof(ExprContext, ecxt_scantuple);
|
||
if (projInfo->pi_lastScanVar < attnum)
|
||
projInfo->pi_lastScanVar = attnum;
|
||
break;
|
||
}
|
||
numSimpleVars++;
|
||
} else {
|
||
/* Not a simple variable, add it to generic targetlist */
|
||
exprlist = lappend(exprlist, gstate);
|
||
/* 检查表达式以包含在 lastXXXVar 计数中包含的变量 */
|
||
get_last_attnums((Node*)variable, projInfo);
|
||
}
|
||
}
|
||
projInfo->pi_targetlist = exprlist;
|
||
projInfo->pi_numSimpleVars = numSimpleVars;
|
||
projInfo->pi_directMap = directMap;
|
||
|
||
if (exprlist == NIL)
|
||
projInfo->pi_itemIsDone = NULL; /* not needed */
|
||
else
|
||
projInfo->pi_itemIsDone = (ExprDoneCond*)palloc(len * sizeof(ExprDoneCond));
|
||
|
||
return projInfo;
|
||
}
|
||
|
||
/*
|
||
* get_last_attnums: 用于 ExecBuildProjectionInfo 的表达式遍历器
|
||
*
|
||
* 更新 lastXXXVar 计数,使其至少与表达式中发现的最大属性号一样大
|
||
*/
|
||
static bool get_last_attnums(Node* node, ProjectionInfo* projInfo)
|
||
{
|
||
if (node == NULL)
|
||
return false;
|
||
if (IsA(node, Var)) {
|
||
Var* variable = (Var*)node;
|
||
AttrNumber attnum = variable->varattno;
|
||
|
||
switch (variable->varno) {
|
||
case INNER_VAR:
|
||
if (projInfo->pi_lastInnerVar < attnum)
|
||
projInfo->pi_lastInnerVar = attnum;
|
||
break;
|
||
|
||
case OUTER_VAR:
|
||
if (projInfo->pi_lastOuterVar < attnum)
|
||
projInfo->pi_lastOuterVar = attnum;
|
||
break;
|
||
|
||
/* INDEX_VAR is handled by default case */
|
||
default:
|
||
if (projInfo->pi_lastScanVar < attnum)
|
||
projInfo->pi_lastScanVar = attnum;
|
||
break;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/*
|
||
* 不要检查 Aggrefs 或 WindowFuncs 的参数,因为它们不表示在整体目标列表的 econtext 中要评估的表达式。
|
||
* GroupingFunc 参数根本不会被评估。
|
||
*/
|
||
if (IsA(node, Aggref) || IsA(node, GroupingFunc))
|
||
return false;
|
||
if (IsA(node, WindowFunc))
|
||
return false;
|
||
return expression_tree_walker(node, (bool (*)())get_last_attnums, (void*)projInfo);
|
||
}
|
||
|
||
/*
|
||
* 从节点的目标列表中形成投影信息
|
||
*
|
||
* 输入 inputDesc 的注意事项与 ExecBuildProjectionInfo 相同:为关系扫描节点提供它,对于上层节点可以传递 NULL
|
||
*/
|
||
|
||
void ExecAssignProjectionInfo(PlanState* planstate, TupleDesc inputDesc)
|
||
{
|
||
planstate->ps_ProjInfo = ExecBuildProjectionInfo(
|
||
planstate->targetlist, planstate->ps_ExprContext, planstate->ps_ResultTupleSlot, inputDesc);
|
||
}
|
||
|
||
/*
|
||
* 在执行器关闭时,需要显式地释放计划节点的 ExprContext,因为可能有需要调用的关闭回调函数。(上述例程创建的其他资源,如投影信息,不需要显式释放,因为它们只是在每个查询的内存上下文中的内存。)
|
||
*
|
||
* 然而... 没有特定的需要在 ExecEndNode 期间执行它,因为 FreeExecutorState 将在 EState 中释放所有剩余的 ExprContext。让 FreeExecutorState 执行它允许 ExprContexts 按创建的相反顺序进行释放,而不是按创建顺序进行释放,这可以节省在 FreeExprContext 内部的列表清理中的 O(N^2) 的工作。
|
||
*/
|
||
|
||
void ExecFreeExprContext(PlanState* planstate)
|
||
{
|
||
/*
|
||
* 根据上述讨论,实际上不要删除 ExprContext。但我们会将其从计划节点中取消链接。
|
||
*/
|
||
planstate->ps_ExprContext = NULL;
|
||
}
|
||
|
||
/*
|
||
* 以下的扫描类型支持函数是为了那些顽固的节点,它们将元组返回到它们的扫描元组槽而不是结果元组槽中。
|
||
* 幸运的是,这些节点不进行投影操作,所以我们不必担心为它们正确获取 ProjectionInfo。 -cim 6/3/91
|
||
*/
|
||
|
||
/* ----------------
|
||
* ExecGetScanType
|
||
* ----------------
|
||
*/
|
||
TupleDesc ExecGetScanType(ScanState* scanstate)
|
||
{
|
||
TupleTableSlot* slot = scanstate->ss_ScanTupleSlot;
|
||
|
||
return slot->tts_tupleDescriptor;
|
||
}
|
||
|
||
/* ----------------
|
||
* ExecAssignScanType
|
||
* ----------------
|
||
*/
|
||
void ExecAssignScanType(ScanState* scanstate, TupleDesc tupDesc)
|
||
{
|
||
TupleTableSlot* slot = scanstate->ss_ScanTupleSlot;
|
||
|
||
ExecSetSlotDescriptor(slot, tupDesc);
|
||
}
|
||
|
||
/* ----------------
|
||
* ExecAssignScanTypeFromOuterPlan
|
||
* ----------------
|
||
*/
|
||
void ExecAssignScanTypeFromOuterPlan(ScanState* scanstate)
|
||
{
|
||
PlanState* outerPlan = NULL;
|
||
TupleDesc tupDesc;
|
||
|
||
outerPlan = outerPlanState(scanstate);
|
||
tupDesc = ExecGetResultType(outerPlan);
|
||
|
||
ExecAssignScanType(scanstate, tupDesc);
|
||
}
|
||
|
||
/* ----------------------------------------------------------------
|
||
* Scan node support
|
||
* ----------------------------------------------------------------
|
||
*/
|
||
/* ----------------------------------------------------------------
|
||
* ExecRelationIsTargetRelation
|
||
*
|
||
* 检测一个关系(通过范围表索引标识)是否是查询的目标关系之一。
|
||
* ----------------------------------------------------------------
|
||
*/
|
||
bool ExecRelationIsTargetRelation(EState* estate, Index scanrelid)
|
||
{
|
||
ResultRelInfo* resultRelInfos = NULL;
|
||
int i;
|
||
|
||
resultRelInfos = estate->es_result_relations;
|
||
for (i = 0; i < estate->es_num_result_relations; i++) {
|
||
if (resultRelInfos[i].ri_RangeTableIndex == scanrelid)
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/* ExecOpenScanRelation
|
||
|
||
* 在基本级别的扫描计划节点要扫描的堆关系上打开扫描。应该在节点的ExecInit例程中调用此函数。
|
||
*
|
||
* 默认情况下,这会在关系上获取AccessShareLock。但是,如果关系已经被InitPlan锁定,我们就不需要获取任何其他锁定。这可以节省共享锁管理器的访问。
|
||
*/
|
||
Relation ExecOpenScanRelation(EState* estate, Index scanrelid)
|
||
{
|
||
Oid reloid;
|
||
LOCKMODE lockmode;
|
||
Relation rel;
|
||
|
||
/*
|
||
* 确定我们需要的锁定类型。首先,扫描以查看目标关系是否是结果关系。
|
||
* 如果不是,检查它是否是一个FOR UPDATE/FOR SHARE关系。
|
||
* 在这两种情况下,我们已经获取了锁定。
|
||
*/
|
||
lockmode = AccessShareLock;
|
||
if (ExecRelationIsTargetRelation(estate, scanrelid))
|
||
lockmode = NoLock;
|
||
else {
|
||
ListCell* l = NULL;
|
||
|
||
foreach (l, estate->es_rowMarks) {
|
||
ExecRowMark* erm = (ExecRowMark*)lfirst(l);
|
||
|
||
/* 保持这个检查与InitPlan同步! */
|
||
if (erm->rti == scanrelid && erm->relation != NULL) {
|
||
lockmode = NoLock;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 打开关系并根据需要获取锁定 */
|
||
reloid = getrelid(scanrelid, estate->es_range_table);
|
||
rel = heap_open(reloid, lockmode);
|
||
|
||
if (STMT_RETRY_ENABLED) {
|
||
// 目前什么都不做,如果查询重试已启用,则跳过在这里进行 validateTempRelation 操作
|
||
} else
|
||
validateTempRelation(rel);
|
||
|
||
return rel;
|
||
}
|
||
|
||
/* ----------------------------------------------------------------
|
||
* ExecCloseScanRelation
|
||
*
|
||
* 关闭由基础级扫描计划节点扫描的堆关系。
|
||
* 应该在节点的 ExecEnd 例程中调用此函数。
|
||
*
|
||
* 目前,我们不会释放由 ExecOpenScanRelation 获取的锁。
|
||
* 这个锁应该保持到事务结束。(有人认为这是过多的锁定,但也有人持相反意见。)
|
||
*
|
||
* 如果我们确实想要释放这个锁,我们需要重复 ExecOpenScanRelation 中的逻辑,
|
||
* 以便弄清楚需要释放哪些资源。
|
||
* ----------------------------------------------------------------
|
||
*/
|
||
|
||
void ExecCloseScanRelation(Relation scanrel)
|
||
{
|
||
heap_close(scanrel, NoLock);
|
||
}
|
||
|
||
/*
|
||
* @@GaussDB@@
|
||
* 目标:数据分区
|
||
* 简述:打开由基础级扫描计划节点扫描的堆分区关系。这应该在节点的 ExecInit 例程中调用。
|
||
* 描述:
|
||
* 注意:默认情况下,这会在分区关系上获取 AccessShareLock。
|
||
* 但是,如果关系已经被 InitPlan 锁定,我们就不需要获取任何额外的锁。
|
||
* 这可以减少到共享锁管理器的访问次数。
|
||
*/
|
||
|
||
Partition ExecOpenScanParitition(EState* estate, Relation parent, PartitionIdentifier* partID, LOCKMODE lockmode)
|
||
{
|
||
Oid partoid = InvalidOid;
|
||
|
||
Assert(PointerIsValid(estate));
|
||
Assert(PointerIsValid(parent));
|
||
Assert(PointerIsValid(partID));
|
||
|
||
/* 打开关系并根据需要获取锁 */
|
||
partoid = partIDGetPartOid(parent, partID);
|
||
|
||
return partitionOpen(parent, partoid, lockmode);
|
||
}
|
||
|
||
/* ----------------------------------------------------------------
|
||
* ExecInsertIndexTuples support
|
||
* ----------------------------------------------------------------
|
||
*/
|
||
/*
|
||
* ----------------------------------------------------------------
|
||
* ExecOpenIndices
|
||
*
|
||
* 查找与结果关系关联的索引,打开它们,
|
||
* 并在结果 ResultRelInfo 中保存相关信息。
|
||
*
|
||
* 在进入此函数时,调用者已经打开并锁定了
|
||
* resultRelInfo->ri_RelationDesc。
|
||
* ----------------------------------------------------------------
|
||
*/
|
||
|
||
void ExecOpenIndices(ResultRelInfo* resultRelInfo, bool speculative)
|
||
{
|
||
Relation resultRelation = resultRelInfo->ri_RelationDesc;
|
||
List* indexoidlist = NIL;
|
||
ListCell* l = NULL;
|
||
int len, i;
|
||
RelationPtr relationDescs;
|
||
IndexInfo** indexInfoArray;
|
||
|
||
resultRelInfo->ri_NumIndices = 0;
|
||
resultRelInfo->ri_ContainGPI = false;
|
||
|
||
/* 如果没有索引,则使用快速路径 */
|
||
if (!RelationGetForm(resultRelation)->relhasindex)
|
||
return;
|
||
|
||
/* 获取缓存的索引 OID 列表 */
|
||
indexoidlist = RelationGetIndexList(resultRelation);
|
||
len = list_length(indexoidlist);
|
||
if (len == 0) {
|
||
return;
|
||
}
|
||
|
||
/* 为结果数组分配空间 */
|
||
relationDescs = (RelationPtr)palloc(len * sizeof(Relation));
|
||
indexInfoArray = (IndexInfo**)palloc(len * sizeof(IndexInfo*));
|
||
|
||
resultRelInfo->ri_IndexRelationDescs = relationDescs;
|
||
resultRelInfo->ri_IndexRelationInfo = indexInfoArray;
|
||
|
||
/* 对于每个索引,打开索引关系并保存pg_index信息。我们获取RowExclusiveLock,表示我们将更新索引。
|
||
注意:即使索引不是IndexIsReady,我们也会这样做;优化它不值得。
|
||
*/
|
||
i = 0;
|
||
foreach (l, indexoidlist) {
|
||
Oid indexOid = lfirst_oid(l);
|
||
Relation indexDesc;
|
||
IndexInfo* ii = NULL;
|
||
|
||
indexDesc = index_open(indexOid, RowExclusiveLock);
|
||
|
||
// 忽略无法使用的索引上的INSERT/UPDATE/DELETE操作
|
||
if (!IndexIsUsable(indexDesc->rd_index)) {
|
||
index_close(indexDesc, RowExclusiveLock);
|
||
continue;
|
||
}
|
||
|
||
// 检查索引是否为全局分区索引,然后保存
|
||
if (RelationIsGlobalIndex(indexDesc)) {
|
||
resultRelInfo->ri_ContainGPI = true;
|
||
}
|
||
|
||
// 从索引的 pg_index 信息中提取索引键信息
|
||
ii = BuildIndexInfo(indexDesc);
|
||
|
||
/*
|
||
* 如果索引将用于推测性插入,则需要添加唯一索引条目所需的额外信息。
|
||
*/
|
||
|
||
if (speculative && ii->ii_Unique) {
|
||
BuildSpeculativeIndexInfo(indexDesc, ii);
|
||
}
|
||
relationDescs[i] = indexDesc;
|
||
indexInfoArray[i] = ii;
|
||
i++;
|
||
}
|
||
|
||
// 记得设置可用索引的数量
|
||
resultRelInfo->ri_NumIndices = i;
|
||
|
||
list_free_ext(indexoidlist);
|
||
}
|
||
|
||
/* ----------------------------------------------------------------
|
||
* ExecCloseIndices
|
||
*
|
||
* 关闭存储在resultRelInfo中的索引关系
|
||
* ----------------------------------------------------------------
|
||
*/
|
||
void ExecCloseIndices(ResultRelInfo* resultRelInfo)
|
||
{
|
||
int i;
|
||
int numIndices;
|
||
RelationPtr indexDescs;
|
||
|
||
numIndices = resultRelInfo->ri_NumIndices;
|
||
indexDescs = resultRelInfo->ri_IndexRelationDescs;
|
||
|
||
for (i = 0; i < numIndices; i++) {
|
||
if (indexDescs[i] == NULL)
|
||
continue; /* shouldn't happen? */
|
||
|
||
/* 释放ExecOpenIndices获取的锁 */
|
||
index_close(indexDescs[i], RowExclusiveLock);
|
||
}
|
||
|
||
/* XXX 应该在这里释放indexInfo数组吗?当前我们假设这些内容将在FreeExecutorState中自动清理。 */
|
||
|
||
}
|
||
|
||
/* 从ExecInsertIndexTuples复制而来 */
|
||
|
||
void ExecDeleteIndexTuples(TupleTableSlot* slot, ItemPointer tupleid, EState* estate,
|
||
Relation targetPartRel, Partition p, const Bitmapset *modifiedIdxAttrs, const bool inplaceUpdated)
|
||
{
|
||
ResultRelInfo* resultRelInfo = NULL;
|
||
int numIndices;
|
||
RelationPtr relationDescs;
|
||
Relation heapRelation;
|
||
IndexInfo** indexInfoArray;
|
||
ExprContext* econtext = NULL;
|
||
Datum values[INDEX_MAX_KEYS];
|
||
bool isnull[INDEX_MAX_KEYS];
|
||
Relation actualheap;
|
||
bool ispartitionedtable = false;
|
||
List* partitionIndexOidList = NIL;
|
||
|
||
resultRelInfo = estate->es_result_relation_info;
|
||
|
||
numIndices = resultRelInfo->ri_NumIndices;
|
||
if (numIndices == 0) {
|
||
return;
|
||
}
|
||
|
||
if (slot->tts_nvalid == 0) {
|
||
tableam_tslot_getallattrs(slot);
|
||
}
|
||
|
||
if (slot->tts_nvalid == 0) {
|
||
elog(ERROR, "no values in slot when trying to delete index tuple");
|
||
}
|
||
|
||
/*
|
||
* 从结果关系信息结构中获取信息。
|
||
*/
|
||
|
||
relationDescs = resultRelInfo->ri_IndexRelationDescs;
|
||
indexInfoArray = resultRelInfo->ri_IndexRelationInfo;
|
||
heapRelation = resultRelInfo->ri_RelationDesc;
|
||
|
||
/*
|
||
* 我们将使用EState的每个元组上下文来评估谓词和索引表达式(如果尚未创建上下文,则创建它)。
|
||
*/
|
||
econtext = GetPerTupleExprContext(estate);
|
||
|
||
/* 安排econtext的扫描元组成为要测试的元组 */
|
||
econtext->ecxt_scantuple = slot;
|
||
|
||
if (RELATION_IS_PARTITIONED(heapRelation)) {
|
||
Assert(PointerIsValid(targetPartRel));
|
||
|
||
ispartitionedtable = true;
|
||
|
||
actualheap = targetPartRel;
|
||
|
||
if (p == NULL || p->pd_part == NULL) {
|
||
return;
|
||
}
|
||
if (!p->pd_part->indisusable) {
|
||
numIndices = 0;
|
||
}
|
||
} else {
|
||
actualheap = heapRelation;
|
||
}
|
||
|
||
if (!RelationIsUstoreFormat(heapRelation))
|
||
return;
|
||
|
||
/* 对于每个索引,生成并插入索引元组 */
|
||
for (int i = 0; i < numIndices; i++) {
|
||
Relation indexRelation = relationDescs[i];
|
||
IndexInfo* indexInfo = NULL;
|
||
Oid partitionedindexid = InvalidOid;
|
||
Oid indexpartitionid = InvalidOid;
|
||
Relation actualindex = NULL;
|
||
Partition indexpartition = NULL;
|
||
|
||
if (indexRelation == NULL) {
|
||
continue;
|
||
}
|
||
|
||
indexInfo = indexInfoArray[i];
|
||
|
||
/* 如果索引标记为只读,忽略它 */
|
||
if (!indexInfo->ii_ReadyForInserts) {
|
||
continue;
|
||
}
|
||
|
||
/* modifiedIdxAttrs != NULL 表示更新操作,不是每个索引都受影响 */
|
||
if (inplaceUpdated && modifiedIdxAttrs != NULL) {
|
||
/* 收集此索引的属性 Bitmapset 并与 modifiedIdxAttrs 进行比较 */
|
||
Bitmapset *indexattrs = IndexGetAttrBitmap(indexRelation, indexInfo);
|
||
bool overlap = bms_overlap(indexattrs, modifiedIdxAttrs);
|
||
|
||
bms_free(indexattrs);
|
||
if (!overlap) {
|
||
continue; /* 相关列未被修改 */
|
||
}
|
||
}
|
||
|
||
/* GPI索引插入与常规表相同 */
|
||
if (ispartitionedtable && !RelationIsGlobalIndex(indexRelation)) {
|
||
partitionedindexid = RelationGetRelid(indexRelation);
|
||
if (!PointerIsValid(partitionIndexOidList)) {
|
||
partitionIndexOidList = PartitionGetPartIndexList(p);
|
||
// 没有可用的本地索引
|
||
if (!PointerIsValid(partitionIndexOidList)) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
indexpartitionid = searchPartitionIndexOid(partitionedindexid, partitionIndexOidList);
|
||
|
||
searchFakeReationForPartitionOid(estate->esfRelations,
|
||
estate->es_query_cxt,
|
||
indexRelation,
|
||
indexpartitionid,
|
||
actualindex,
|
||
indexpartition,
|
||
RowExclusiveLock);
|
||
// 跳过不可用的索引
|
||
if (indexpartition != NULL && indexpartition->pd_part != NULL && !indexpartition->pd_part->indisusable) {
|
||
continue;
|
||
}
|
||
} else {
|
||
actualindex = indexRelation;
|
||
}
|
||
/* 请在这里适应 ustore 的哈希桶。参考 ExecInsertIndexTuples() 函数。 */
|
||
|
||
/* 检查部分索引 */
|
||
if (indexInfo->ii_Predicate != NIL) {
|
||
List* predicate = NIL;
|
||
|
||
/*
|
||
* 如果断言状态尚未设置,则创建它(在estate的每个查询上下文中)
|
||
*/
|
||
predicate = indexInfo->ii_PredicateState;
|
||
if (predicate == NIL) {
|
||
predicate = (List*)ExecPrepareExpr((Expr*)indexInfo->ii_Predicate, estate);
|
||
indexInfo->ii_PredicateState = predicate;
|
||
}
|
||
|
||
/* 如果断言未满足,则跳过此索引更新 */
|
||
if (!ExecQual(predicate, econtext, false)) {
|
||
continue;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* FormIndexDatum填充其values和isnull参数,以获得索引的列的适当值。
|
||
*/
|
||
|
||
FormIndexDatum(indexInfo, slot, estate, values, isnull);
|
||
|
||
index_delete(actualindex, values, isnull, tupleid);
|
||
}
|
||
|
||
list_free_ext(partitionIndexOidList);
|
||
}
|
||
|
||
void ExecUHeapDeleteIndexTuplesGuts(
|
||
TupleTableSlot* oldslot, Relation rel, ModifyTableState* node, ItemPointer tupleid,
|
||
ExecIndexTuplesState exec_index_tuples_state, Bitmapset *modifiedIdxAttrs, bool inplaceUpdated)
|
||
{
|
||
Assert(oldslot);
|
||
if (node != NULL && node->mt_upsert->us_action == UPSERT_UPDATE) {
|
||
ExecDeleteIndexTuples(node->mt_upsert->us_existing,
|
||
tupleid,
|
||
exec_index_tuples_state.estate, exec_index_tuples_state.targetPartRel,
|
||
exec_index_tuples_state.p,
|
||
modifiedIdxAttrs,
|
||
inplaceUpdated);
|
||
} else {
|
||
UHeapTuple tmpUtup = ExecGetUHeapTupleFromSlot(oldslot);// 将元组材料化(将元组的内部格式转换为可以插入索引的格式)
|
||
|
||
tmpUtup->table_oid = RelationGetRelid(rel);
|
||
ExecDeleteIndexTuples(oldslot,
|
||
tupleid,
|
||
exec_index_tuples_state.estate, exec_index_tuples_state.targetPartRel,
|
||
exec_index_tuples_state.p,
|
||
modifiedIdxAttrs,
|
||
inplaceUpdated);
|
||
}
|
||
}
|
||
|
||
/* 仅用于降低圈复杂性 */
|
||
static inline bool GetPartiionIndexOidList(List **oidlist_ptr, Partition part)
|
||
{
|
||
Assert(oidlist_ptr != NULL);
|
||
|
||
if (!PointerIsValid(*oidlist_ptr)) {
|
||
*oidlist_ptr = PartitionGetPartIndexList(part);
|
||
if (!PointerIsValid(*oidlist_ptr)) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
static inline bool CheckForPartialIndex(IndexInfo* indexInfo, EState* estate, ExprContext* econtext)
|
||
{
|
||
List* predicate = indexInfo->ii_PredicateState;
|
||
|
||
if (indexInfo->ii_Predicate != NIL) {
|
||
/*
|
||
* 如果谓词状态尚未设置,请在执行环境的每个查询上下文中创建它。
|
||
*/
|
||
|
||
if (predicate == NIL) {
|
||
predicate = (List*)ExecPrepareExpr((Expr*)indexInfo->ii_Predicate, estate);
|
||
indexInfo->ii_PredicateState = predicate;
|
||
}
|
||
|
||
/* 如果谓词不满足,则跳过这个索引更新 */
|
||
if (!ExecQual(predicate, econtext, false)) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/* 如果 indexInfo->ii_Predicate 为空,则直接返回 true,以便继续执行 */
|
||
return true;
|
||
}
|
||
|
||
static inline void SetInfoForUpsertGPI(bool isgpi, Relation *actualHeap, Relation *parentRel, bool *isgpiResult,
|
||
Oid *partoid, int2 *bktid)
|
||
{
|
||
if (isgpi) {
|
||
*actualHeap = *parentRel;
|
||
*isgpiResult = true;
|
||
*partoid = InvalidOid;
|
||
*bktid = InvalidBktId;
|
||
} else {
|
||
*isgpiResult = false;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* ExecCheckIndexConstraints
|
||
*
|
||
* 此例程检查元组是否违反任何唯一或排除约束。如果没有冲突则返回true。
|
||
* 否则返回false,并将冲突元组的TID存储在*conflictTid中。
|
||
*
|
||
* 注意,这不会以任何方式锁定值,因此在此返回后,可能立即插入冲突的元组。
|
||
* 但这可以用于插入之前的预检查。
|
||
*/
|
||
bool ExecCheckIndexConstraints(TupleTableSlot *slot, EState *estate, Relation targetRel, Partition p, bool *isgpiResult,
|
||
int2 bucketId, ConflictInfoData *conflictInfo, Oid *conflictPartOid,
|
||
int2 *conflictBucketid)
|
||
{
|
||
ResultRelInfo* resultRelInfo = NULL;
|
||
RelationPtr relationDescs = NULL;
|
||
int i = 0;
|
||
int numIndices = 0;
|
||
IndexInfo** indexInfoArray = NULL;
|
||
Relation heapRelationDesc = NULL;
|
||
Relation actualHeap = NULL;
|
||
ExprContext* econtext = NULL;
|
||
Datum values[INDEX_MAX_KEYS];
|
||
bool isnull[INDEX_MAX_KEYS];
|
||
ItemPointerData invalidItemPtr;
|
||
bool isPartitioned = false;
|
||
bool containGPI;
|
||
List* partitionIndexOidList = NIL;
|
||
Oid partoid;
|
||
int2 bktid;
|
||
errno_t rc;
|
||
|
||
ItemPointerSetInvalid(&conflictInfo->conflictTid);
|
||
ItemPointerSetInvalid(&invalidItemPtr);
|
||
|
||
/*
|
||
* 从结果关系信息结构中获取信息。
|
||
*/
|
||
resultRelInfo = estate->es_result_relation_info;
|
||
numIndices = resultRelInfo->ri_NumIndices;
|
||
relationDescs = resultRelInfo->ri_IndexRelationDescs;
|
||
indexInfoArray = resultRelInfo->ri_IndexRelationInfo;
|
||
heapRelationDesc = resultRelInfo->ri_RelationDesc;
|
||
containGPI = resultRelInfo->ri_ContainGPI;
|
||
actualHeap = targetRel;
|
||
|
||
rc = memset_s(isnull, sizeof(isnull), 0, sizeof(isnull));
|
||
securec_check(rc, "", "");
|
||
|
||
if (RELATION_IS_PARTITIONED(heapRelationDesc)) {
|
||
Assert(p != NULL && p->pd_part != NULL);
|
||
isPartitioned = true;
|
||
|
||
if (!p->pd_part->indisusable && !containGPI) {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 使用EState的每个元组上下文来评估谓词和索引表达式(如果不存在则创建)。
|
||
*/
|
||
|
||
econtext = GetPerTupleExprContext(estate);
|
||
|
||
/* 安排econtext的扫描元组为待测试的元组 */
|
||
econtext->ecxt_scantuple = slot;
|
||
|
||
/*
|
||
* 对于每个索引,形成索引元组并检查它是否满足约束。
|
||
*/
|
||
for (i = 0; i < numIndices; i++) {
|
||
Relation indexRelation = relationDescs[i];
|
||
IndexInfo* indexInfo = NULL;
|
||
bool satisfiesConstraint = false;
|
||
Relation actualIndex = NULL;
|
||
Oid partitionedindexid = InvalidOid;
|
||
Oid indexpartitionid = InvalidOid;
|
||
Partition indexpartition = NULL;
|
||
|
||
if (indexRelation == NULL)
|
||
continue;
|
||
|
||
bool isgpi = RelationIsGlobalIndex(indexRelation);
|
||
bool iscbi = RelationIsCrossBucketIndex(indexRelation);
|
||
|
||
indexInfo = indexInfoArray[i];
|
||
|
||
if (!indexInfo->ii_Unique && !indexInfo->ii_ExclusionOps)
|
||
continue;
|
||
|
||
/* If the index is marked as read-only, ignore it */
|
||
if (!indexInfo->ii_ReadyForInserts)
|
||
continue;
|
||
|
||
if (!indexRelation->rd_index->indimmediate)
|
||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||
errmsg("INSERT ON DUPLICATE KEY UPDATE does not support deferrable"
|
||
" unique constraints/exclusion constraints.")));
|
||
/*
|
||
* 我们将具有全局索引的分区表视为普通表,因为冲突可能发生在多个分区之间。
|
||
*/
|
||
|
||
if (isPartitioned && !isgpi) {
|
||
partitionedindexid = RelationGetRelid(indexRelation);
|
||
|
||
if (!GetPartiionIndexOidList(&partitionIndexOidList, p)) {
|
||
/* no local indexes available */
|
||
return true;
|
||
}
|
||
|
||
indexpartitionid = searchPartitionIndexOid(partitionedindexid, partitionIndexOidList);
|
||
|
||
searchFakeReationForPartitionOid(estate->esfRelations,
|
||
estate->es_query_cxt,
|
||
indexRelation,
|
||
indexpartitionid,
|
||
actualIndex,
|
||
indexpartition,
|
||
RowExclusiveLock);
|
||
/* skip unusable index */
|
||
if (indexpartition->pd_part->indisusable == false) {
|
||
continue;
|
||
}
|
||
} else {
|
||
actualIndex = indexRelation;
|
||
}
|
||
|
||
if (bucketId != InvalidBktId && !iscbi) {
|
||
searchHBucketFakeRelation(estate->esfRelations, estate->es_query_cxt, actualIndex, bucketId, actualIndex);
|
||
}
|
||
|
||
/* Check for partial index */
|
||
if (!CheckForPartialIndex(indexInfo, estate, econtext)) {
|
||
continue;
|
||
}
|
||
|
||
/*
|
||
* FormIndexDatum使用适当的值填充其值和isnull参数,以用于索引的列(s)。
|
||
*/
|
||
|
||
FormIndexDatum(indexInfo, slot, estate, values, isnull);
|
||
|
||
partoid = (isgpi ? p->pd_id : InvalidOid);
|
||
bktid = (iscbi ? bucketId : InvalidBktId);
|
||
|
||
SetInfoForUpsertGPI(isgpi, &actualHeap, &heapRelationDesc, isgpiResult, &partoid, &bktid);
|
||
|
||
satisfiesConstraint =
|
||
check_violation(actualHeap, actualIndex, indexInfo, &invalidItemPtr, values, isnull, estate, false, true,
|
||
CHECK_WAIT, conflictInfo, partoid, bktid, conflictPartOid, conflictBucketid);
|
||
if (!satisfiesConstraint) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
* 以注释的形式翻译:
|
||
|
||
* ----------------------------------------------------------------
|
||
* ExecInsertIndexTuples
|
||
*
|
||
* 此例程负责在将堆元组插入结果关系时插入索引元组,所有索引关系都索引结果关系。
|
||
* 大部分代码应该移到genam模块中,因为它只存在于此处是因为genam模块提供的功能不满足执行器所需。
|
||
* -cim 1989年9月27日
|
||
*
|
||
* 此函数返回在唯一或排他约束中存在潜在(未确认)冲突且被推迟的情况下的所有索引OID列表。
|
||
*
|
||
* 注意:不能为HOT更新调用此函数。由于缺乏信息,我们无法在此处防范这种情况。我们是否应该更改API以使其更安全?
|
||
* ----------------------------------------------------------------
|
||
*/
|
||
|
||
List* ExecInsertIndexTuples(TupleTableSlot* slot, ItemPointer tupleid, EState* estate,
|
||
Relation targetPartRel, Partition p, int2 bucketId, bool* conflict,
|
||
Bitmapset *modifiedIdxAttrs, bool inplaceUpdated)
|
||
{
|
||
List* result = NIL;
|
||
ResultRelInfo* resultRelInfo = NULL;
|
||
int i;
|
||
int numIndices;
|
||
RelationPtr relationDescs;
|
||
Relation heapRelation;
|
||
IndexInfo** indexInfoArray;
|
||
ExprContext* econtext = NULL;
|
||
Datum values[INDEX_MAX_KEYS];
|
||
bool isnull[INDEX_MAX_KEYS];
|
||
Relation actualheap;
|
||
bool ispartitionedtable = false;
|
||
bool containGPI;
|
||
List* partitionIndexOidList = NIL;
|
||
|
||
/*
|
||
* 从结果关系信息结构中获取信息。
|
||
*/
|
||
|
||
resultRelInfo = estate->es_result_relation_info;
|
||
numIndices = resultRelInfo->ri_NumIndices;
|
||
relationDescs = resultRelInfo->ri_IndexRelationDescs;
|
||
indexInfoArray = resultRelInfo->ri_IndexRelationInfo;
|
||
heapRelation = resultRelInfo->ri_RelationDesc;
|
||
containGPI = resultRelInfo->ri_ContainGPI;
|
||
|
||
/*
|
||
* 我们将使用EState的每个元组上下文来评估谓词和索引表达式(如果尚不存在,则创建它)。
|
||
*/
|
||
|
||
econtext = GetPerTupleExprContext(estate);
|
||
|
||
/* 安排econtext的扫描元组成为待测试的元组 */
|
||
|
||
econtext->ecxt_scantuple = slot;
|
||
|
||
if (RELATION_IS_PARTITIONED(heapRelation)) {
|
||
Assert(PointerIsValid(targetPartRel));
|
||
|
||
ispartitionedtable = true;
|
||
|
||
actualheap = targetPartRel;
|
||
|
||
if (p == NULL || p->pd_part == NULL) {
|
||
return NIL;
|
||
}
|
||
/* 如果包括全局分区索引,则需要继续索引插入过程 */
|
||
if (!p->pd_part->indisusable && !containGPI) {
|
||
numIndices = 0;
|
||
}
|
||
} else {
|
||
actualheap = heapRelation;
|
||
}
|
||
|
||
if (bucketId != InvalidBktId) {
|
||
searchHBucketFakeRelation(estate->esfRelations, estate->es_query_cxt, actualheap, bucketId, actualheap);
|
||
}
|
||
|
||
/* 在当前事务中创建分区,设置分区和关系的reloption为wait_clean_gpi */
|
||
if (RelationCreateInCurrXact(actualheap) && containGPI && !PartitionEnableWaitCleanGpi(p)) {
|
||
/* 如果分区创建时没有设置wait_clean_gpi,则必须使用更新,我们确保没有并发操作 */
|
||
PartitionSetWaitCleanGpi(RelationGetRelid(actualheap), true, false);
|
||
/* 分区创建设置wait_clean_gpi=n,我们想要保存它,所以只需使用inplace */
|
||
PartitionedSetWaitCleanGpi(RelationGetRelationName(heapRelation), RelationGetRelid(heapRelation), true, true);
|
||
}
|
||
|
||
/* 对于每个索引,形成并插入索引元组 */
|
||
|
||
for (i = 0; i < numIndices; i++) {
|
||
Relation indexRelation = relationDescs[i];
|
||
IndexInfo* indexInfo = NULL;
|
||
IndexUniqueCheck checkUnique;
|
||
bool satisfiesConstraint = false;
|
||
Oid partitionedindexid = InvalidOid;
|
||
Oid indexpartitionid = InvalidOid;
|
||
Relation actualindex = NULL;
|
||
Partition indexpartition = NULL;
|
||
|
||
if (indexRelation == NULL) {
|
||
continue;
|
||
}
|
||
|
||
indexInfo = indexInfoArray[i];
|
||
|
||
/* If the index is marked as read-only, ignore it */
|
||
if (!indexInfo->ii_ReadyForInserts) {
|
||
continue;
|
||
}
|
||
|
||
/* modifiedIdxAttrs != NULL 意味着正在更新,不是每个索引都受影响 */
|
||
if (inplaceUpdated && modifiedIdxAttrs != NULL) {
|
||
/* 收集此索引的属性 Bitmapset,并与 modifiedIdxAttrs 进行比较 */
|
||
Bitmapset *indexattrs = IndexGetAttrBitmap(indexRelation, indexInfo);
|
||
bool overlap = bms_overlap(indexattrs, modifiedIdxAttrs);
|
||
|
||
bms_free(indexattrs);
|
||
if (!overlap) {
|
||
continue; /* related columns are not modified */
|
||
}
|
||
}
|
||
|
||
/* 全局分区索引(GPI)的插入与普通表相同 */
|
||
if (ispartitionedtable && !RelationIsGlobalIndex(indexRelation)) {
|
||
partitionedindexid = RelationGetRelid(indexRelation);
|
||
if (!PointerIsValid(partitionIndexOidList)) {
|
||
partitionIndexOidList = PartitionGetPartIndexList(p);
|
||
// no local indexes available
|
||
if (!PointerIsValid(partitionIndexOidList)) {
|
||
return NIL;
|
||
}
|
||
}
|
||
|
||
indexpartitionid = searchPartitionIndexOid(partitionedindexid, partitionIndexOidList);
|
||
|
||
searchFakeReationForPartitionOid(estate->esfRelations,
|
||
estate->es_query_cxt,
|
||
indexRelation,
|
||
indexpartitionid,
|
||
actualindex,
|
||
indexpartition,
|
||
RowExclusiveLock);
|
||
// skip unusable index
|
||
if (!indexpartition->pd_part->indisusable) {
|
||
continue;
|
||
}
|
||
} else {
|
||
actualindex = indexRelation;
|
||
}
|
||
if (bucketId != InvalidBktId && !RelationIsCrossBucketIndex(indexRelation)) {
|
||
searchHBucketFakeRelation(estate->esfRelations, estate->es_query_cxt, actualindex, bucketId, actualindex);
|
||
}
|
||
|
||
/* Check for partial index */
|
||
if (indexInfo->ii_Predicate != NIL) {
|
||
List* predicate = NIL;
|
||
|
||
/*
|
||
* 如果谓词状态尚未设置,就在estate的每个查询上下文中创建它。
|
||
*/
|
||
|
||
predicate = indexInfo->ii_PredicateState;
|
||
if (predicate == NIL) {
|
||
predicate = (List*)ExecPrepareExpr((Expr*)indexInfo->ii_Predicate, estate);
|
||
indexInfo->ii_PredicateState = predicate;
|
||
}
|
||
|
||
/* 如果谓词不满足,则跳过这个索引更新 */
|
||
|
||
if (!ExecQual(predicate, econtext, false)) {
|
||
continue;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* FormIndexDatum会填充其values和isnull参数,其中包含索引的列的适当值。
|
||
*/
|
||
FormIndexDatum(indexInfo, slot, estate, values, isnull);
|
||
|
||
/*
|
||
* 对于立即模式的唯一索引,我们只需告诉索引AM如果不唯一就抛出错误。
|
||
*
|
||
* 对于可延迟的唯一索引,我们告诉索引AM仅检测可能的非唯一性,如果需要进一步检查,则将索引OID添加到结果列表中。
|
||
*/
|
||
|
||
if (!indexRelation->rd_index->indisunique) {
|
||
checkUnique = UNIQUE_CHECK_NO;
|
||
} else if (conflict != NULL) {
|
||
checkUnique = UNIQUE_CHECK_UPSERT;
|
||
} else if (indexRelation->rd_index->indimmediate) {
|
||
checkUnique = UNIQUE_CHECK_YES;
|
||
} else {
|
||
checkUnique = UNIQUE_CHECK_PARTIAL;
|
||
}
|
||
|
||
satisfiesConstraint = index_insert(actualindex, /* index relation */
|
||
values, /* array of index Datums */
|
||
isnull, /* null flags */
|
||
tupleid, /* tid of heap tuple */
|
||
actualheap, /* heap relation */
|
||
checkUnique); /* type of uniqueness check to do */
|
||
|
||
/*
|
||
* 如果索引有一个关联的排他约束,则进行检查。
|
||
* 这比唯一性检查的过程简单,因为我们总是先插入然后再检查。
|
||
* 如果约束被延迟,我们现在也进行检查,但不会在违反时抛出错误;相反,我们将排队重新检查事件。
|
||
*
|
||
* 一个用于排他约束的索引也不能是唯一的(不是必需的属性,我们只是不允许在语法中使用它),所以不需要保留satisfiesConstraint的先前状态。
|
||
*/
|
||
|
||
if (indexInfo->ii_ExclusionOps != NULL) {
|
||
bool errorOK = !actualindex->rd_index->indimmediate;
|
||
|
||
satisfiesConstraint = check_exclusion_constraint(
|
||
actualheap, actualindex, indexInfo, tupleid, values, isnull, estate, false, errorOK);
|
||
}
|
||
|
||
if ((IndexUniqueCheckNoError(checkUnique) || indexInfo->ii_ExclusionOps != NULL) && !satisfiesConstraint) {
|
||
/*
|
||
* 该元组可能违反唯一性或排除约束,因此请注意索引,以便稍后重新检查它。
|
||
* 如果有投机性冲突,会告诉投机插入者,因为这总是需要重新开始。
|
||
*/
|
||
|
||
result = lappend_oid(result, RelationGetRelid(indexRelation));
|
||
if (conflict != NULL) {
|
||
*conflict = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
list_free_ext(partitionIndexOidList);
|
||
return result;
|
||
}
|
||
|
||
/*
|
||
* 检查排除约束是否违反
|
||
*
|
||
* heap: 包含新元组的表
|
||
* index: 支持排除约束的索引
|
||
* indexInfo: 关于索引的信息,包括排除属性
|
||
* tupleid: 我们刚刚插入的新元组的堆TID
|
||
* values, isnull: 为新元组计算的*索引*列值
|
||
* estate: 我们可以在其中进行评估的EState
|
||
* newIndex: 如果为true,我们正在尝试构建新索引(这仅影响错误消息的措辞)
|
||
* errorOK: 如果为true,则不会因违规而抛出错误
|
||
*
|
||
* 如果errorOK为true,我们会在不等待查看任何并发事务是否已提交的情况下报告违规;因此,违规仅是潜在的,调用者必须稍后重新检查。
|
||
* 这种行为对于延迟的排除检查非常方便;如果在插入时明确没有冲突,我们就不必费心排队延迟事件。
|
||
*
|
||
* 当errorOK为false时,我们会在违规时抛出错误,因此不可能出现false的结果。
|
||
*/
|
||
|
||
bool check_exclusion_constraint(Relation heap, Relation index, IndexInfo* indexInfo, ItemPointer tupleid, Datum* values,
|
||
const bool* isnull, EState* estate, bool newIndex, bool errorOK)
|
||
{
|
||
return check_violation(heap, index, indexInfo, tupleid, values, isnull,
|
||
estate, newIndex, errorOK, errorOK ? CHECK_NOWAIT : CHECK_WAIT, NULL);
|
||
}
|
||
|
||
static inline IndexScanDesc scan_handler_idx_beginscan_wrapper(Relation parentheap, Relation heap, Relation index,
|
||
Snapshot snapshot, int nkeys, int norderbys, ScanState* scan_state)
|
||
{
|
||
IndexScanDesc index_scan;
|
||
if (RelationIsCrossBucketIndex(index) && RELATION_OWN_BUCKET(parentheap)) {
|
||
/* 对于跨桶索引,传递父关系以构造HBktIdxScanDesc */
|
||
index_scan = scan_handler_idx_beginscan(parentheap, index, snapshot, nkeys, norderbys, scan_state);
|
||
HBktIdxScanDesc hpscan = (HBktIdxScanDesc)index_scan;
|
||
/* 然后将扫描范围设置为目标堆 */
|
||
hpscan->currBktHeapRel = hpscan->currBktIdxScan->heapRelation = heap;
|
||
/* 同时确保目标堆在扫描结束时不会被释放 */
|
||
hpscan->rs_rd = heap;
|
||
} else {
|
||
index_scan = scan_handler_idx_beginscan(heap, index, snapshot, nkeys, norderbys, scan_state);
|
||
}
|
||
|
||
return index_scan;
|
||
}
|
||
|
||
static inline bool index_scan_need_recheck(IndexScanDesc scan)
|
||
{
|
||
if (RELATION_OWN_BUCKET(scan->indexRelation)) {
|
||
return ((HBktIdxScanDesc)scan)->currBktIdxScan->xs_recheck;
|
||
}
|
||
|
||
return scan->xs_recheck;
|
||
}
|
||
|
||
bool check_violation(Relation heap, Relation index, IndexInfo *indexInfo, ItemPointer tupleid, Datum *values,
|
||
const bool *isnull, EState *estate, bool newIndex, bool errorOK, CheckWaitMode waitMode,
|
||
ConflictInfoData *conflictInfo, Oid partoid, int2 bucketid, Oid *conflictPartOid,
|
||
int2 *conflictBucketid)
|
||
{
|
||
Oid* constr_procs = indexInfo->ii_ExclusionProcs;
|
||
uint16* constr_strats = indexInfo->ii_ExclusionStrats;
|
||
Oid* index_collations = index->rd_indcollation;
|
||
int indnkeyatts = IndexRelationGetNumberOfKeyAttributes(index);
|
||
IndexScanDesc index_scan;
|
||
Tuple tup;
|
||
ScanKeyData scankeys[INDEX_MAX_KEYS];
|
||
SnapshotData DirtySnapshot;
|
||
int i;
|
||
bool conflict = false;
|
||
bool found_self = false;
|
||
ExprContext* econtext = NULL;
|
||
TupleTableSlot* existing_slot = NULL;
|
||
TupleTableSlot* save_scantuple = NULL;
|
||
Relation parentheap;
|
||
|
||
/*
|
||
* 如果任何输入值为NULL,则假定约束检查通过(即,我们假设操作符是严格的)。
|
||
*/
|
||
|
||
for (i = 0; i < indnkeyatts; i++) {
|
||
if (isnull[i]) {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
if (indexInfo->ii_ExclusionOps) {
|
||
constr_procs = indexInfo->ii_ExclusionProcs;
|
||
constr_strats = indexInfo->ii_ExclusionStrats;
|
||
} else {
|
||
constr_procs = indexInfo->ii_UniqueProcs;
|
||
constr_strats = indexInfo->ii_UniqueStrats;
|
||
}
|
||
/*
|
||
* 在索引中搜索违规的元组,包括尚不可见的元组。
|
||
*/
|
||
InitDirtySnapshot(DirtySnapshot);
|
||
|
||
for (i = 0; i < indnkeyatts; i++) {
|
||
ScanKeyEntryInitialize(
|
||
&scankeys[i], 0, i + 1, constr_strats[i], InvalidOid, index_collations[i], constr_procs[i], values[i]);
|
||
}
|
||
|
||
/*
|
||
* 需要一个 TupleTableSlot 用来放置现有的元组。
|
||
*
|
||
* 为了使用 FormIndexDatum,我们必须让 econtext 的 scantuple 指向这个插槽。
|
||
* 请确保保存并还原调用者对 scantuple 的值。
|
||
*/
|
||
|
||
existing_slot = MakeSingleTupleTableSlot(RelationGetDescr(heap), false, heap->rd_tam_type);
|
||
econtext = GetPerTupleExprContext(estate);
|
||
save_scantuple = econtext->ecxt_scantuple;
|
||
econtext->ecxt_scantuple = existing_slot;
|
||
|
||
/*
|
||
* 如果发现潜在的冲突,可能需要从此处重新开始扫描。
|
||
*/
|
||
|
||
retry:
|
||
conflict = false;
|
||
found_self = false;
|
||
|
||
/* 仅仅是为了降低循环复杂度 */
|
||
parentheap = estate->es_result_relation_info->ri_RelationDesc;
|
||
index_scan = scan_handler_idx_beginscan_wrapper(parentheap, heap, index, &DirtySnapshot, indnkeyatts, 0, NULL);
|
||
scan_handler_idx_rescan_local(index_scan, scankeys, indnkeyatts, NULL, 0);
|
||
index_scan->isUpsert = true;
|
||
|
||
while ((tup = scan_handler_idx_getnext(index_scan, ForwardScanDirection, partoid, bucketid)) != NULL) {
|
||
TransactionId xwait;
|
||
Datum existing_values[INDEX_MAX_KEYS];
|
||
bool existing_isnull[INDEX_MAX_KEYS];
|
||
char* error_new = NULL;
|
||
char* error_existing = NULL;
|
||
|
||
/* 忽略我们要检查的元组的条目。 */
|
||
ItemPointer item = TUPLE_IS_UHEAP_TUPLE(tup) ? &((UHeapTuple)tup)->ctid : &((HeapTuple)tup)->t_self;
|
||
if (ItemPointerIsValid(tupleid) && ItemPointerEquals(tupleid, item)) {
|
||
if (found_self) /* should not happen */
|
||
ereport(ERROR,
|
||
(errcode(ERRCODE_FETCH_DATA_FAILED),
|
||
errmsg("found self tuple multiple times in index \"%s\"", RelationGetRelationName(index))));
|
||
found_self = true;
|
||
continue;
|
||
}
|
||
|
||
/* 从现有元组中提取索引列的值和isnull标志。 */
|
||
|
||
(void)ExecStoreTuple(tup, existing_slot, InvalidBuffer, false);
|
||
FormIndexDatum(indexInfo, existing_slot, estate, existing_values, existing_isnull);
|
||
|
||
bool is_scan = index_scan_need_recheck(index_scan) &&
|
||
!index_recheck_constraint(index, constr_procs, existing_values, existing_isnull, values);
|
||
/* 如果有信息损失的索引扫描,必须重新检查条件 */
|
||
if (is_scan) {
|
||
/* 元组实际上不匹配,因此没有冲突 */
|
||
continue;
|
||
}
|
||
|
||
/*
|
||
* 此时我们要么有一个冲突,要么有一个潜在冲突。
|
||
* 如果一个正在进行的事务正在影响此元组的可见性,我们需要等待它完成然后重新检查(除非调用者要求不要这样做)。
|
||
* 为了简化起见,我们通过重新启动整个扫描来进行重新检查 --- 这种情况可能不经常发生,不值得更加努力,
|
||
* 无论如何,我们都不想在等待期间持有任何索引内部锁。
|
||
*/
|
||
xwait = TransactionIdIsValid(DirtySnapshot.xmin) ? DirtySnapshot.xmin : DirtySnapshot.xmax;
|
||
|
||
if (TransactionIdIsValid(xwait) && waitMode == CHECK_WAIT) {
|
||
scan_handler_idx_endscan(index_scan);
|
||
|
||
/*
|
||
* 对于投机插入(INSERT ON DUPLICATE KEY UPDATE),
|
||
* 我们只需要等待投机令牌锁被释放,
|
||
* 这发生在其他正在运行的事务通过投机插入元组并完成插入(要么完成了,要么中止了)时。
|
||
*/
|
||
|
||
XactLockTableWait(xwait);
|
||
goto retry;
|
||
}
|
||
|
||
/*
|
||
* 确定扫描的元组的索引列是否与要插入的元组相同。
|
||
* 如果不同,表示该项目指向的元组已被其他事务修改。
|
||
* 重新检查是否存在冲突。
|
||
*/
|
||
|
||
for (int i=0; i < indnkeyatts; i++) {
|
||
if (existing_isnull[i] != isnull[i]) {
|
||
conflict = false;
|
||
scan_handler_idx_endscan(index_scan);
|
||
goto retry;
|
||
}
|
||
if (!existing_isnull[i] &&
|
||
!DatumGetBool(FunctionCall2Coll(&scankeys[i].sk_func, scankeys[i].sk_collation,
|
||
existing_values[i], values[i]))) {
|
||
conflict = false;
|
||
scan_handler_idx_endscan(index_scan);
|
||
goto retry;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 我们有一个明确的冲突(或潜在的冲突,但调用者不想等待)。
|
||
* 如果我们不应该引发错误,只需返回给调用者。
|
||
*/
|
||
if (errorOK) {
|
||
conflict = true;
|
||
if (conflictInfo != NULL) {
|
||
conflictInfo->conflictTid = *item;
|
||
conflictInfo->conflictXid = tableam_tops_get_conflictXid(heap, tup);
|
||
}
|
||
*conflictPartOid = TUPLE_IS_UHEAP_TUPLE(tup) ? ((UHeapTuple)tup)->table_oid : ((HeapTuple)tup)->t_tableOid;
|
||
*conflictBucketid = TUPLE_IS_UHEAP_TUPLE(tup) ? ((UHeapTuple)tup)->t_bucketId : ((HeapTuple)tup)->t_bucketId;
|
||
break;
|
||
}
|
||
|
||
/*
|
||
* 我们有一个明确的冲突(或潜在的冲突,但调用者不想等待)。
|
||
* 如果我们不应该引发错误,只需返回给调用者。
|
||
*/
|
||
error_new = BuildIndexValueDescription(index, values, isnull);
|
||
error_existing = BuildIndexValueDescription(index, existing_values, existing_isnull);
|
||
newIndex ?
|
||
ereport(ERROR,
|
||
(errcode(ERRCODE_EXCLUSION_VIOLATION),
|
||
errmsg("could not create exclusion constraint \"%s\" when trying to build a new index",
|
||
RelationGetRelationName(index)),
|
||
(error_new && error_existing) ? errdetail("Key %s conflicts with key %s.", error_new, error_existing)
|
||
: errdetail("Key conflicts exist."))) :
|
||
ereport(ERROR,
|
||
(errcode(ERRCODE_EXCLUSION_VIOLATION),
|
||
errmsg(
|
||
"conflicting key value violates exclusion constraint \"%s\"", RelationGetRelationName(index)),
|
||
(error_new && error_existing)
|
||
? errdetail("Key %s conflicts with existing key %s.", error_new, error_existing)
|
||
: errdetail("Key conflicts with existing key.")));
|
||
}
|
||
|
||
scan_handler_idx_endscan(index_scan);
|
||
|
||
/*
|
||
* 通常情况下,到了这一点,搜索应该已经找到了最初插入的元组(如果有的话),
|
||
* 除非我们因为冲突而提前退出了循环。然而,也有可能为排除约束定义这样的情况,
|
||
* 其中这个条件不成立 --- 例如,如果操作符是<>。
|
||
* 因此,如果found_self仍然为false,我们将不再抱怨。
|
||
*/
|
||
|
||
econtext->ecxt_scantuple = save_scantuple;
|
||
|
||
ExecDropSingleTupleTableSlot(existing_slot);
|
||
|
||
return !conflict;
|
||
}
|
||
|
||
/*
|
||
* 检查现有元组的索引值,看它是否与 new_values 真正匹配排除条件。
|
||
* 如果有冲突,返回true。
|
||
*/
|
||
|
||
static bool index_recheck_constraint(
|
||
Relation index, Oid* constr_procs, Datum* existing_values, const bool* existing_isnull, Datum* new_values)
|
||
{
|
||
int indnkeyatts = IndexRelationGetNumberOfKeyAttributes(index);
|
||
int i;
|
||
|
||
for (i = 0; i < indnkeyatts; i++) {
|
||
/* Assume the exclusion operators are strict */
|
||
if (existing_isnull[i]) {
|
||
return false;
|
||
}
|
||
|
||
if (!DatumGetBool(
|
||
OidFunctionCall2Coll(constr_procs[i], index->rd_indcollation[i], existing_values[i], new_values[i]))) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
* UpdateChangedParamSet
|
||
* 将已更改的参数添加到计划节点的 chgParam 集合中
|
||
*/
|
||
|
||
void UpdateChangedParamSet(PlanState* node, Bitmapset* newchg)
|
||
{
|
||
Bitmapset* parmset = NULL;
|
||
|
||
/*
|
||
* 计划节点仅依赖于其 allParam 集合中列出的参数。不要将其他任何东西包含在其 chgParam 集合中。
|
||
*/
|
||
|
||
parmset = bms_intersect(node->plan->allParam, newchg);
|
||
|
||
/*
|
||
* 如果实际上没有成员,则保持 node->chgParam == NULL;这允许在执行节点文件中进行最简单的测试。
|
||
*/
|
||
|
||
if (!bms_is_empty(parmset))
|
||
node->chgParam = bms_join(node->chgParam, parmset);
|
||
else
|
||
bms_free_ext(parmset);
|
||
}
|
||
|
||
/*
|
||
* 在 ExprContext 中注册一个关闭回调。
|
||
*
|
||
* 关闭回调将在删除或重新扫描 ExprContext 时被调用(按注册的相反顺序)。
|
||
* 这为在上下文中调用的函数提供了一个挂钩,用于进行所需的任何清理工作,尤其适用于返回集合的函数。
|
||
* 请注意,如果由错误中止执行,则不会调用回调。
|
||
*/
|
||
|
||
void RegisterExprContextCallback(ExprContext* econtext, ExprContextCallbackFunction function, Datum arg)
|
||
{
|
||
ExprContext_CB* ecxt_callback = NULL;
|
||
|
||
/* 将信息保存在适当的内存上下文中 */
|
||
ecxt_callback = (ExprContext_CB*)MemoryContextAlloc(econtext->ecxt_per_query_memory, sizeof(ExprContext_CB));
|
||
|
||
ecxt_callback->function = function;
|
||
ecxt_callback->arg = arg;
|
||
ecxt_callback->resowner = t_thrd.utils_cxt.CurrentResourceOwner;
|
||
|
||
/* 将信息保存在适当的内存上下文中 */
|
||
ecxt_callback->next = econtext->ecxt_callbacks;
|
||
econtext->ecxt_callbacks = ecxt_callback;
|
||
}
|
||
|
||
/*
|
||
* 在ExprContext中取消注册一个关闭回调函数。
|
||
*
|
||
* 任何匹配函数和参数的列表条目都将被删除。
|
||
* 如果不再需要调用回调函数,则可以使用此函数。
|
||
*/
|
||
|
||
void UnregisterExprContextCallback(ExprContext* econtext, ExprContextCallbackFunction function, Datum arg)
|
||
{
|
||
ExprContext_CB** prev_callback = NULL;
|
||
ExprContext_CB* ecxt_callback = NULL;
|
||
|
||
prev_callback = &econtext->ecxt_callbacks;
|
||
|
||
while ((ecxt_callback = *prev_callback) != NULL) {
|
||
if (ecxt_callback->function == function && ecxt_callback->arg == arg) {
|
||
*prev_callback = ecxt_callback->next;
|
||
pfree_ext(ecxt_callback);
|
||
} else
|
||
prev_callback = &ecxt_callback->next;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 调用在ExprContext中注册的所有关闭回调函数。
|
||
*
|
||
* 回调函数列表将被清空(如果这仅是重新扫描重置,而不是删除ExprContext,则这很重要)。
|
||
*
|
||
* 如果isCommit为false,则只清理回调列表但不调用回调函数。
|
||
* (请参阅FreeExprContext的注释。)
|
||
*/
|
||
|
||
static void ShutdownExprContext(ExprContext* econtext, bool isCommit)
|
||
{
|
||
ExprContext_CB* ecxt_callback = NULL;
|
||
MemoryContext oldcontext;
|
||
|
||
/* Fast path in normal case where there's nothing to do. */
|
||
if (econtext->ecxt_callbacks == NULL)
|
||
return;
|
||
|
||
/*
|
||
* 在econtext的每个元组上下文中调用回调函数。这可以确保它们可能泄漏的任何内存都将被清理。
|
||
*/
|
||
|
||
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
|
||
|
||
/*
|
||
* 按照注册顺序的相反顺序调用每个回调函数。
|
||
*/
|
||
|
||
ResourceOwner oldOwner = t_thrd.utils_cxt.CurrentResourceOwner;
|
||
PG_TRY();
|
||
{
|
||
while ((ecxt_callback = econtext->ecxt_callbacks) != NULL) {
|
||
econtext->ecxt_callbacks = ecxt_callback->next;
|
||
if (isCommit) {
|
||
t_thrd.utils_cxt.CurrentResourceOwner = ecxt_callback->resowner;
|
||
(*ecxt_callback->function)(ecxt_callback->arg);
|
||
}
|
||
pfree_ext(ecxt_callback);
|
||
}
|
||
}
|
||
PG_CATCH();
|
||
{
|
||
t_thrd.utils_cxt.CurrentResourceOwner = oldOwner;
|
||
PG_RE_THROW();
|
||
}
|
||
PG_END_TRY();
|
||
t_thrd.utils_cxt.CurrentResourceOwner = oldOwner;
|
||
|
||
MemoryContextSwitchTo(oldcontext);
|
||
}
|
||
|
||
/*
|
||
* PthreadMutexLock - 尝试获取或等待一个pthread互斥锁
|
||
*
|
||
* 此函数尝试获取一个pthread互斥锁。如果获取成功,函数返回0,否则返回错误码。
|
||
*
|
||
* 参数:
|
||
* - owner: 资源拥有者,表示该互斥锁受此资源拥有者的管理。可以为NULL。
|
||
* - mutex: 要获取的pthread互斥锁。
|
||
* - trace: 是否启用跟踪标志,用于记录互斥锁的使用情况。
|
||
*
|
||
* 注意:
|
||
* - 此函数在尝试获取互斥锁之前会禁用中断,以避免竞态条件。
|
||
* - 如果指定了资源拥有者(owner非NULL),则函数将确保该资源拥有者已准备好用于存储pthread互斥锁的信息。
|
||
* - 如果获取互斥锁成功且启用了跟踪标志,函数将记录该互斥锁的使用情况。
|
||
* - 最后,函数会恢复中断状态,并返回获取互斥锁的结果(0表示成功,否则表示失败)。
|
||
*/
|
||
|
||
int PthreadMutexLock(ResourceOwner owner, pthread_mutex_t* mutex, bool trace)
|
||
{
|
||
HOLD_INTERRUPTS();
|
||
if (owner)
|
||
ResourceOwnerEnlargePthreadMutex(owner);
|
||
|
||
int ret = pthread_mutex_lock(mutex);
|
||
if (ret == 0 && trace && owner) {
|
||
ResourceOwnerRememberPthreadMutex(owner, mutex);
|
||
}
|
||
RESUME_INTERRUPTS();
|
||
return ret;
|
||
}
|
||
/*
|
||
* PthreadMutexTryLock - 尝试非阻塞获取pthread互斥锁
|
||
*
|
||
* 此函数尝试非阻塞地获取一个pthread互斥锁。如果获取成功,函数返回0,否则返回错误码。
|
||
*
|
||
* 参数:
|
||
* - owner: 资源拥有者,表示该互斥锁受此资源拥有者的管理。可以为NULL。
|
||
* - mutex: 要获取的pthread互斥锁。
|
||
* - trace: 是否启用跟踪标志,用于记录互斥锁的使用情况。
|
||
*
|
||
* 注意:
|
||
* - 此函数在尝试获取互斥锁之前会禁用中断,以避免竞态条件。
|
||
* - 如果指定了资源拥有者(owner非NULL),则函数将确保该资源拥有者已准备好用于存储pthread互斥锁的信息。
|
||
* - 如果非阻塞获取互斥锁成功且启用了跟踪标志,函数将记录该互斥锁的使用情况。
|
||
* - 最后,函数会恢复中断状态,并返回获取互斥锁的结果(0表示成功,否则表示失败)。
|
||
*/
|
||
|
||
int PthreadMutexTryLock(ResourceOwner owner, pthread_mutex_t* mutex, bool trace)
|
||
{
|
||
HOLD_INTERRUPTS();
|
||
if (owner)
|
||
ResourceOwnerEnlargePthreadMutex(owner);
|
||
|
||
int ret = pthread_mutex_trylock(mutex);
|
||
if (ret == 0 && trace && owner) {
|
||
ResourceOwnerRememberPthreadMutex(owner, mutex);
|
||
}
|
||
RESUME_INTERRUPTS();
|
||
return ret;
|
||
}
|
||
|
||
//释放一个 pthread 互斥锁(mutex)
|
||
int PthreadMutexUnlock(ResourceOwner owner, pthread_mutex_t* mutex, bool trace)
|
||
{
|
||
HOLD_INTERRUPTS();
|
||
int ret = pthread_mutex_unlock(mutex);
|
||
if (ret == 0 && trace && owner)
|
||
ResourceOwnerForgetPthreadMutex(owner, mutex);
|
||
RESUME_INTERRUPTS();
|
||
|
||
return ret;
|
||
}
|
||
//用于尝试以读取锁(read lock)的方式获取一个 pthread 读写锁(rwlock)
|
||
int PthreadRWlockTryRdlock(ResourceOwner owner, pthread_rwlock_t* rwlock)
|
||
{
|
||
if (owner) {
|
||
ResourceOwnerEnlargePthreadRWlock(owner);
|
||
}
|
||
bool ret;
|
||
HOLD_INTERRUPTS();
|
||
ret = pthread_rwlock_tryrdlock(rwlock);
|
||
if (ret == 0) {
|
||
if (owner) {
|
||
ResourceOwnerRememberPthreadRWlock(owner, rwlock);
|
||
} else {
|
||
START_CRIT_SECTION();
|
||
}
|
||
}
|
||
RESUME_INTERRUPTS();
|
||
return ret;
|
||
}
|
||
//用于以读取锁(read lock)的方式获取一个 pthread 读写锁(rwlock)
|
||
void PthreadRWlockRdlock(ResourceOwner owner, pthread_rwlock_t* rwlock)
|
||
{
|
||
if (owner) {
|
||
ResourceOwnerEnlargePthreadRWlock(owner);
|
||
}
|
||
HOLD_INTERRUPTS();
|
||
int ret = pthread_rwlock_rdlock(rwlock);
|
||
Assert(ret == 0);
|
||
if (ret != 0) {
|
||
ereport(ERROR,
|
||
(errcode(ERRCODE_LOCK_NOT_AVAILABLE), errmsg("aquire rdlock failed")));
|
||
}
|
||
if (owner) {
|
||
ResourceOwnerRememberPthreadRWlock(owner, rwlock);
|
||
} else {
|
||
START_CRIT_SECTION();
|
||
}
|
||
RESUME_INTERRUPTS();
|
||
}
|
||
|
||
//用于以尝试写入锁(try write lock)的方式获取一个 pthread 读写锁(rwlock)
|
||
int PthreadRWlockTryWrlock(ResourceOwner owner, pthread_rwlock_t* rwlock)
|
||
{
|
||
if (owner) {
|
||
ResourceOwnerEnlargePthreadRWlock(owner);
|
||
}
|
||
HOLD_INTERRUPTS();
|
||
int ret = pthread_rwlock_trywrlock(rwlock);
|
||
if (ret == 0) {
|
||
if (owner) {
|
||
ResourceOwnerRememberPthreadRWlock(owner, rwlock);
|
||
} else {
|
||
START_CRIT_SECTION();
|
||
}
|
||
}
|
||
RESUME_INTERRUPTS();
|
||
return ret;
|
||
}
|
||
//用于以阻塞方式获取一个 pthread 读写锁(rwlock)的写入锁(write lock)
|
||
void PthreadRWlockWrlock(ResourceOwner owner, pthread_rwlock_t* rwlock)
|
||
{
|
||
if (owner) {
|
||
ResourceOwnerEnlargePthreadRWlock(owner);
|
||
}
|
||
HOLD_INTERRUPTS();
|
||
int ret = pthread_rwlock_wrlock(rwlock);
|
||
Assert(ret == 0);
|
||
if (ret != 0) {
|
||
ereport(ERROR,
|
||
(errcode(ERRCODE_LOCK_NOT_AVAILABLE), errmsg("aquire wrlock failed")));
|
||
}
|
||
if (owner) {
|
||
ResourceOwnerRememberPthreadRWlock(owner, rwlock);
|
||
} else {
|
||
START_CRIT_SECTION();
|
||
}
|
||
RESUME_INTERRUPTS();
|
||
}
|
||
//用于释放 pthread 读写锁(rwlock)
|
||
void PthreadRWlockUnlock(ResourceOwner owner, pthread_rwlock_t* rwlock)
|
||
{
|
||
HOLD_INTERRUPTS();
|
||
int ret = pthread_rwlock_unlock(rwlock);
|
||
Assert(ret == 0);
|
||
if (ret != 0) {
|
||
ereport(ERROR,
|
||
(errcode(ERRCODE_LOCK_NOT_AVAILABLE), errmsg("release rwlock failed")));
|
||
}
|
||
if (owner) {
|
||
ResourceOwnerForgetPthreadRWlock(owner, rwlock);
|
||
} else {
|
||
END_CRIT_SECTION();
|
||
}
|
||
RESUME_INTERRUPTS();
|
||
}
|
||
//用于初始化 pthread 读写锁(rwlock)
|
||
void PthreadRwLockInit(pthread_rwlock_t* rwlock, pthread_rwlockattr_t *attr)
|
||
{
|
||
int ret = pthread_rwlock_init(rwlock, attr);
|
||
Assert(ret == 0);
|
||
if (ret != 0) {
|
||
ereport(ERROR,
|
||
(errcode(ERRCODE_INITIALIZE_FAILED), errmsg("init rwlock failed")));
|
||
}
|
||
} |