From 14d951ca644860eec6d72ac03e3a95b12373938b Mon Sep 17 00:00:00 2001 From: amitlan Date: Wed, 22 Dec 2021 16:55:17 +0900 Subject: [PATCH v7 4/4] Optimize AcquireExecutorLocks() to skip pruned partitions Instead of locking all relations listed in the range table in the cases where the PlannedStmt indicates that some nodes in the plan tree can do partition pruning without depending on execution having started (so called "initial" pruning), AcquireExecutorLocks() now calls the new executor function ExecutorGetLockRels() which returns a set of relations (their RT indexes) to be locked not including those scanned by the subplans that pruned. The result of pruning done this way must be remembered and reused during actual execution of the plan, which is done by creating a PlanInitPruningOutput nodes for for each plan node that undergoes pruning and a set of those for the whole plan tree are added to ExecLockRelsInfo which also stores the bitmapset of RT indexes of relations that are actually locked by AcquireExecutorLocks(). ExecLockRelsInfos are passed down the executor alongside the PlannedStmts. This arrangement ensures that the executor doesn't accidentally try to process a plan tree subnodes that has been deemed pruned by AcquireExecutorLocks(). --- src/backend/commands/copyto.c | 2 +- src/backend/commands/createas.c | 2 +- src/backend/commands/explain.c | 7 +- src/backend/commands/extension.c | 13 +- src/backend/commands/matview.c | 2 +- src/backend/commands/portalcmds.c | 1 + src/backend/commands/prepare.c | 17 +- src/backend/executor/README | 24 +++ src/backend/executor/execMain.c | 202 ++++++++++++++++++++ src/backend/executor/execParallel.c | 26 ++- src/backend/executor/execPartition.c | 224 ++++++++++++++++++---- src/backend/executor/execUtils.c | 8 + src/backend/executor/functions.c | 2 +- src/backend/executor/nodeAppend.c | 52 ++++- src/backend/executor/nodeMergeAppend.c | 52 ++++- src/backend/executor/nodeModifyTable.c | 25 +++ src/backend/executor/spi.c | 14 +- src/backend/nodes/copyfuncs.c | 49 ++++- src/backend/nodes/outfuncs.c | 39 ++++ src/backend/nodes/readfuncs.c | 37 ++++ src/backend/optimizer/plan/planner.c | 2 + src/backend/optimizer/plan/setrefs.c | 6 + src/backend/partitioning/partprune.c | 37 +++- src/backend/tcop/postgres.c | 15 +- src/backend/tcop/pquery.c | 21 ++- src/backend/utils/cache/plancache.c | 252 ++++++++++++++++++++++--- src/backend/utils/mmgr/portalmem.c | 2 + src/include/commands/explain.h | 3 +- src/include/executor/execPartition.h | 2 + src/include/executor/execdesc.h | 2 + src/include/executor/executor.h | 2 + src/include/executor/nodeAppend.h | 1 + src/include/executor/nodeMergeAppend.h | 1 + src/include/executor/nodeModifyTable.h | 1 + src/include/nodes/execnodes.h | 96 ++++++++++ src/include/nodes/nodes.h | 5 + src/include/nodes/pathnodes.h | 4 + src/include/nodes/plannodes.h | 15 ++ src/include/tcop/tcopprot.h | 2 +- src/include/utils/plancache.h | 6 + src/include/utils/portal.h | 5 + 41 files changed, 1174 insertions(+), 104 deletions(-) diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c index 55c38b04c4..d403eb2309 100644 --- a/src/backend/commands/copyto.c +++ b/src/backend/commands/copyto.c @@ -542,7 +542,7 @@ BeginCopyTo(ParseState *pstate, ((DR_copy *) dest)->cstate = cstate; /* Create a QueryDesc requesting no output */ - cstate->queryDesc = CreateQueryDesc(plan, pstate->p_sourcetext, + cstate->queryDesc = CreateQueryDesc(plan, NULL, pstate->p_sourcetext, GetActiveSnapshot(), InvalidSnapshot, dest, NULL, NULL, 0); diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 9abbb6b555..f6607f2454 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -325,7 +325,7 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt, UpdateActiveSnapshotCommandId(); /* Create a QueryDesc, redirecting output to our tuple receiver */ - queryDesc = CreateQueryDesc(plan, pstate->p_sourcetext, + queryDesc = CreateQueryDesc(plan, NULL, pstate->p_sourcetext, GetActiveSnapshot(), InvalidSnapshot, dest, params, queryEnv, 0); diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 9f632285b6..1f1a44b9bb 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -407,7 +407,7 @@ ExplainOneQuery(Query *query, int cursorOptions, } /* run it (if needed) and produce output */ - ExplainOnePlan(plan, into, es, queryString, params, queryEnv, + ExplainOnePlan(plan, NULL, into, es, queryString, params, queryEnv, &planduration, (es->buffers ? &bufusage : NULL)); } } @@ -515,7 +515,8 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es, * to call it. */ void -ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, +ExplainOnePlan(PlannedStmt *plannedstmt, ExecLockRelsInfo *execlockrelsinfo, + IntoClause *into, ExplainState *es, const char *queryString, ParamListInfo params, QueryEnvironment *queryEnv, const instr_time *planduration, const BufferUsage *bufusage) @@ -563,7 +564,7 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, dest = None_Receiver; /* Create a QueryDesc for the query */ - queryDesc = CreateQueryDesc(plannedstmt, queryString, + queryDesc = CreateQueryDesc(plannedstmt, execlockrelsinfo, queryString, GetActiveSnapshot(), InvalidSnapshot, dest, params, queryEnv, instrument_option); diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c index 1013790dbb..008b8ce0e9 100644 --- a/src/backend/commands/extension.c +++ b/src/backend/commands/extension.c @@ -741,8 +741,10 @@ execute_sql_string(const char *sql) RawStmt *parsetree = lfirst_node(RawStmt, lc1); MemoryContext per_parsetree_context, oldcontext; - List *stmt_list; - ListCell *lc2; + List *stmt_list, + *execlockrelsinfo_list; + ListCell *lc2, + *lc3; /* * We do the work for each parsetree in a short-lived context, to @@ -762,11 +764,13 @@ execute_sql_string(const char *sql) NULL, 0, NULL); - stmt_list = pg_plan_queries(stmt_list, sql, CURSOR_OPT_PARALLEL_OK, NULL); + stmt_list = pg_plan_queries(stmt_list, sql, CURSOR_OPT_PARALLEL_OK, NULL, + &execlockrelsinfo_list); - foreach(lc2, stmt_list) + forboth(lc2, stmt_list, lc3, execlockrelsinfo_list) { PlannedStmt *stmt = lfirst_node(PlannedStmt, lc2); + ExecLockRelsInfo *execlockrelsinfo = lfirst_node(ExecLockRelsInfo, lc3); CommandCounterIncrement(); @@ -777,6 +781,7 @@ execute_sql_string(const char *sql) QueryDesc *qdesc; qdesc = CreateQueryDesc(stmt, + execlockrelsinfo, sql, GetActiveSnapshot(), NULL, dest, NULL, NULL, 0); diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 05e7b60059..4ef44aaf23 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -416,7 +416,7 @@ refresh_matview_datafill(DestReceiver *dest, Query *query, UpdateActiveSnapshotCommandId(); /* Create a QueryDesc, redirecting output to our tuple receiver */ - queryDesc = CreateQueryDesc(plan, queryString, + queryDesc = CreateQueryDesc(plan, NULL, queryString, GetActiveSnapshot(), InvalidSnapshot, dest, NULL, NULL, 0); diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c index 9902c5c566..85e73ddded 100644 --- a/src/backend/commands/portalcmds.c +++ b/src/backend/commands/portalcmds.c @@ -107,6 +107,7 @@ PerformCursorOpen(ParseState *pstate, DeclareCursorStmt *cstmt, ParamListInfo pa queryString, CMDTAG_SELECT, /* cursor's query is always a SELECT */ list_make1(plan), + list_make1(NULL), /* no ExecLockRelsInfo to pass */ NULL); /*---------- diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index 80738547ed..bbbf8bbcbd 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -155,6 +155,7 @@ ExecuteQuery(ParseState *pstate, PreparedStatement *entry; CachedPlan *cplan; List *plan_list; + List *plan_execlockrelsinfo_list; ParamListInfo paramLI = NULL; EState *estate = NULL; Portal portal; @@ -195,6 +196,7 @@ ExecuteQuery(ParseState *pstate, /* Replan if needed, and increment plan refcount for portal */ cplan = GetCachedPlan(entry->plansource, paramLI, NULL, NULL); plan_list = cplan->stmt_list; + plan_execlockrelsinfo_list = cplan->execlockrelsinfo_list; /* * DO NOT add any logic that could possibly throw an error between @@ -204,7 +206,7 @@ ExecuteQuery(ParseState *pstate, NULL, query_string, entry->plansource->commandTag, - plan_list, + plan_list, plan_execlockrelsinfo_list, cplan); /* @@ -576,7 +578,9 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es, const char *query_string; CachedPlan *cplan; List *plan_list; - ListCell *p; + List *plan_execlockrelsinfo_list; + ListCell *p, + *pe; ParamListInfo paramLI = NULL; EState *estate = NULL; instr_time planstart; @@ -632,15 +636,18 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es, } plan_list = cplan->stmt_list; + plan_execlockrelsinfo_list = cplan->execlockrelsinfo_list; /* Explain each query */ - foreach(p, plan_list) + forboth(p, plan_list, pe, plan_execlockrelsinfo_list) { PlannedStmt *pstmt = lfirst_node(PlannedStmt, p); + ExecLockRelsInfo *execlockrelsinfo = lfirst_node(ExecLockRelsInfo, pe); if (pstmt->commandType != CMD_UTILITY) - ExplainOnePlan(pstmt, into, es, query_string, paramLI, queryEnv, - &planduration, (es->buffers ? &bufusage : NULL)); + ExplainOnePlan(pstmt, execlockrelsinfo, into, es, query_string, + paramLI, queryEnv, &planduration, + (es->buffers ? &bufusage : NULL)); else ExplainOneUtility(pstmt->utilityStmt, into, es, query_string, paramLI, queryEnv); diff --git a/src/backend/executor/README b/src/backend/executor/README index bf5e70860d..9720d0ac2c 100644 --- a/src/backend/executor/README +++ b/src/backend/executor/README @@ -65,6 +65,27 @@ found there. This currently only occurs for Append and MergeAppend nodes. In this case the non-required subplans are ignored and the executor state's subnode array will become out of sequence to the plan's subplan list. +Actually, the so-called execution time pruning may also occur even before the +execution has started. One case where that occurs is when a cached generic +plan is being validated for execution by plancache.c: GetCachedPlan(), which +proceeds by locking all the relations that will be scanned by that plan. If +the generic plan has nodes that contain so-called initial pruning steps (a +subset of execution pruning steps that do not depend on full-fledged execution +having started), they are performed at this point to figure out the minimal +set of child subplans that satisfy those pruning instructions and the result +of performing that pruning is saved in a data structure that gets passed to +the executor alongside the plan tree. Relations scanned by only those +surviving subplans are then locked while those scanned by the pruned subplans +are not, even though the pruned subplans themselves are not removed from the +plan tree. So, it is imperative that the executor and any third party code +invoked by it that gets passed the plan tree look at the initial pruning result +made available via the aforementioned data structure to determine whether or +not a particular subplan is valid. (The data structure basically consists of +an array of PlanInitPruningOutput nodes containing one element for each node +of the plan tree indexable using plan_node_id of the individual plan nodes, +where each node contains a bitmapset of indexes of unpruned child subplans of +a given node.) + Each Plan node may have expression trees associated with it, to represent its target list, qualification conditions, etc. These trees are also read-only to the executor, but the executor state for expression evaluation @@ -247,6 +268,9 @@ Query Processing Control Flow This is a sketch of control flow for full query processing: + [ ExecutorGetLockRels ] --- an optional step to walk over the plan tree + to produce an ExecLockRelsInfo to be passed to CreateQueryDesc + CreateQueryDesc ExecutorStart diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 473d2e00a2..1ddd1dfb83 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -49,11 +49,15 @@ #include "commands/matview.h" #include "commands/trigger.h" #include "executor/execdebug.h" +#include "executor/nodeAppend.h" +#include "executor/nodeMergeAppend.h" +#include "executor/nodeModifyTable.h" #include "executor/nodeSubplan.h" #include "foreign/fdwapi.h" #include "jit/jit.h" #include "mb/pg_wchar.h" #include "miscadmin.h" +#include "nodes/nodeFuncs.h" #include "parser/parsetree.h" #include "storage/bufmgr.h" #include "storage/lmgr.h" @@ -101,9 +105,205 @@ static char *ExecBuildSlotValueDescription(Oid reloid, Bitmapset *modifiedCols, int maxfieldlen); static void EvalPlanQualStart(EPQState *epqstate, Plan *planTree); +static bool ExecGetScanLockRels(Scan *scan, ExecGetLockRelsContext *context); /* end of local decls */ +/* ---------------------------------------------------------------- + * ExecutorGetLockRels + * + * Figure out the minimal set of relations to lock to be able to safely + * execute a given plan + * + * This ignores the relations scanned by child subplans that are pruned away + * after performing initial pruning steps present in the plan using the + * provided set of EXTERN parameters. + * + * Along with the set of RT indexes of relations that must be locked, the + * returned struct also contains an array of PlanInitPruningOutput nodes each + * of which contains the result of initial pruning for a given plan node, which + * is basically a bitmapset of the indexes of surviving child subplans. Each + * plan node in the tree that undergoes pruning will have an element in the + * array. + * + * Note that while relations scanned by the subplans that are pruned will not + * be locked, the subplans themselves are left as-is in the plan tree, assuming + * anything that reads the plan tree during execution knows to ignore them by + * looking at the PlanInitPruningOutput's list of valid subplans. + * + * Partitioned tables mentioned in PartitionedRelPruneInfo nodes that drive + * the pruning will be locked before doing the pruning and also added to the + * the returned set. + */ +ExecLockRelsInfo * +ExecutorGetLockRels(PlannedStmt *plannedstmt, ParamListInfo params) +{ + int numPlanNodes = plannedstmt->numPlanNodes; + ExecGetLockRelsContext context; + ExecLockRelsInfo *result; + ListCell *lc; + + /* Only get here if there is any pruning to do. */ + Assert(plannedstmt->containsInitialPruning); + + context.stmt = plannedstmt; + context.params = params; + + /* + * Go walk all the plan tree(s) present in the PlannedStmt, filling + * context.lockrels with only the relations from plan nodes that + * survive initial pruning and also the tables mentioned in + * partitioned_rels sets found in the plan. + */ + context.lockrels = NULL; + context.initPruningOutputs = NIL; + context.ipoIndexes = palloc0(sizeof(int) * numPlanNodes); + + /* All the subplans. */ + foreach(lc, plannedstmt->subplans) + { + Plan *subplan = lfirst(lc); + + (void) ExecGetLockRels(subplan, &context); + } + + /* And the main tree. */ + (void) ExecGetLockRels(plannedstmt->planTree, &context); + + /* + * Also be sure to lock partitioned relations from any [Merge]Append nodes + * that were originally present but were ultimately left out from the plan + * due to being deemed no-op nodes. + */ + context.lockrels = bms_add_members(context.lockrels, + plannedstmt->elidedAppendPartedRels); + + result = makeNode(ExecLockRelsInfo); + result->lockrels = context.lockrels; + result->numPlanNodes = numPlanNodes; + result->initPruningOutputs = context.initPruningOutputs; + result->ipoIndexes = context.ipoIndexes; + + return result; +} + +/* ------------------------------------------------------------------------ + * ExecGetLockRels + * Adds all the relations that will be scanned by 'node' and its child + * plans to context->lockrels after taking into the account the effect + * of performing initial pruning if any + * + * context->stmt gives the PlannedStmt being inspected to access the plan's + * range table if needed and context->params the set of EXTERN parameters + * available to evaluate pruning parameters. + * + * If initial pruning is done, a PlanInitPruningOutput node containing the + * result of pruning will be stored in context->initPruningOutputs that will + * be made available to the executor to reuse. + * ------------------------------------------------------------------------ + */ +bool +ExecGetLockRels(Plan *node, ExecGetLockRelsContext *context) +{ + /* Do nothing when we get to the end of a leaf on tree. */ + if (node == NULL) + return true; + + /* Make sure there's enough stack available. */ + check_stack_depth(); + + switch (nodeTag(node)) + { + /* Currently, only these two nodes have prunable child subplans. */ + case T_Append: + if (ExecGetAppendLockRels((Append *) node, context)) + return true; + break; + case T_MergeAppend: + if (ExecGetMergeAppendLockRels((MergeAppend *) node, + context)) + return true; + break; + + /* + * And these manipulate relations that must be added context->lockrels. + */ + case T_SeqScan: + case T_SampleScan: + case T_IndexScan: + case T_IndexOnlyScan: + case T_BitmapIndexScan: + case T_BitmapHeapScan: + case T_TidScan: + case T_TidRangeScan: + case T_ForeignScan: + case T_SubqueryScan: + case T_CustomScan: + if (ExecGetScanLockRels((Scan *) node, context)) + return true; + break; + case T_ModifyTable: + if (ExecGetModifyTableLockRels((ModifyTable *) node, context)) + return true; + /* plan_tree_walker() will visit the subplan (outerNode) */ + break; + + default: + break; + } + + /* Recurse to subnodes. */ + return plan_tree_walker(node, ExecGetLockRels, (void *) context); +} + +/* + * ExecGetScanLockRels + * Do ExecGetLockRels()'s work for a leaf Scan node + */ +static bool +ExecGetScanLockRels(Scan *scan, ExecGetLockRelsContext *context) +{ + switch (nodeTag(scan)) + { + case T_ForeignScan: + { + ForeignScan *fscan = (ForeignScan *) scan; + + context->lockrels = bms_add_members(context->lockrels, + fscan->fs_relids); + } + break; + + case T_SubqueryScan: + { + SubqueryScan *sscan = (SubqueryScan *) scan; + + (void) ExecGetLockRels((Plan *) sscan->subplan, context); + } + break; + + case T_CustomScan: + { + CustomScan *cscan = (CustomScan *) scan; + ListCell *lc; + + context->lockrels = bms_add_members(context->lockrels, + cscan->custom_relids); + foreach(lc, cscan->custom_plans) + { + (void) ExecGetLockRels((Plan *) lfirst(lc), context); + } + } + break; + + default: + context->lockrels = bms_add_member(context->lockrels, + scan->scanrelid); + break; + } + + return true; +} /* ---------------------------------------------------------------- * ExecutorStart @@ -805,6 +1005,7 @@ InitPlan(QueryDesc *queryDesc, int eflags) { CmdType operation = queryDesc->operation; PlannedStmt *plannedstmt = queryDesc->plannedstmt; + ExecLockRelsInfo *execlockrelsinfo = queryDesc->execlockrelsinfo; Plan *plan = plannedstmt->planTree; List *rangeTable = plannedstmt->rtable; EState *estate = queryDesc->estate; @@ -824,6 +1025,7 @@ InitPlan(QueryDesc *queryDesc, int eflags) ExecInitRangeTable(estate, rangeTable); estate->es_plannedstmt = plannedstmt; + estate->es_execlockrelsinfo = execlockrelsinfo; /* * Next, build the ExecRowMark array from the PlanRowMark(s), if any. diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c index 9a0d5d59ef..fb6dbd298a 100644 --- a/src/backend/executor/execParallel.c +++ b/src/backend/executor/execParallel.c @@ -66,6 +66,7 @@ #define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xE000000000000008) #define PARALLEL_KEY_JIT_INSTRUMENTATION UINT64CONST(0xE000000000000009) #define PARALLEL_KEY_WAL_USAGE UINT64CONST(0xE00000000000000A) +#define PARALLEL_KEY_EXECLOCKRELSINFO UINT64CONST(0xE00000000000000B) #define PARALLEL_TUPLE_QUEUE_SIZE 65536 @@ -182,6 +183,7 @@ ExecSerializePlan(Plan *plan, EState *estate) pstmt->transientPlan = false; pstmt->dependsOnRole = false; pstmt->parallelModeNeeded = false; + pstmt->containsInitialPruning = false; pstmt->planTree = plan; pstmt->rtable = estate->es_range_table; pstmt->resultRelations = NIL; @@ -596,12 +598,15 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, FixedParallelExecutorState *fpes; char *pstmt_data; char *pstmt_space; + char *execlockrelsinfo_data; + char *execlockrelsinfo_space; char *paramlistinfo_space; BufferUsage *bufusage_space; WalUsage *walusage_space; SharedExecutorInstrumentation *instrumentation = NULL; SharedJitInstrumentation *jit_instrumentation = NULL; int pstmt_len; + int execlockrelsinfo_len; int paramlistinfo_len; int instrumentation_len = 0; int jit_instrumentation_len = 0; @@ -630,6 +635,7 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, /* Fix up and serialize plan to be sent to workers. */ pstmt_data = ExecSerializePlan(planstate->plan, estate); + execlockrelsinfo_data = nodeToString(estate->es_execlockrelsinfo); /* Create a parallel context. */ pcxt = CreateParallelContext("postgres", "ParallelQueryMain", nworkers); @@ -656,6 +662,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, shm_toc_estimate_chunk(&pcxt->estimator, pstmt_len); shm_toc_estimate_keys(&pcxt->estimator, 1); + /* Estimate space for serialized ExecLockRelsInfo. */ + execlockrelsinfo_len = strlen(execlockrelsinfo_data) + 1; + shm_toc_estimate_chunk(&pcxt->estimator, execlockrelsinfo_len); + shm_toc_estimate_keys(&pcxt->estimator, 1); + /* Estimate space for serialized ParamListInfo. */ paramlistinfo_len = EstimateParamListSpace(estate->es_param_list_info); shm_toc_estimate_chunk(&pcxt->estimator, paramlistinfo_len); @@ -750,6 +761,12 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, memcpy(pstmt_space, pstmt_data, pstmt_len); shm_toc_insert(pcxt->toc, PARALLEL_KEY_PLANNEDSTMT, pstmt_space); + /* Store serialized ExecLockRelsInfo */ + execlockrelsinfo_space = shm_toc_allocate(pcxt->toc, execlockrelsinfo_len); + memcpy(execlockrelsinfo_space, execlockrelsinfo_data, execlockrelsinfo_len); + shm_toc_insert(pcxt->toc, PARALLEL_KEY_EXECLOCKRELSINFO, + execlockrelsinfo_space); + /* Store serialized ParamListInfo. */ paramlistinfo_space = shm_toc_allocate(pcxt->toc, paramlistinfo_len); shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAMLISTINFO, paramlistinfo_space); @@ -1231,8 +1248,10 @@ ExecParallelGetQueryDesc(shm_toc *toc, DestReceiver *receiver, int instrument_options) { char *pstmtspace; + char *execlockrelsinfospace; char *paramspace; PlannedStmt *pstmt; + ExecLockRelsInfo *execlockrelsinfo; ParamListInfo paramLI; char *queryString; @@ -1243,12 +1262,17 @@ ExecParallelGetQueryDesc(shm_toc *toc, DestReceiver *receiver, pstmtspace = shm_toc_lookup(toc, PARALLEL_KEY_PLANNEDSTMT, false); pstmt = (PlannedStmt *) stringToNode(pstmtspace); + /* Reconstruct leader-supplied ExecLockRelsInfo. */ + execlockrelsinfospace = shm_toc_lookup(toc, PARALLEL_KEY_EXECLOCKRELSINFO, + false); + execlockrelsinfo = (ExecLockRelsInfo *) stringToNode(execlockrelsinfospace); + /* Reconstruct ParamListInfo. */ paramspace = shm_toc_lookup(toc, PARALLEL_KEY_PARAMLISTINFO, false); paramLI = RestoreParamList(¶mspace); /* Create a QueryDesc for the query. */ - return CreateQueryDesc(pstmt, + return CreateQueryDesc(pstmt, execlockrelsinfo, queryString, GetActiveSnapshot(), InvalidSnapshot, receiver, paramLI, NULL, instrument_options); diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c index 7ff5a95f05..fddc97280e 100644 --- a/src/backend/executor/execPartition.c +++ b/src/backend/executor/execPartition.c @@ -24,6 +24,7 @@ #include "mb/pg_wchar.h" #include "miscadmin.h" #include "nodes/makefuncs.h" +#include "parser/parsetree.h" #include "partitioning/partbounds.h" #include "partitioning/partdesc.h" #include "partitioning/partprune.h" @@ -183,8 +184,13 @@ static char *ExecBuildSlotPartitionKeyDescription(Relation rel, int maxfieldlen); static List *adjust_partition_colnos(List *colnos, ResultRelInfo *leaf_part_rri); static PartitionPruneState *ExecCreatePartitionPruneState(PlanState *planstate, - PartitionPruneInfo *partitionpruneinfo); -static Bitmapset *ExecFindInitialMatchingSubPlans(PartitionPruneState *prunestate); + PartitionPruneInfo *partitionpruneinfo, + bool consider_initial_steps, + bool consider_exec_steps, + List *rtable, ExprContext *econtext, + PartitionDirectory partdir); +static Bitmapset *ExecFindInitialMatchingSubPlans(PartitionPruneState *prunestate, + PartitionPruneInfo *pruneinfo); static void ExecInitPruningContext(PartitionPruneContext *context, List *pruning_steps, PartitionDesc partdesc, @@ -1483,8 +1489,9 @@ adjust_partition_colnos(List *colnos, ResultRelInfo *leaf_part_rri) * considered to be a stable expression, it can change value from one plan * node scan to the next during query execution. Stable comparison * expressions that don't involve such Params allow partition pruning to be - * done once during executor startup. Expressions that do involve such Params - * require us to prune separately for each scan of the parent plan node. + * done once during executor startup or even before during ExecutorGetLockRels(). + * Expressions that do involve such Params require us to prune separately for + * each scan of the parent plan node. * * Note that pruning away unneeded subplans during executor startup has the * added benefit of not having to initialize the unneeded subplans at all. @@ -1496,10 +1503,17 @@ adjust_partition_colnos(List *colnos, ResultRelInfo *leaf_part_rri) * Creates the PartitionPruneState required by each of the two pruning * functions. Details stored include how to map the partition index * returned by the partition pruning code into subplan indexes. Also - * determines the set of initially valid subplans by performing initial - * pruning steps, only which need be initialized by the caller such as - * ExecInitAppend. Maps in PartitionPruneState are updated to account - * for initial pruning having eliminated some of the subplans, if any. + * determines the set of initially valid subplans by either looking that + * up in the plan node's PlanInitPruningOutput if one found in + * EState.es_execlockrelinfo or by performing initial pruning steps. + * Only the subplans included in that need be initialized by the caller + * such as ExecInitAppend. Maps in PartitionPruneState are updated to + * account for initial pruning having eliminated some of the subplans, + * if any. + * + * ExecGetLockRelsDoInitialPruning: + * Do initial pruning as part of ExecGetLockRels() on the parent plan + * node * * ExecFindMatchingSubPlans: * Returns indexes of matching subplans after evaluating all available @@ -1514,9 +1528,10 @@ adjust_partition_colnos(List *colnos, ResultRelInfo *leaf_part_rri) * ExecInitPartitionPruning * Initialize data structure needed for run-time partition pruning * - * Initial pruning can be done immediately, so it is done here if needed and - * the set of surviving partition subplans' indexes are added to the output - * parameter *initially_valid_subplans. + * Initial pruning can be done immediately, so it is done here unless it has + * already been done by ExecGetLockRelsDoInitialPruning(), and the set of + * surviving partition subplans' indexes are added to the output parameter + * *initially_valid_subplans. * * If subplans are indeed pruned, subplan_map arrays contained in the returned * PartitionPruneState are re-sequenced to not count those, though only if the @@ -1530,22 +1545,57 @@ ExecInitPartitionPruning(PlanState *planstate, { PartitionPruneState *prunestate; EState *estate = planstate->state; + Plan *plan = planstate->plan; + PlanInitPruningOutput *initPruningOutput = NULL; + bool do_pruning = (pruneinfo->needs_init_pruning || + pruneinfo->needs_exec_pruning); - /* We may need an expression context to evaluate partition exprs */ - ExecAssignExprContext(estate, planstate); + /* Retrieve the parent plan's PlanInitPruningOutput, if any. */ + if (estate->es_execlockrelsinfo) + { + initPruningOutput = (PlanInitPruningOutput *) + ExecFetchPlanInitPruningOutput(estate->es_execlockrelsinfo, plan); - /* - * Create the working data structure for pruning. - */ - prunestate = ExecCreatePartitionPruneState(planstate, pruneinfo); + Assert(initPruningOutput != NULL && + IsA(initPruningOutput, PlanInitPruningOutput)); + /* No need to do initial pruning again, only exec pruning. */ + do_pruning = pruneinfo->needs_exec_pruning; + } + + prunestate = NULL; + if (do_pruning) + { + /* We may need an expression context to evaluate partition exprs */ + ExecAssignExprContext(estate, planstate); + + /* For data reading, executor always omits detached partitions */ + if (estate->es_partition_directory == NULL) + estate->es_partition_directory = + CreatePartitionDirectory(estate->es_query_cxt, false); + + /* + * Create the working data structure for pruning. No need to consider + * initial pruning steps if we have a PlanInitPruningOutput. + */ + prunestate = ExecCreatePartitionPruneState(planstate, pruneinfo, + initPruningOutput == NULL, true, + NIL, planstate->ps_ExprContext, + estate->es_partition_directory); + } /* * Perform an initial partition prune, if required. */ - if (prunestate->do_initial_prune) + if (initPruningOutput) + { + /* ExecGetLockRelsDoInitialPruning() already did it for us! */ + *initially_valid_subplans = initPruningOutput->initially_valid_subplans; + } + else if (prunestate && prunestate->do_initial_prune) { /* Determine which subplans survive initial pruning */ - *initially_valid_subplans = ExecFindInitialMatchingSubPlans(prunestate); + *initially_valid_subplans = ExecFindInitialMatchingSubPlans(prunestate, + pruneinfo); } else { @@ -1563,7 +1613,7 @@ ExecInitPartitionPruning(PlanState *planstate, * invalid data in prunestate, because that data won't be consulted again * (cf initial Assert in ExecFindMatchingSubPlans). */ - if (prunestate->do_exec_prune && + if (prunestate && prunestate->do_exec_prune && bms_num_members(*initially_valid_subplans) < n_total_subplans) PartitionPruneStateFixSubPlanMap(prunestate, *initially_valid_subplans, @@ -1572,12 +1622,75 @@ ExecInitPartitionPruning(PlanState *planstate, return prunestate; } +/* + * ExecGetLockRelsDoInitialPruning + * Perform initial pruning as part of doing ExecGetLockRels() on the parent + * plan node + */ +Bitmapset * +ExecGetLockRelsDoInitialPruning(Plan *plan, ExecGetLockRelsContext *context, + PartitionPruneInfo *pruneinfo) +{ + List *rtable = context->stmt->rtable; + ParamListInfo params = context->params; + ExprContext *econtext; + PartitionDirectory pdir; + MemoryContext oldcontext, + tmpcontext; + PartitionPruneState *prunestate; + PlanInitPruningOutput *initPruningOutput; + + /* + * A temporary context to allocate stuff needded to run the pruning steps. + */ + tmpcontext = AllocSetContextCreate(CurrentMemoryContext, + "initial pruning working data", + ALLOCSET_DEFAULT_SIZES); + oldcontext = MemoryContextSwitchTo(tmpcontext); + + /* + * PartitionDirectory to look up partition descriptors, which omits + * detached partitions, just like in the executor proper. + */ + pdir = CreatePartitionDirectory(CurrentMemoryContext, false); + + /* + * We don't yet have a PlanState for the parent plan node, so must create + * a standalone ExprContext to evaluate pruning expressions, equipped with + * the information about the EXTERN parameters that the caller passed us. + * Note that that's okay because the initial pruning steps do not contain + * anything that requires the execution to have started. + */ + econtext = CreateStandaloneExprContext(); + econtext->ecxt_param_list_info = params; + prunestate = ExecCreatePartitionPruneState(NULL, pruneinfo, + true, false, + rtable, econtext, + pdir); + MemoryContextSwitchTo(oldcontext); + + /* Do the pruning and populate a PlanInitPruningOutput for this node. */ + initPruningOutput = makeNode(PlanInitPruningOutput); + initPruningOutput->initially_valid_subplans = + ExecFindInitialMatchingSubPlans(prunestate, pruneinfo); + ExecStorePlanInitPruningOutput(context, initPruningOutput, plan); + + FreeExprContext(econtext, true); + DestroyPartitionDirectory(pdir); + MemoryContextDelete(tmpcontext); + + return initPruningOutput->initially_valid_subplans; +} + /* * ExecCreatePartitionPruneState * Build the data structure required for calling * ExecFindInitialMatchingSubPlans and ExecFindMatchingSubPlans. * - * 'planstate' is the parent plan node's execution state. + * 'planstate', if not NULL, is the parent plan node's execution state. It + * can be NULL if being called before ExecutorStart(), in which case, + * 'rtable' (range table), 'econtext', and 'partdir' must be explicitly + * provided. * * 'partitionpruneinfo' is a PartitionPruneInfo as generated by * make_partition_pruneinfo. Here we build a PartitionPruneState containing a @@ -1592,19 +1705,20 @@ ExecInitPartitionPruning(PlanState *planstate, */ static PartitionPruneState * ExecCreatePartitionPruneState(PlanState *planstate, - PartitionPruneInfo *partitionpruneinfo) + PartitionPruneInfo *partitionpruneinfo, + bool consider_initial_steps, + bool consider_exec_steps, + List *rtable, ExprContext *econtext, + PartitionDirectory partdir) { - EState *estate = planstate->state; + EState *estate = planstate ? planstate->state : NULL; PartitionPruneState *prunestate; int n_part_hierarchies; ListCell *lc; int i; - ExprContext *econtext = planstate->ps_ExprContext; - /* For data reading, executor always omits detached partitions */ - if (estate->es_partition_directory == NULL) - estate->es_partition_directory = - CreatePartitionDirectory(estate->es_query_cxt, false); + Assert((estate != NULL) || + (partdir != NULL && econtext != NULL && rtable != NIL)); n_part_hierarchies = list_length(partitionpruneinfo->prune_infos); Assert(n_part_hierarchies > 0); @@ -1655,19 +1769,48 @@ ExecCreatePartitionPruneState(PlanState *planstate, PartitionedRelPruneInfo *pinfo = lfirst_node(PartitionedRelPruneInfo, lc2); PartitionedRelPruningData *pprune = &prunedata->partrelprunedata[j]; Relation partrel; + bool close_partrel = false; PartitionDesc partdesc; PartitionKey partkey; /* - * We can rely on the copies of the partitioned table's partition - * key and partition descriptor appearing in its relcache entry, - * because that entry will be held open and locked for the - * duration of this executor run. + * Must open the relation by ourselves when called before the + * execution has started, such as, when called during + * ExecutorGetLockRels() on a cached plan. In that case, + * sub-partitions must be locked, because AcquirePlannerLocks() + * would not have seen them. (1st relation in a partrelpruneinfos + * list is always the root partitioned table appearing in the + * query, which AcquirePlannerLocks() would have locked; the + * Assert in relation_open() guards that assumption.) + */ + if (estate == NULL) + { + RangeTblEntry *rte = rt_fetch(pinfo->rtindex, rtable); + int lockmode = (j == 0) ? NoLock : rte->rellockmode; + + partrel = table_open(rte->relid, lockmode); + close_partrel = true; + } + else + partrel = ExecGetRangeTableRelation(estate, pinfo->rtindex); + + /* + * We can rely on the copy of the partitioned table's partition + * key from in its relcache entry, because it can't change (or + * get destroyed) as long as the relation is locked. Partition + * descriptor is taken from the PartitionDirectory associated with + * the table that is held open long enough for the descriptor to + * remain valid while it's used to perform the pruning steps. */ - partrel = ExecGetRangeTableRelation(estate, pinfo->rtindex); partkey = RelationGetPartitionKey(partrel); - partdesc = PartitionDirectoryLookup(estate->es_partition_directory, - partrel); + partdesc = PartitionDirectoryLookup(partdir, partrel); + + /* + * Must close partrel, keeping the lock taken, if we're not using + * EState's entry. + */ + if (close_partrel) + table_close(partrel, NoLock); /* * Initialize the subplan_map and subpart_map. @@ -1769,7 +1912,7 @@ ExecCreatePartitionPruneState(PlanState *planstate, * Initialize pruning contexts as needed. */ pprune->initial_pruning_steps = pinfo->initial_pruning_steps; - if (pinfo->initial_pruning_steps) + if (consider_initial_steps && pinfo->initial_pruning_steps) { ExecInitPruningContext(&pprune->initial_context, pinfo->initial_pruning_steps, @@ -1779,7 +1922,7 @@ ExecCreatePartitionPruneState(PlanState *planstate, prunestate->do_initial_prune = true; } pprune->exec_pruning_steps = pinfo->exec_pruning_steps; - if (pinfo->exec_pruning_steps) + if (consider_exec_steps && pinfo->exec_pruning_steps) { ExecInitPruningContext(&pprune->exec_context, pinfo->exec_pruning_steps, @@ -1893,7 +2036,8 @@ ExecInitPruningContext(PartitionPruneContext *context, * is required. */ static Bitmapset * -ExecFindInitialMatchingSubPlans(PartitionPruneState *prunestate) +ExecFindInitialMatchingSubPlans(PartitionPruneState *prunestate, + PartitionPruneInfo *pruneinfo) { Bitmapset *result = NULL; MemoryContext oldcontext; @@ -1903,8 +2047,8 @@ ExecFindInitialMatchingSubPlans(PartitionPruneState *prunestate) Assert(prunestate->do_initial_prune); /* - * Switch to a temp context to avoid leaking memory in the executor's - * query-lifespan memory context. + * Switch to a temp context to avoid leaking memory in the longer-term + * memory context. */ oldcontext = MemoryContextSwitchTo(prunestate->prune_context); diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index 9df1f81ea8..7246f9175f 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -119,6 +119,7 @@ CreateExecutorState(void) estate->es_relations = NULL; estate->es_rowmarks = NULL; estate->es_plannedstmt = NULL; + estate->es_execlockrelsinfo = NULL; estate->es_junkFilter = NULL; @@ -785,6 +786,13 @@ ExecGetRangeTableRelation(EState *estate, Index rti) Assert(rti > 0 && rti <= estate->es_range_table_size); + /* + * A cross-check that AcquireExecutorLocks() hasn't missed any relations + * it must not have. + */ + Assert(estate->es_execlockrelsinfo == NULL || + bms_is_member(rti, estate->es_execlockrelsinfo->lockrels)); + rel = estate->es_relations[rti - 1]; if (rel == NULL) { diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index f9460ae506..a2182a6b1f 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -844,7 +844,7 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache) else dest = None_Receiver; - es->qd = CreateQueryDesc(es->stmt, + es->qd = CreateQueryDesc(es->stmt, NULL, fcache->src, GetActiveSnapshot(), InvalidSnapshot, diff --git a/src/backend/executor/nodeAppend.c b/src/backend/executor/nodeAppend.c index 5b6d3eb23b..9c6f907687 100644 --- a/src/backend/executor/nodeAppend.c +++ b/src/backend/executor/nodeAppend.c @@ -94,6 +94,55 @@ static bool ExecAppendAsyncRequest(AppendState *node, TupleTableSlot **result); static void ExecAppendAsyncEventWait(AppendState *node); static void classify_matching_subplans(AppendState *node); +/* ---------------------------------------------------------------- + * ExecGetAppendLockRels + * Do ExecGetLockRels()'s work for an Append plan + * ---------------------------------------------------------------- + */ +bool +ExecGetAppendLockRels(Append *node, ExecGetLockRelsContext *context) +{ + PartitionPruneInfo *pruneinfo = node->part_prune_info; + + /* + * Must always lock all the partitioned tables whose direct and indirect + * partitions will be scanned by this Append. + */ + context->lockrels = bms_add_members(context->lockrels, + node->partitioned_rels); + + /* + * Now recurse to subplans to add relations scanned therein. + * + * If initial pruning can be done, do that now and only recurse to the + * surviving subplans. + */ + if (pruneinfo && pruneinfo->needs_init_pruning) + { + List *subplans = node->appendplans; + Bitmapset *validsubplans; + int i; + + validsubplans = ExecGetLockRelsDoInitialPruning((Plan *) node, + context, pruneinfo); + + /* Recurse to surviving subplans. */ + i = -1; + while ((i = bms_next_member(validsubplans, i)) >= 0) + { + Plan *subplan = list_nth(subplans, i); + + (void) ExecGetLockRels(subplan, context); + } + + /* done with this node */ + return true; + } + + /* Tell the caller to recurse to *all* the subplans. */ + return false; +} + /* ---------------------------------------------------------------- * ExecInitAppend * @@ -155,7 +204,8 @@ ExecInitAppend(Append *node, EState *estate, int eflags) * subplan, we can fill as_valid_subplans immediately, preventing * later calls to ExecFindMatchingSubPlans. */ - if (!prunestate->do_exec_prune && nplans > 0) + if (appendstate->as_prune_state == NULL || + (!appendstate->as_prune_state->do_exec_prune && nplans > 0)) appendstate->as_valid_subplans = bms_add_range(NULL, 0, nplans - 1); } else diff --git a/src/backend/executor/nodeMergeAppend.c b/src/backend/executor/nodeMergeAppend.c index 9a9f29e845..4b04fcdbc2 100644 --- a/src/backend/executor/nodeMergeAppend.c +++ b/src/backend/executor/nodeMergeAppend.c @@ -54,6 +54,55 @@ typedef int32 SlotNumber; static TupleTableSlot *ExecMergeAppend(PlanState *pstate); static int heap_compare_slots(Datum a, Datum b, void *arg); +/* ---------------------------------------------------------------- + * ExecGetMergeAppendLockRels + * Do ExecGetLockRels()'s work for a MergeAppend plan + * ---------------------------------------------------------------- + */ +bool +ExecGetMergeAppendLockRels(MergeAppend *node, ExecGetLockRelsContext *context) +{ + PartitionPruneInfo *pruneinfo = node->part_prune_info; + + /* + * Must always lock all the partitioned tables whose direct and indirect + * partitions will be scanned by this Append. + */ + context->lockrels = bms_add_members(context->lockrels, + node->partitioned_rels); + + /* + * Now recurse to subplans to add relations scanned therein. + * + * If initial pruning can be done, do that now and only recurse to the + * surviving subplans. + */ + if (pruneinfo && pruneinfo->needs_init_pruning) + { + List *subplans = node->mergeplans; + Bitmapset *validsubplans; + int i; + + validsubplans = ExecGetLockRelsDoInitialPruning((Plan *) node, + context, pruneinfo); + + /* Recurse to surviving subplans. */ + i = -1; + while ((i = bms_next_member(validsubplans, i)) >= 0) + { + Plan *subplan = list_nth(subplans, i); + + (void) ExecGetLockRels(subplan, context); + } + + /* done with this node */ + return true; + } + + /* Tell the caller to recurse to *all* the subplans. */ + return false; +} + /* ---------------------------------------------------------------- * ExecInitMergeAppend @@ -103,7 +152,8 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags) * subplan, we can fill as_valid_subplans immediately, preventing * later calls to ExecFindMatchingSubPlans. */ - if (!prunestate->do_exec_prune && nplans > 0) + if (mergestate->ms_prune_state == NULL || + (!mergestate->ms_prune_state->do_exec_prune && nplans > 0)) mergestate->ms_valid_subplans = bms_add_range(NULL, 0, nplans - 1); } else diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 701fe05296..23df3efef0 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -3008,6 +3008,31 @@ ExecLookupResultRelByOid(ModifyTableState *node, Oid resultoid, return NULL; } +/* + * ExecGetModifyTableLockRels + * Do ExecGetLockRels()'s work for a ModifyTable plan + */ +bool +ExecGetModifyTableLockRels(ModifyTable *plan, ExecGetLockRelsContext *context) +{ + ListCell *lc; + + /* First add the result relation RTIs mentioned in the node. */ + if (plan->rootRelation > 0) + context->lockrels = bms_add_member(context->lockrels, + plan->rootRelation); + context->lockrels = bms_add_member(context->lockrels, + plan->nominalRelation); + foreach(lc, plan->resultRelations) + { + context->lockrels = bms_add_member(context->lockrels, + lfirst_int(lc)); + } + + /* Tell the caller to recurse to the subplan (outerPlan(plan)). */ + return false; +} + /* ---------------------------------------------------------------- * ExecInitModifyTable * ---------------------------------------------------------------- diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index a82e986667..2107009591 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -1578,6 +1578,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, CachedPlanSource *plansource; CachedPlan *cplan; List *stmt_list; + List *execlockrelsinfo_list; char *query_string; Snapshot snapshot; MemoryContext oldcontext; @@ -1659,6 +1660,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, /* Replan if needed, and increment plan refcount for portal */ cplan = GetCachedPlan(plansource, paramLI, NULL, _SPI_current->queryEnv); stmt_list = cplan->stmt_list; + execlockrelsinfo_list = cplan->execlockrelsinfo_list; if (!plan->saved) { @@ -1670,6 +1672,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, */ oldcontext = MemoryContextSwitchTo(portal->portalContext); stmt_list = copyObject(stmt_list); + execlockrelsinfo_list = copyObject(execlockrelsinfo_list); MemoryContextSwitchTo(oldcontext); ReleaseCachedPlan(cplan, NULL); cplan = NULL; /* portal shouldn't depend on cplan */ @@ -1683,6 +1686,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, query_string, plansource->commandTag, stmt_list, + execlockrelsinfo_list, cplan); /* @@ -2473,7 +2477,9 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options, { CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1); List *stmt_list; - ListCell *lc2; + List *execlockrelsinfo_list; + ListCell *lc2, + *lc3; spicallbackarg.query = plansource->query_string; @@ -2552,6 +2558,7 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options, plan_owner, _SPI_current->queryEnv); stmt_list = cplan->stmt_list; + execlockrelsinfo_list = cplan->execlockrelsinfo_list; /* * If we weren't given a specific snapshot to use, and the statement @@ -2589,9 +2596,10 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options, } } - foreach(lc2, stmt_list) + forboth(lc2, stmt_list, lc3, execlockrelsinfo_list) { PlannedStmt *stmt = lfirst_node(PlannedStmt, lc2); + ExecLockRelsInfo *execlockrelsinfo = lfirst_node(ExecLockRelsInfo, lc3); bool canSetTag = stmt->canSetTag; DestReceiver *dest; @@ -2663,7 +2671,7 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options, else snap = InvalidSnapshot; - qdesc = CreateQueryDesc(stmt, + qdesc = CreateQueryDesc(stmt, execlockrelsinfo, plansource->query_string, snap, crosscheck_snapshot, dest, diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index d4b5cc7e59..631727d310 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -68,6 +68,13 @@ } \ } while (0) +/* Copy a field that is an array with numElem ints */ +#define COPY_INT_ARRAY(fldname, numElem) \ + do { \ + newnode->fldname = (numElem) > 0 ? palloc((numElem) * sizeof(int)) : NULL; \ + memcpy(newnode->fldname, from->fldname, sizeof(int) * (numElem)); \ + } while (0) + /* Copy a parse location field (for Copy, this is same as scalar case) */ #define COPY_LOCATION_FIELD(fldname) \ (newnode->fldname = from->fldname) @@ -94,8 +101,10 @@ _copyPlannedStmt(const PlannedStmt *from) COPY_SCALAR_FIELD(transientPlan); COPY_SCALAR_FIELD(dependsOnRole); COPY_SCALAR_FIELD(parallelModeNeeded); + COPY_SCALAR_FIELD(containsInitialPruning); COPY_SCALAR_FIELD(jitFlags); COPY_NODE_FIELD(planTree); + COPY_SCALAR_FIELD(numPlanNodes); COPY_NODE_FIELD(rtable); COPY_NODE_FIELD(resultRelations); COPY_NODE_FIELD(appendRelations); @@ -1281,6 +1290,8 @@ _copyPartitionPruneInfo(const PartitionPruneInfo *from) PartitionPruneInfo *newnode = makeNode(PartitionPruneInfo); COPY_NODE_FIELD(prune_infos); + COPY_SCALAR_FIELD(needs_init_pruning); + COPY_SCALAR_FIELD(needs_exec_pruning); COPY_BITMAPSET_FIELD(other_subplans); return newnode; @@ -5137,6 +5148,33 @@ _copyExtensibleNode(const ExtensibleNode *from) return newnode; } +/* **************************************************************** + * execnodes.h copy functions + * **************************************************************** + */ +static ExecLockRelsInfo * +_copyExecLockRelsInfo(const ExecLockRelsInfo *from) +{ + ExecLockRelsInfo *newnode = makeNode(ExecLockRelsInfo); + + COPY_BITMAPSET_FIELD(lockrels); + COPY_SCALAR_FIELD(numPlanNodes); + COPY_NODE_FIELD(initPruningOutputs); + COPY_INT_ARRAY(ipoIndexes, from->numPlanNodes); + + return newnode; +} + +static PlanInitPruningOutput * +_copyPlanInitPruningOutput(const PlanInitPruningOutput *from) +{ + PlanInitPruningOutput *newnode = makeNode(PlanInitPruningOutput); + + COPY_BITMAPSET_FIELD(initially_valid_subplans); + + return newnode; +} + /* **************************************************************** * value.h copy functions * **************************************************************** @@ -5191,7 +5229,6 @@ _copyBitString(const BitString *from) return newnode; } - static ForeignKeyCacheInfo * _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from) { @@ -6176,6 +6213,16 @@ copyObjectImpl(const void *from) retval = _copyPublicationTable(from); break; + /* + * EXECUTION NODES + */ + case T_ExecLockRelsInfo: + retval = _copyExecLockRelsInfo(from); + break; + case T_PlanInitPruningOutput: + retval = _copyPlanInitPruningOutput(from); + break; + /* * MISCELLANEOUS NODES */ diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 99056272f3..f361d2e2bc 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -312,8 +312,10 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node) WRITE_BOOL_FIELD(transientPlan); WRITE_BOOL_FIELD(dependsOnRole); WRITE_BOOL_FIELD(parallelModeNeeded); + WRITE_BOOL_FIELD(containsInitialPruning); WRITE_INT_FIELD(jitFlags); WRITE_NODE_FIELD(planTree); + WRITE_INT_FIELD(numPlanNodes); WRITE_NODE_FIELD(rtable); WRITE_NODE_FIELD(resultRelations); WRITE_NODE_FIELD(appendRelations); @@ -1007,6 +1009,8 @@ _outPartitionPruneInfo(StringInfo str, const PartitionPruneInfo *node) WRITE_NODE_TYPE("PARTITIONPRUNEINFO"); WRITE_NODE_FIELD(prune_infos); + WRITE_BOOL_FIELD(needs_init_pruning); + WRITE_BOOL_FIELD(needs_exec_pruning); WRITE_BITMAPSET_FIELD(other_subplans); } @@ -2747,6 +2751,31 @@ _outExtensibleNode(StringInfo str, const ExtensibleNode *node) methods->nodeOut(str, node); } +/***************************************************************************** + * + * Stuff from execnodes.h + * + *****************************************************************************/ + +static void +_outExecLockRelsInfo(StringInfo str, const ExecLockRelsInfo *node) +{ + WRITE_NODE_TYPE("EXECLOCKRELSINFO"); + + WRITE_BITMAPSET_FIELD(lockrels); + WRITE_INT_FIELD(numPlanNodes); + WRITE_NODE_FIELD(initPruningOutputs); + WRITE_INT_ARRAY(ipoIndexes, node->numPlanNodes); +} + +static void +_outPlanInitPruningOutput(StringInfo str, const PlanInitPruningOutput *node) +{ + WRITE_NODE_TYPE("PLANINITPRUNINGOUTPUT"); + + WRITE_BITMAPSET_FIELD(initially_valid_subplans); +} + /***************************************************************************** * * Stuff from parsenodes.h. @@ -4600,6 +4629,16 @@ outNode(StringInfo str, const void *obj) _outJsonConstructorExpr(str, obj); break; + /* + * EXECUTION NODES + */ + case T_ExecLockRelsInfo: + _outExecLockRelsInfo(str, obj); + break; + case T_PlanInitPruningOutput: + _outPlanInitPruningOutput(str, obj); + break; + default: /* diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 7536f216bd..41fc710999 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -1650,8 +1650,10 @@ _readPlannedStmt(void) READ_BOOL_FIELD(transientPlan); READ_BOOL_FIELD(dependsOnRole); READ_BOOL_FIELD(parallelModeNeeded); + READ_BOOL_FIELD(containsInitialPruning); READ_INT_FIELD(jitFlags); READ_NODE_FIELD(planTree); + READ_INT_FIELD(numPlanNodes); READ_NODE_FIELD(rtable); READ_NODE_FIELD(resultRelations); READ_NODE_FIELD(appendRelations); @@ -2602,6 +2604,8 @@ _readPartitionPruneInfo(void) READ_LOCALS(PartitionPruneInfo); READ_NODE_FIELD(prune_infos); + READ_BOOL_FIELD(needs_init_pruning); + READ_BOOL_FIELD(needs_exec_pruning); READ_BITMAPSET_FIELD(other_subplans); READ_DONE(); @@ -2771,6 +2775,35 @@ _readPartitionRangeDatum(void) READ_DONE(); } +/* + * _readExecLockRelsInfo + */ +static ExecLockRelsInfo * +_readExecLockRelsInfo(void) +{ + READ_LOCALS(ExecLockRelsInfo); + + READ_BITMAPSET_FIELD(lockrels); + READ_INT_FIELD(numPlanNodes); + READ_NODE_FIELD(initPruningOutputs); + READ_INT_ARRAY(ipoIndexes, local_node->numPlanNodes); + + READ_DONE(); +} + +/* + * _readPlanInitPruningOutput + */ +static PlanInitPruningOutput * +_readPlanInitPruningOutput(void) +{ + READ_LOCALS(PlanInitPruningOutput); + + READ_BITMAPSET_FIELD(initially_valid_subplans); + + READ_DONE(); +} + /* * parseNodeString * @@ -3050,6 +3083,10 @@ parseNodeString(void) return_value = _readJsonValueExpr(); else if (MATCH("JSONCTOREXPR", 12)) return_value = _readJsonConstructorExpr(); + else if (MATCH("EXECLOCKRELSINFO", 16)) + return_value = _readExecLockRelsInfo(); + else if (MATCH("PLANINITPRUNINGOUTPUT", 21)) + return_value = _readPlanInitPruningOutput(); else { elog(ERROR, "badly formatted node string \"%.32s\"...", token); diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 374a9d9753..329fb9d6e7 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -517,7 +517,9 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions, result->transientPlan = glob->transientPlan; result->dependsOnRole = glob->dependsOnRole; result->parallelModeNeeded = glob->parallelModeNeeded; + result->containsInitialPruning = glob->containsInitialPruning; result->planTree = top_plan; + result->numPlanNodes = glob->lastPlanNodeId; result->rtable = glob->finalrtable; result->resultRelations = glob->resultRelations; result->appendRelations = glob->appendRelations; diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index dbdeb8ec9d..ac795ae9d9 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -1561,6 +1561,9 @@ set_append_references(PlannerInfo *root, pinfo->rtindex += rtoffset; } } + + if (aplan->part_prune_info->needs_init_pruning) + root->glob->containsInitialPruning = true; } /* We don't need to recurse to lefttree or righttree ... */ @@ -1648,6 +1651,9 @@ set_mergeappend_references(PlannerInfo *root, pinfo->rtindex += rtoffset; } } + + if (mplan->part_prune_info->needs_init_pruning) + root->glob->containsInitialPruning = true; } /* We don't need to recurse to lefttree or righttree ... */ diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 7080cb25d9..3322dc79f2 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -144,7 +144,9 @@ static List *make_partitionedrel_pruneinfo(PlannerInfo *root, List *prunequal, Bitmapset *partrelids, int *relid_subplan_map, - Bitmapset **matchedsubplans); + Bitmapset **matchedsubplans, + bool *needs_init_pruning, + bool *needs_exec_pruning); static void gen_partprune_steps(RelOptInfo *rel, List *clauses, PartClauseTarget target, GeneratePruningStepsContext *context); @@ -230,6 +232,8 @@ make_partition_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, int *relid_subplan_map; ListCell *lc; int i; + bool needs_init_pruning = false; + bool needs_exec_pruning = false; /* * Scan the subpaths to see which ones are scans of partition child @@ -309,12 +313,16 @@ make_partition_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, Bitmapset *partrelids = (Bitmapset *) lfirst(lc); List *pinfolist; Bitmapset *matchedsubplans = NULL; + bool partrel_needs_init_pruning; + bool partrel_needs_exec_pruning; pinfolist = make_partitionedrel_pruneinfo(root, parentrel, prunequal, partrelids, relid_subplan_map, - &matchedsubplans); + &matchedsubplans, + &partrel_needs_init_pruning, + &partrel_needs_exec_pruning); /* When pruning is possible, record the matched subplans */ if (pinfolist != NIL) @@ -323,6 +331,10 @@ make_partition_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, allmatchedsubplans = bms_join(matchedsubplans, allmatchedsubplans); } + if (!needs_init_pruning) + needs_init_pruning = partrel_needs_init_pruning; + if (!needs_exec_pruning) + needs_exec_pruning = partrel_needs_exec_pruning; } pfree(relid_subplan_map); @@ -337,6 +349,8 @@ make_partition_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, /* Else build the result data structure */ pruneinfo = makeNode(PartitionPruneInfo); pruneinfo->prune_infos = prunerelinfos; + pruneinfo->needs_init_pruning = needs_init_pruning; + pruneinfo->needs_exec_pruning = needs_exec_pruning; /* * Some subplans may not belong to any of the identified partitioned rels. @@ -435,13 +449,18 @@ add_part_relids(List *allpartrelids, Bitmapset *partrelids) * If we cannot find any useful run-time pruning steps, return NIL. * However, on success, each rel identified in partrelids will have * an element in the result list, even if some of them are useless. + * *needs_init_pruning and *needs_exec_pruning are set to indicate that the + * returned PartitionedRelPruneInfos contains pruning steps that can be + * performed before and after execution begins, respectively. */ static List * make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, List *prunequal, Bitmapset *partrelids, int *relid_subplan_map, - Bitmapset **matchedsubplans) + Bitmapset **matchedsubplans, + bool *needs_init_pruning, + bool *needs_exec_pruning) { RelOptInfo *targetpart = NULL; List *pinfolist = NIL; @@ -452,6 +471,10 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, int rti; int i; + /* Will find out below. */ + *needs_init_pruning = false; + *needs_exec_pruning = false; + /* * Examine each partitioned rel, constructing a temporary array to map * from planner relids to index of the partitioned rel, and building a @@ -539,6 +562,9 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * executor per-scan pruning steps. This first pass creates startup * pruning steps and detects whether there's any possibly-useful quals * that would require per-scan pruning. + * + * In the first pass, we note whether the 2nd pass is necessary by + * by noting the presence of EXEC parameters. */ gen_partprune_steps(subpart, partprunequal, PARTTARGET_INITIAL, &context); @@ -613,6 +639,11 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, pinfo->execparamids = execparamids; /* Remaining fields will be filled in the next loop */ + if (!*needs_init_pruning) + *needs_init_pruning = (initial_pruning_steps != NIL); + if (!*needs_exec_pruning) + *needs_exec_pruning = (exec_pruning_steps != NIL); + pinfolist = lappend(pinfolist, pinfo); } diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index ba2fcfeb4a..085eb3f209 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -945,15 +945,17 @@ pg_plan_query(Query *querytree, const char *query_string, int cursorOptions, * For normal optimizable statements, invoke the planner. For utility * statements, just make a wrapper PlannedStmt node. * - * The result is a list of PlannedStmt nodes. + * The result is a list of PlannedStmt nodes. Also, a NULL is appended to + * *execlockrelsinfo_list for each PlannedStmt added to the returned list. */ List * pg_plan_queries(List *querytrees, const char *query_string, int cursorOptions, - ParamListInfo boundParams) + ParamListInfo boundParams, List **execlockrelsinfo_list) { List *stmt_list = NIL; ListCell *query_list; + *execlockrelsinfo_list = NIL; foreach(query_list, querytrees) { Query *query = lfirst_node(Query, query_list); @@ -977,6 +979,7 @@ pg_plan_queries(List *querytrees, const char *query_string, int cursorOptions, } stmt_list = lappend(stmt_list, stmt); + *execlockrelsinfo_list = lappend(*execlockrelsinfo_list, NULL); } return stmt_list; @@ -1080,7 +1083,8 @@ exec_simple_query(const char *query_string) QueryCompletion qc; MemoryContext per_parsetree_context = NULL; List *querytree_list, - *plantree_list; + *plantree_list, + *plantree_execlockrelsinfo_list; Portal portal; DestReceiver *receiver; int16 format; @@ -1167,7 +1171,8 @@ exec_simple_query(const char *query_string) NULL, 0, NULL); plantree_list = pg_plan_queries(querytree_list, query_string, - CURSOR_OPT_PARALLEL_OK, NULL); + CURSOR_OPT_PARALLEL_OK, NULL, + &plantree_execlockrelsinfo_list); /* * Done with the snapshot used for parsing/planning. @@ -1203,6 +1208,7 @@ exec_simple_query(const char *query_string) query_string, commandTag, plantree_list, + plantree_execlockrelsinfo_list, NULL); /* @@ -1991,6 +1997,7 @@ exec_bind_message(StringInfo input_message) query_string, psrc->commandTag, cplan->stmt_list, + cplan->execlockrelsinfo_list, cplan); /* Done with the snapshot used for parameter I/O and parsing/planning */ diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c index 5f907831a3..972ddc014e 100644 --- a/src/backend/tcop/pquery.c +++ b/src/backend/tcop/pquery.c @@ -35,7 +35,7 @@ Portal ActivePortal = NULL; -static void ProcessQuery(PlannedStmt *plan, +static void ProcessQuery(PlannedStmt *plan, ExecLockRelsInfo *execlockrelsinfo, const char *sourceText, ParamListInfo params, QueryEnvironment *queryEnv, @@ -65,6 +65,7 @@ static void DoPortalRewind(Portal portal); */ QueryDesc * CreateQueryDesc(PlannedStmt *plannedstmt, + ExecLockRelsInfo *execlockrelsinfo, const char *sourceText, Snapshot snapshot, Snapshot crosscheck_snapshot, @@ -77,6 +78,7 @@ CreateQueryDesc(PlannedStmt *plannedstmt, qd->operation = plannedstmt->commandType; /* operation */ qd->plannedstmt = plannedstmt; /* plan */ + qd->execlockrelsinfo = execlockrelsinfo; /* ExecutorGetLockRels() output for plan */ qd->sourceText = sourceText; /* query text */ qd->snapshot = RegisterSnapshot(snapshot); /* snapshot */ /* RI check snapshot */ @@ -122,6 +124,7 @@ FreeQueryDesc(QueryDesc *qdesc) * PORTAL_ONE_RETURNING, or PORTAL_ONE_MOD_WITH portal * * plan: the plan tree for the query + * execlockrelsinfo: ExecutorGetLockRels() output for the plan tree * sourceText: the source text of the query * params: any parameters needed * dest: where to send results @@ -134,6 +137,7 @@ FreeQueryDesc(QueryDesc *qdesc) */ static void ProcessQuery(PlannedStmt *plan, + ExecLockRelsInfo *execlockrelsinfo, const char *sourceText, ParamListInfo params, QueryEnvironment *queryEnv, @@ -145,7 +149,7 @@ ProcessQuery(PlannedStmt *plan, /* * Create the QueryDesc object */ - queryDesc = CreateQueryDesc(plan, sourceText, + queryDesc = CreateQueryDesc(plan, execlockrelsinfo, sourceText, GetActiveSnapshot(), InvalidSnapshot, dest, params, queryEnv, 0); @@ -490,6 +494,7 @@ PortalStart(Portal portal, ParamListInfo params, * the destination to DestNone. */ queryDesc = CreateQueryDesc(linitial_node(PlannedStmt, portal->stmts), + linitial_node(ExecLockRelsInfo, portal->execlockrelsinfos), portal->sourceText, GetActiveSnapshot(), InvalidSnapshot, @@ -1190,7 +1195,8 @@ PortalRunMulti(Portal portal, QueryCompletion *qc) { bool active_snapshot_set = false; - ListCell *stmtlist_item; + ListCell *stmtlist_item, + *execlockrelsinfolist_item; /* * If the destination is DestRemoteExecute, change to DestNone. The @@ -1211,9 +1217,12 @@ PortalRunMulti(Portal portal, * Loop to handle the individual queries generated from a single parsetree * by analysis and rewrite. */ - foreach(stmtlist_item, portal->stmts) + forboth(stmtlist_item, portal->stmts, + execlockrelsinfolist_item, portal->execlockrelsinfos) { PlannedStmt *pstmt = lfirst_node(PlannedStmt, stmtlist_item); + ExecLockRelsInfo *execlockrelsinfo = lfirst_node(ExecLockRelsInfo, + execlockrelsinfolist_item); /* * If we got a cancel signal in prior command, quit @@ -1271,7 +1280,7 @@ PortalRunMulti(Portal portal, if (pstmt->canSetTag) { /* statement can set tag string */ - ProcessQuery(pstmt, + ProcessQuery(pstmt, execlockrelsinfo, portal->sourceText, portal->portalParams, portal->queryEnv, @@ -1280,7 +1289,7 @@ PortalRunMulti(Portal portal, else { /* stmt added by rewrite cannot set tag */ - ProcessQuery(pstmt, + ProcessQuery(pstmt, execlockrelsinfo, portal->sourceText, portal->portalParams, portal->queryEnv, diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c index 4cf6db504f..9f5a40a0a6 100644 --- a/src/backend/utils/cache/plancache.c +++ b/src/backend/utils/cache/plancache.c @@ -99,14 +99,16 @@ static dlist_head cached_expression_list = DLIST_STATIC_INIT(cached_expression_l static void ReleaseGenericPlan(CachedPlanSource *plansource); static List *RevalidateCachedQuery(CachedPlanSource *plansource, QueryEnvironment *queryEnv); -static bool CheckCachedPlan(CachedPlanSource *plansource); +static bool CheckCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams); static CachedPlan *BuildCachedPlan(CachedPlanSource *plansource, List *qlist, ParamListInfo boundParams, QueryEnvironment *queryEnv); +static void CachedPlanSaveExecLockRelsInfos(CachedPlan *plan, List *execlockrelsinfo_list); static bool choose_custom_plan(CachedPlanSource *plansource, ParamListInfo boundParams); static double cached_plan_cost(CachedPlan *plan, bool include_planner); static Query *QueryListGetPrimaryStmt(List *stmts); -static void AcquireExecutorLocks(List *stmt_list, bool acquire); +static List *AcquireExecutorLocks(List *stmt_list, ParamListInfo boundParams); +static void ReleaseExecutorLocks(List *stmt_list, List *execlockrelsinfo_list); static void AcquirePlannerLocks(List *stmt_list, bool acquire); static void ScanQueryForLocks(Query *parsetree, bool acquire); static bool ScanQueryWalker(Node *node, bool *acquire); @@ -790,9 +792,21 @@ RevalidateCachedQuery(CachedPlanSource *plansource, * * On a "true" return, we have acquired the locks needed to run the plan. * (We must do this for the "true" result to be race-condition-free.) + * + * If the CachedPlan is valid, this may in some cases call ExecutorGetLockRels + * on each PlannedStmt contained in it to determine the set of relations to be + * locked by AcquireExecutorLocks(), instead of just scanning its range table, + * which is done to prune away any nodes in the tree that need not be executed + * based on the result of initial partition pruning. Resulting + * ExecLockRelsInfo nodes containing the result of such pruning, allocated in + * a child context of the context containing the plan itself, are added into + * plan->execlockrelsinfo_list. The previous contents of the list from the + * last invocation on the same CachedPlan are deleted, because they would no + * longer be valid given the fresh set of parameter values which may be used + * as pruning parameters. */ static bool -CheckCachedPlan(CachedPlanSource *plansource) +CheckCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams) { CachedPlan *plan = plansource->gplan; @@ -820,13 +834,25 @@ CheckCachedPlan(CachedPlanSource *plansource) */ if (plan->is_valid) { + List *execlockrelsinfo_list; + /* * Plan must have positive refcount because it is referenced by * plansource; so no need to fear it disappears under us here. */ Assert(plan->refcount > 0); - AcquireExecutorLocks(plan->stmt_list, true); + /* + * Lock relations scanned by the plan. If ExecutorGetLockRels() asked + * to omit some relations because the plan nodes that scan them were + * found to be pruned, the executor will be informed of the omission of + * the plan nodes themselves, so that it doesn't accidentally try to + * execute those nodes, via the ExecLockRelsInfo nodes collected in the + * returned list that is also passed to it along with the list of + * PlannedStmts. + */ + execlockrelsinfo_list = AcquireExecutorLocks(plan->stmt_list, + boundParams); /* * If plan was transient, check to see if TransactionXmin has @@ -844,11 +870,14 @@ CheckCachedPlan(CachedPlanSource *plansource) if (plan->is_valid) { /* Successfully revalidated and locked the query. */ + + /* Remember ExecLockRelsInfos in the CachedPlan. */ + CachedPlanSaveExecLockRelsInfos(plan, execlockrelsinfo_list); return true; } /* Oops, the race case happened. Release useless locks. */ - AcquireExecutorLocks(plan->stmt_list, false); + ReleaseExecutorLocks(plan->stmt_list, execlockrelsinfo_list); } /* @@ -880,7 +909,8 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist, ParamListInfo boundParams, QueryEnvironment *queryEnv) { CachedPlan *plan; - List *plist; + List *plist, + *execlockrelsinfo_list; bool snapshot_set; bool is_transient; MemoryContext plan_context; @@ -933,7 +963,8 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist, * Generate the plan. */ plist = pg_plan_queries(qlist, plansource->query_string, - plansource->cursor_options, boundParams); + plansource->cursor_options, boundParams, + &execlockrelsinfo_list); /* Release snapshot if we got one */ if (snapshot_set) @@ -1002,6 +1033,16 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist, plan->is_saved = false; plan->is_valid = true; + /* + * Save the dummy ExecLockRelsInfo list, that is a list containing NULLs + * as elements. We must do this, becasue users of the CachedPlan expect + * one to go with the list of PlannedStmts. + * XXX maybe get rid of that contract. + */ + plan->execlockrelsinfo_context = NULL; + CachedPlanSaveExecLockRelsInfos(plan, execlockrelsinfo_list); + Assert(MemoryContextIsValid(plan->execlockrelsinfo_context)); + /* assign generation number to new plan */ plan->generation = ++(plansource->generation); @@ -1160,7 +1201,7 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams, if (!customplan) { - if (CheckCachedPlan(plansource)) + if (CheckCachedPlan(plansource, boundParams)) { /* We want a generic plan, and we already have a valid one */ plan = plansource->gplan; @@ -1586,6 +1627,49 @@ CopyCachedPlan(CachedPlanSource *plansource) return newsource; } +/* + * CachedPlanSaveExecLockRelsInfos + * Save the list containing ExecLockRelsInfo nodes into the given + * CachedPlan + * + * The provided list is copied into a dedicated context that is a child of + * plan->context. If the child context already exists, it is emptied, because + * any ExecLockRelsInfo contained therein would no longer be useful. + */ +static void +CachedPlanSaveExecLockRelsInfos(CachedPlan *plan, List *execlockrelsinfo_list) +{ + MemoryContext execlockrelsinfo_context = plan->execlockrelsinfo_context, + oldcontext = CurrentMemoryContext; + List *execlockrelsinfo_list_copy; + + /* + * Set up the dedicated context if not already done, saving it as a child + * of the CachedPlan's context. + */ + if (execlockrelsinfo_context == NULL) + { + execlockrelsinfo_context = AllocSetContextCreate(CurrentMemoryContext, + "CachedPlan execlockrelsinfo list", + ALLOCSET_START_SMALL_SIZES); + MemoryContextSetParent(execlockrelsinfo_context, plan->context); + MemoryContextSetIdentifier(execlockrelsinfo_context, plan->context->ident); + plan->execlockrelsinfo_context = execlockrelsinfo_context; + } + else + { + /* Just clear existing contents by resetting the context. */ + Assert(MemoryContextIsValid(execlockrelsinfo_context)); + MemoryContextReset(execlockrelsinfo_context); + } + + MemoryContextSwitchTo(execlockrelsinfo_context); + execlockrelsinfo_list_copy = copyObject(execlockrelsinfo_list); + MemoryContextSwitchTo(oldcontext); + + plan->execlockrelsinfo_list = execlockrelsinfo_list_copy; +} + /* * CachedPlanIsValid: test whether the rewritten querytree within a * CachedPlanSource is currently valid (that is, not marked as being in need @@ -1737,17 +1821,21 @@ QueryListGetPrimaryStmt(List *stmts) /* * AcquireExecutorLocks: acquire locks needed for execution of a cached plan; - * or release them if acquire is false. + * + * Returns a list of ExecLockRelsInfo nodes containing one element for each + * PlannedStmt in stmt_list or NULL if the latter is utility statement or its + * containsInitialPruning is false. */ -static void -AcquireExecutorLocks(List *stmt_list, bool acquire) +static List * +AcquireExecutorLocks(List *stmt_list, ParamListInfo boundParams) { ListCell *lc1; + List *execlockrelsinfo_list = NIL; foreach(lc1, stmt_list) { PlannedStmt *plannedstmt = lfirst_node(PlannedStmt, lc1); - ListCell *lc2; + ExecLockRelsInfo *execlockrelsinfo = NULL; if (plannedstmt->commandType == CMD_UTILITY) { @@ -1761,27 +1849,139 @@ AcquireExecutorLocks(List *stmt_list, bool acquire) Query *query = UtilityContainsQuery(plannedstmt->utilityStmt); if (query) - ScanQueryForLocks(query, acquire); - continue; + ScanQueryForLocks(query, true); } - - foreach(lc2, plannedstmt->rtable) + else { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc2); + /* + * Figure out the set of relations that would need to be locked + * before executing the plan. + */ + if (!plannedstmt->containsInitialPruning) + { + /* + * If the plan contains no initial pruning steps, just lock + * all the relations found in the range table. + */ + ListCell *lc; - if (rte->rtekind != RTE_RELATION) - continue; + foreach(lc, plannedstmt->rtable) + { + RangeTblEntry *rte = lfirst(lc); + + if (rte->rtekind != RTE_RELATION) + continue; + + /* + * Acquire the appropriate type of lock on each relation + * OID. Note that we don't actually try to open the rel, + * and hence will not fail if it's been dropped entirely + * --- we'll just transiently acquire a non-conflicting + * lock. + */ + LockRelationOid(rte->relid, rte->rellockmode); + } + } + else + { + int rti; + Bitmapset *lockrels; + + /* + * Walk the plan tree to find only the minimal set of + * relations to be locked, considering the effect of performing + * initial partition pruning. + */ + execlockrelsinfo = ExecutorGetLockRels(plannedstmt, boundParams); + lockrels = execlockrelsinfo->lockrels; + + rti = -1; + while ((rti = bms_next_member(lockrels, rti)) >= 0) + { + RangeTblEntry *rte = rt_fetch(rti, plannedstmt->rtable); + Assert(rte->rtekind == RTE_RELATION); + + /* See the comment above. */ + LockRelationOid(rte->relid, rte->rellockmode); + } + } + } + + /* + * Remember ExecLockRelsInfo for later adding to the QueryDesc that + * will be passed to the executor when executing this plan. May be + * NULL, but must keep the list the same length as stmt_list. + */ + execlockrelsinfo_list = lappend(execlockrelsinfo_list, + execlockrelsinfo); + } + + return execlockrelsinfo_list; +} + +/* + * ReleaseExecutorLocks + * Release locks that would've been acquired by an earlier call to + * AcquireExecutorLocks() + */ +static void +ReleaseExecutorLocks(List *stmt_list, List *execlockrelsinfo_list) +{ + ListCell *lc1, + *lc2; + + forboth(lc1, stmt_list, lc2, execlockrelsinfo_list) + { + PlannedStmt *plannedstmt = lfirst_node(PlannedStmt, lc1); + ExecLockRelsInfo *execlockrelsinfo = lfirst_node(ExecLockRelsInfo, lc2); + + if (plannedstmt->commandType == CMD_UTILITY) + { /* - * Acquire the appropriate type of lock on each relation OID. Note - * that we don't actually try to open the rel, and hence will not - * fail if it's been dropped entirely --- we'll just transiently - * acquire a non-conflicting lock. + * Ignore utility statements, except those (such as EXPLAIN) that + * contain a parsed-but-not-planned query. Note: it's okay to use + * ScanQueryForLocks, even though the query hasn't been through + * rule rewriting, because rewriting doesn't change the query + * representation. */ - if (acquire) - LockRelationOid(rte->relid, rte->rellockmode); + Query *query = UtilityContainsQuery(plannedstmt->utilityStmt); + + if (query) + ScanQueryForLocks(query, false); + } + else + { + if (execlockrelsinfo == NULL) + { + ListCell *lc; + + foreach(lc, plannedstmt->rtable) + { + RangeTblEntry *rte = lfirst(lc); + + if (rte->rtekind != RTE_RELATION) + continue; + + LockRelationOid(rte->relid, rte->rellockmode); + } + } else - UnlockRelationOid(rte->relid, rte->rellockmode); + { + int rti; + Bitmapset *lockrels; + + lockrels = execlockrelsinfo->lockrels; + rti = -1; + while ((rti = bms_next_member(lockrels, rti)) >= 0) + { + RangeTblEntry *rte = rt_fetch(rti, plannedstmt->rtable); + + Assert(rte->rtekind == RTE_RELATION); + + UnlockRelationOid(rte->relid, rte->rellockmode); + } + } } } } diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c index d549f66d4a..896f51be08 100644 --- a/src/backend/utils/mmgr/portalmem.c +++ b/src/backend/utils/mmgr/portalmem.c @@ -285,6 +285,7 @@ PortalDefineQuery(Portal portal, const char *sourceText, CommandTag commandTag, List *stmts, + List *execlockrelsinfos, CachedPlan *cplan) { AssertArg(PortalIsValid(portal)); @@ -299,6 +300,7 @@ PortalDefineQuery(Portal portal, portal->qc.nprocessed = 0; portal->commandTag = commandTag; portal->stmts = stmts; + portal->execlockrelsinfos = execlockrelsinfos; portal->cplan = cplan; portal->status = PORTAL_DEFINED; } diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h index 666977fb1f..fef75ba147 100644 --- a/src/include/commands/explain.h +++ b/src/include/commands/explain.h @@ -87,7 +87,8 @@ extern void ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es, const char *queryString, ParamListInfo params, QueryEnvironment *queryEnv); -extern void ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, +extern void ExplainOnePlan(PlannedStmt *plannedstmt, ExecLockRelsInfo *execlockrelsinfo, + IntoClause *into, ExplainState *es, const char *queryString, ParamListInfo params, QueryEnvironment *queryEnv, const instr_time *planduration, diff --git a/src/include/executor/execPartition.h b/src/include/executor/execPartition.h index fd5735a946..ded19b8cbb 100644 --- a/src/include/executor/execPartition.h +++ b/src/include/executor/execPartition.h @@ -124,4 +124,6 @@ extern PartitionPruneState *ExecInitPartitionPruning(PlanState *planstate, PartitionPruneInfo *pruneinfo, Bitmapset **initially_valid_subplans); extern Bitmapset *ExecFindMatchingSubPlans(PartitionPruneState *prunestate); +extern Bitmapset *ExecGetLockRelsDoInitialPruning(Plan *plan, ExecGetLockRelsContext *context, + PartitionPruneInfo *pruneinfo); #endif /* EXECPARTITION_H */ diff --git a/src/include/executor/execdesc.h b/src/include/executor/execdesc.h index e79e2c001f..4338463479 100644 --- a/src/include/executor/execdesc.h +++ b/src/include/executor/execdesc.h @@ -35,6 +35,7 @@ typedef struct QueryDesc /* These fields are provided by CreateQueryDesc */ CmdType operation; /* CMD_SELECT, CMD_UPDATE, etc. */ PlannedStmt *plannedstmt; /* planner's output (could be utility, too) */ + ExecLockRelsInfo *execlockrelsinfo; /* ExecutorGetLockRels()'s output given plannedstmt */ const char *sourceText; /* source text of the query */ Snapshot snapshot; /* snapshot to use for query */ Snapshot crosscheck_snapshot; /* crosscheck for RI update/delete */ @@ -57,6 +58,7 @@ typedef struct QueryDesc /* in pquery.c */ extern QueryDesc *CreateQueryDesc(PlannedStmt *plannedstmt, + ExecLockRelsInfo *execlockrelsinfo, const char *sourceText, Snapshot snapshot, Snapshot crosscheck_snapshot, diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 82925b4b63..5cf414cc11 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -185,6 +185,8 @@ ExecGetJunkAttribute(TupleTableSlot *slot, AttrNumber attno, bool *isNull) /* * prototypes from functions in execMain.c */ +extern ExecLockRelsInfo *ExecutorGetLockRels(PlannedStmt *plannedstmt, ParamListInfo params); +extern bool ExecGetLockRels(Plan *node, ExecGetLockRelsContext *context); extern void ExecutorStart(QueryDesc *queryDesc, int eflags); extern void standard_ExecutorStart(QueryDesc *queryDesc, int eflags); extern void ExecutorRun(QueryDesc *queryDesc, diff --git a/src/include/executor/nodeAppend.h b/src/include/executor/nodeAppend.h index 4cb78ee5b6..b53535c2a4 100644 --- a/src/include/executor/nodeAppend.h +++ b/src/include/executor/nodeAppend.h @@ -17,6 +17,7 @@ #include "access/parallel.h" #include "nodes/execnodes.h" +extern bool ExecGetAppendLockRels(Append *node, ExecGetLockRelsContext *context); extern AppendState *ExecInitAppend(Append *node, EState *estate, int eflags); extern void ExecEndAppend(AppendState *node); extern void ExecReScanAppend(AppendState *node); diff --git a/src/include/executor/nodeMergeAppend.h b/src/include/executor/nodeMergeAppend.h index 97fe3b0665..8eb4e9df93 100644 --- a/src/include/executor/nodeMergeAppend.h +++ b/src/include/executor/nodeMergeAppend.h @@ -16,6 +16,7 @@ #include "nodes/execnodes.h" +extern bool ExecGetMergeAppendLockRels(MergeAppend *node, ExecGetLockRelsContext *context); extern MergeAppendState *ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags); extern void ExecEndMergeAppend(MergeAppendState *node); extern void ExecReScanMergeAppend(MergeAppendState *node); diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h index 1d225bc88d..5006499088 100644 --- a/src/include/executor/nodeModifyTable.h +++ b/src/include/executor/nodeModifyTable.h @@ -19,6 +19,7 @@ extern void ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo, EState *estate, TupleTableSlot *slot, CmdType cmdtype); +extern bool ExecGetModifyTableLockRels(ModifyTable *plan, ExecGetLockRelsContext *context); extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags); extern void ExecEndModifyTable(ModifyTableState *node); extern void ExecReScanModifyTable(ModifyTableState *node); diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 44dd73fc80..1253fdb0ed 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -576,6 +576,7 @@ typedef struct EState struct ExecRowMark **es_rowmarks; /* Array of per-range-table-entry * ExecRowMarks, or NULL if none */ PlannedStmt *es_plannedstmt; /* link to top of plan tree */ + struct ExecLockRelsInfo *es_execlockrelsinfo; /* QueryDesc.execlockrelsinfo */ const char *es_sourceText; /* Source text from QueryDesc */ JunkFilter *es_junkFilter; /* top-level junk filter, if any */ @@ -964,6 +965,101 @@ typedef struct DomainConstraintState */ typedef TupleTableSlot *(*ExecProcNodeMtd) (struct PlanState *pstate); +/*---------------- + * ExecLockRelsInfo + * + * Result of performing ExecutorGetLockRels() for a given PlannedStmt + */ +typedef struct ExecLockRelsInfo +{ + NodeTag type; + + /* + * Relations that must be locked to execute the plan tree contained in + * the PlannedStmt. + */ + Bitmapset *lockrels; + + /* PlannedStmt.numPlanNodes */ + int numPlanNodes; + + /* + * List of PlanInitPruningOutput, each representing the output of + * performing initial pruning on a given plan node, for all nodes in the + * plan tree that have been marked as needing initial pruning. + * + * 'ipoIndexes' is an array of 'numPlanNodes' elements, indexed with + * plan_node_id of the individual nodes in the plan tree, each a 1-based + * index into 'initPruningOutputs' list for a given plan node. 0 means + * that a given plan node has no entry in the list because of not needing + * any initial pruning done on it. + */ + List *initPruningOutputs; + int *ipoIndexes; +} ExecLockRelsInfo; + +/*---------------- + * ExecGetLockRelsContext + * + * Information pertaining to ExecutorGetLockRels() invocation for a given + * plan. + */ +typedef struct ExecGetLockRelsContext +{ + NodeTag type; + + PlannedStmt *stmt; /* target plan */ + ParamListInfo params; /* EXTERN parameters available for pruning */ + + /* Output parameters for ExecGetLockRels and its subroutines. */ + Bitmapset *lockrels; + + /* See the omment in the definition of ExecLockRelsInfo struct. */ + List *initPruningOutputs; + int *ipoIndexes; +} ExecGetLockRelsContext; + +/* + * Appends the provided PlanInitPruningOutput to + * ExecGetLockRelsContext.initPruningOutput + */ +#define ExecStorePlanInitPruningOutput(cxt, initPruningOutput, plannode) \ + do { \ + (cxt)->initPruningOutputs = lappend((cxt)->initPruningOutputs, initPruningOutput); \ + (cxt)->ipoIndexes[(plannode)->plan_node_id] = list_length((cxt)->initPruningOutputs); \ + } while (0) + +/* + * Finds the PlanInitPruningOutput for a given Plan node in + * ExecLockRelsInfo.initPruningOutputs. + */ +#define ExecFetchPlanInitPruningOutput(execlockrelsinfo, plannode) \ + (((execlockrelsinfo) != NULL && (execlockrelsinfo)->initPruningOutputs != NIL) ? \ + list_nth((execlockrelsinfo)->initPruningOutputs, \ + (execlockrelsinfo)->ipoIndexes[(plannode)->plan_node_id] - 1) : NULL) + +/* --------------- + * PlanInitPruningOutput + * + * Node to remember the result of performing initial partition pruning steps + * during ExecutorGetLockRels() on nodes that support pruning. + * + * ExecLockRelsDoInitPruning(), which runs during ExecutorGetLockRels(), + * creates it and stores it in the corresponding ExecLockRelsInfo. + * + * ExecInitPartitionPruning(), which runs during ExecuorStart(), fetches it + * from the EState's ExecLockRelsInfo (if any) and uses the value of + * initially_valid_subplans contained in it as-is to select the subplans to be + * initialized for execution, instead of re-evaluating that by performing + * initial pruning again. + */ +typedef struct PlanInitPruningOutput +{ + NodeTag type; + + Bitmapset *initially_valid_subplans; +} PlanInitPruningOutput; + /* ---------------- * PlanState node * diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 05f0b79e82..00c4d8293e 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -96,6 +96,11 @@ typedef enum NodeTag T_PartitionPruneStepCombine, T_PlanInvalItem, + /* TAGS FOR EXECUTOR PREP NODES (execnodes.h) */ + T_ExecGetLockRelsContext, + T_ExecLockRelsInfo, + T_PlanInitPruningOutput, + /* * TAGS FOR PLAN STATE NODES (execnodes.h) * diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 5327d9ba8b..019719c1a4 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -129,6 +129,10 @@ typedef struct PlannerGlobal char maxParallelHazard; /* worst PROPARALLEL hazard level */ + bool containsInitialPruning; /* Do some Plan nodes in the tree + * have initial (pre-exec) pruning + * steps? */ + PartitionDirectory partition_directory; /* partition descriptors */ Bitmapset *elidedAppendPartedRels; /* Combined partitioned_rels of all diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index bd87c35d6c..bfdb5bbf28 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -59,10 +59,16 @@ typedef struct PlannedStmt bool parallelModeNeeded; /* parallel mode required to execute? */ + bool containsInitialPruning; /* Do some Plan nodes in the tree + * have initial (pre-exec) pruning + * steps? */ + int jitFlags; /* which forms of JIT should be performed */ struct Plan *planTree; /* tree of Plan nodes */ + int numPlanNodes; /* number of nodes in planTree */ + List *rtable; /* list of RangeTblEntry nodes */ /* rtable indexes of target relations for INSERT/UPDATE/DELETE */ @@ -1189,6 +1195,13 @@ typedef struct PlanRowMark * prune_infos List of Lists containing PartitionedRelPruneInfo nodes, * one sublist per run-time-prunable partition hierarchy * appearing in the parent plan node's subplans. + * + * needs_init_pruning Does any of the PartitionedRelPruneInfos in + * prune_infos have its initial_pruning_steps set? + * + * needs_exec_pruning Does any of the PartitionedRelPruneInfos in + * prune_infos have its exec_pruning_steps set? + * * other_subplans Indexes of any subplans that are not accounted for * by any of the PartitionedRelPruneInfo nodes in * "prune_infos". These subplans must not be pruned. @@ -1197,6 +1210,8 @@ typedef struct PartitionPruneInfo { NodeTag type; List *prune_infos; + bool needs_init_pruning; + bool needs_exec_pruning; Bitmapset *other_subplans; } PartitionPruneInfo; diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h index 92291a750d..bf80c53bed 100644 --- a/src/include/tcop/tcopprot.h +++ b/src/include/tcop/tcopprot.h @@ -64,7 +64,7 @@ extern PlannedStmt *pg_plan_query(Query *querytree, const char *query_string, ParamListInfo boundParams); extern List *pg_plan_queries(List *querytrees, const char *query_string, int cursorOptions, - ParamListInfo boundParams); + ParamListInfo boundParams, List **execlockrelsinfo_list); extern bool check_max_stack_depth(int *newval, void **extra, GucSource source); extern void assign_max_stack_depth(int newval, void *extra); diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h index 95b99e3d25..56b0dcc6bd 100644 --- a/src/include/utils/plancache.h +++ b/src/include/utils/plancache.h @@ -148,6 +148,9 @@ typedef struct CachedPlan { int magic; /* should equal CACHEDPLAN_MAGIC */ List *stmt_list; /* list of PlannedStmts */ + List *execlockrelsinfo_list; /* list of ExecutorGetLockRelsResult with one + * element for each of stmt_list; NIL + * if not a generic plan */ bool is_oneshot; /* is it a "oneshot" plan? */ bool is_saved; /* is CachedPlan in a long-lived context? */ bool is_valid; /* is the stmt_list currently valid? */ @@ -158,6 +161,9 @@ typedef struct CachedPlan int generation; /* parent's generation number for this plan */ int refcount; /* count of live references to this struct */ MemoryContext context; /* context containing this CachedPlan */ + MemoryContext execlockrelsinfo_context; /* context containing + * execlockrelsinfo_list, + * a child of the above context */ } CachedPlan; /* diff --git a/src/include/utils/portal.h b/src/include/utils/portal.h index aeddbdafe5..9abace6734 100644 --- a/src/include/utils/portal.h +++ b/src/include/utils/portal.h @@ -137,6 +137,10 @@ typedef struct PortalData CommandTag commandTag; /* command tag for original query */ QueryCompletion qc; /* command completion data for executed query */ List *stmts; /* list of PlannedStmts */ + List *execlockrelsinfos; /* list of ExecutorGetLockRelsResults with one element + * for each of 'stmts'; same as + * cplan->execlockrelsinfo_list if cplan is + * not NULL */ CachedPlan *cplan; /* CachedPlan, if stmts are from one */ ParamListInfo portalParams; /* params to pass to query */ @@ -241,6 +245,7 @@ extern void PortalDefineQuery(Portal portal, const char *sourceText, CommandTag commandTag, List *stmts, + List *execlockrelsinfos, CachedPlan *cplan); extern PlannedStmt *PortalGetPrimaryStmt(Portal portal); extern void PortalCreateHoldStore(Portal portal); -- 2.24.1