From d766e737ade779de3da4addbf71a05bb2a74ab75 Mon Sep 17 00:00:00 2001 From: Amit Langote Date: Wed, 7 Aug 2024 18:25:51 +0900 Subject: [PATCH v51 1/3] Defer locking of runtime-prunable relations to executor When preparing a cached plan for execution, plancache.c locks the relations contained in the plan's range table to ensure it is safe for execution. However, this simplistic approach, implemented in AcquireExecutorLocks(), results in unnecessarily locking relations that might be pruned during "initial" runtime pruning. To optimize this, the locking is now deferred for relations that are subject to "initial" runtime pruning. The planner now provides a set of "unprunable" relations, available through the new PlannedStmt.unprunableRelids field. AcquireExecutorLocks() will now only lock those relations. PlannedStmt.unprunableRelids is populated by subtracting the set of initially prunable relids from the set of all RT indexes. The prunable relids set is constructed by examining all PartitionPruneInfos during set_plan_refs() and storing the RT indexes of partitions subject to "initial" pruning steps. While at it, some duplicated code in set_append_references() and set_mergeappend_references() that constructs the prunable relids set has been refactored into a common function. To enable the executor to determine whether the plan tree it's executing is a cached one, the CachedPlan is now made available via the QueryDesc. The executor can call CachedPlanRequiresLocking(), which returns true if the CachedPlan is a reusable generic plan that might contain relations needing to be locked. If so, the executor will lock any relation that is not in PlannedStmt.unprunableRelids. Finally, an Assert has been added in ExecCheckPermissions() to ensure that all relations whose permissions are checked have been properly locked. This helps catch any accidental omission of relations from the unprunableRelids set that should have their permissions checked. This deferment introduces a window in which prunable relations may be altered by concurrent DDL, potentially causing the plan to become invalid. As a result, the executor might attempt to run an invalid plan, leading to errors such as being unable to locate a partition-only index during ExecInitIndexScan(). Future commits will introduce changes to ready the executor to check plan validity during ExecutorStart() and retry with a newly created plan if the original one becomes invalid after taking deferred locks. --- src/backend/commands/copyto.c | 2 +- src/backend/commands/createas.c | 2 +- src/backend/commands/explain.c | 7 ++-- src/backend/commands/extension.c | 1 + src/backend/commands/matview.c | 2 +- src/backend/commands/prepare.c | 3 +- src/backend/executor/execMain.c | 18 ++++++++ src/backend/executor/execParallel.c | 9 +++- src/backend/executor/execUtils.c | 30 +++++++++++++- src/backend/executor/functions.c | 1 + src/backend/executor/spi.c | 1 + src/backend/optimizer/plan/planner.c | 2 + src/backend/optimizer/plan/setrefs.c | 62 +++++++++++++++------------- src/backend/partitioning/partprune.c | 24 ++++++++++- src/backend/storage/lmgr/lmgr.c | 1 + src/backend/tcop/pquery.c | 10 ++++- src/backend/utils/cache/lsyscache.c | 1 - src/backend/utils/cache/plancache.c | 25 +++++++---- src/include/commands/explain.h | 5 ++- src/include/executor/execdesc.h | 2 + src/include/nodes/execnodes.h | 2 + src/include/nodes/pathnodes.h | 6 +++ src/include/nodes/plannodes.h | 11 +++++ src/include/utils/plancache.h | 10 +++++ 24 files changed, 186 insertions(+), 51 deletions(-) diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c index 91de442f43..db976f928a 100644 --- a/src/backend/commands/copyto.c +++ b/src/backend/commands/copyto.c @@ -552,7 +552,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 0b629b1f79..57a3375cad 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -324,7 +324,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 11df4a04d4..a83ea07db1 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -507,7 +507,7 @@ standard_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), es->memory ? &mem_counters : NULL); } @@ -615,7 +615,8 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es, * to call it. */ void -ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, +ExplainOnePlan(PlannedStmt *plannedstmt, CachedPlan *cplan, + IntoClause *into, ExplainState *es, const char *queryString, ParamListInfo params, QueryEnvironment *queryEnv, const instr_time *planduration, const BufferUsage *bufusage, @@ -671,7 +672,7 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, dest = None_Receiver; /* Create a QueryDesc for the query */ - queryDesc = CreateQueryDesc(plannedstmt, queryString, + queryDesc = CreateQueryDesc(plannedstmt, cplan, queryString, GetActiveSnapshot(), InvalidSnapshot, dest, params, queryEnv, instrument_option); diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c index 1643c8c69a..3f7f4306fe 100644 --- a/src/backend/commands/extension.c +++ b/src/backend/commands/extension.c @@ -798,6 +798,7 @@ execute_sql_string(const char *sql) QueryDesc *qdesc; qdesc = CreateQueryDesc(stmt, + NULL, sql, GetActiveSnapshot(), NULL, dest, NULL, NULL, 0); diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 91f0fd6ea3..a7a79583ec 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -438,7 +438,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/prepare.c b/src/backend/commands/prepare.c index 07257d4db9..311b9ebd5b 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -655,7 +655,8 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es, PlannedStmt *pstmt = lfirst_node(PlannedStmt, p); if (pstmt->commandType != CMD_UTILITY) - ExplainOnePlan(pstmt, into, es, query_string, paramLI, queryEnv, + ExplainOnePlan(pstmt, cplan, into, es, query_string, paramLI, + queryEnv, &planduration, (es->buffers ? &bufusage : NULL), es->memory ? &mem_counters : NULL); else diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 29e186fa73..271f9d93fc 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -52,6 +52,7 @@ #include "miscadmin.h" #include "parser/parse_relation.h" #include "rewrite/rewriteHandler.h" +#include "storage/lmgr.h" #include "tcop/utility.h" #include "utils/acl.h" #include "utils/backend_status.h" @@ -597,6 +598,21 @@ ExecCheckPermissions(List *rangeTable, List *rteperminfos, (rte->rtekind == RTE_SUBQUERY && rte->relkind == RELKIND_VIEW)); + /* + * Ensure that we have at least an AccessShareLock on relations + * whose permissions need to be checked. + * + * Skip this check in a parallel worker because locks won't be + * taken until ExecInitNode() performs plan initialization. + * + * XXX: ExecCheckPermissions() in a parallel worker may be + * redundant with the checks done in the leader process, so this + * should be reviewed to ensure it’s necessary. + */ + Assert(IsParallelWorker() || + CheckRelationOidLockedByMe(rte->relid, AccessShareLock, + true)); + (void) getRTEPermissionInfo(rteperminfos, rte); /* Many-to-one mapping not allowed */ Assert(!bms_is_member(rte->perminfoindex, indexset)); @@ -829,6 +845,7 @@ InitPlan(QueryDesc *queryDesc, int eflags) { CmdType operation = queryDesc->operation; PlannedStmt *plannedstmt = queryDesc->plannedstmt; + CachedPlan *cachedplan = queryDesc->cplan; Plan *plan = plannedstmt->planTree; List *rangeTable = plannedstmt->rtable; EState *estate = queryDesc->estate; @@ -848,6 +865,7 @@ InitPlan(QueryDesc *queryDesc, int eflags) ExecInitRangeTable(estate, rangeTable, plannedstmt->permInfos); estate->es_plannedstmt = plannedstmt; + estate->es_cachedplan = cachedplan; /* * 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 bfb3419efb..03b48e12b4 100644 --- a/src/backend/executor/execParallel.c +++ b/src/backend/executor/execParallel.c @@ -1256,8 +1256,15 @@ ExecParallelGetQueryDesc(shm_toc *toc, DestReceiver *receiver, paramspace = shm_toc_lookup(toc, PARALLEL_KEY_PARAMLISTINFO, false); paramLI = RestoreParamList(¶mspace); - /* Create a QueryDesc for the query. */ + /* + * Create a QueryDesc for the query. We pass NULL for cachedplan, because + * we don't have a pointer to the CachedPlan in the leader's process. It's + * fine because the only reason the executor needs to see it is to decide + * if it should take locks on certain relations, but paraller workers + * always take locks anyway. + */ return CreateQueryDesc(pstmt, + NULL, queryString, GetActiveSnapshot(), InvalidSnapshot, receiver, paramLI, NULL, instrument_options); diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index 5737f9f4eb..6dfd5a26b7 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -752,6 +752,26 @@ ExecInitRangeTable(EState *estate, List *rangeTable, List *permInfos) estate->es_rowmarks = NULL; } +/* + * ExecShouldLockRelation + * Determine if the relation should be locked. + * + * The relation does not need to be locked if we are not running a cached + * plan or if it has already been locked as an unprunable relation. + * + * Lock the relation if it might be one of the prunable relations mentioned + * in the cached plan. + */ +static bool +ExecShouldLockRelation(EState *estate, Index rtindex) +{ + if (estate->es_cachedplan == NULL || + bms_is_member(rtindex, estate->es_plannedstmt->unprunableRelids)) + return false; + + return CachedPlanRequiresLocking(estate->es_cachedplan); +} + /* * ExecGetRangeTableRelation * Open the Relation for a range table entry, if not already done @@ -773,7 +793,7 @@ ExecGetRangeTableRelation(EState *estate, Index rti) Assert(rte->rtekind == RTE_RELATION); - if (!IsParallelWorker()) + if (!IsParallelWorker() && !ExecShouldLockRelation(estate, rti)) { /* * In a normal query, we should already have the appropriate lock, @@ -789,9 +809,17 @@ ExecGetRangeTableRelation(EState *estate, Index rti) else { /* + * Lock relation either if we are a parallel worker or if + * ExecShouldLockRelation() says we should. + * * If we are a parallel worker, we need to obtain our own local * lock on the relation. This ensures sane behavior in case the * parent process exits before we do. + * + * ExecShouldLockRelation() would return true if the RT index is + * that of a prunable relation and we're running a cached generic + * plan. AcquireExecutorLocks() of plancache.c would have locked + * only the unprunable relations in the plan tree. */ rel = table_open(rte->relid, rte->rellockmode); } diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index 692854e2b3..6f6f45e0ad 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -840,6 +840,7 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache) dest = None_Receiver; es->qd = CreateQueryDesc(es->stmt, + NULL, fcache->src, GetActiveSnapshot(), InvalidSnapshot, diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index d6516b1bca..902793b02b 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -2684,6 +2684,7 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options, snap = InvalidSnapshot; qdesc = CreateQueryDesc(stmt, + cplan, plansource->query_string, snap, crosscheck_snapshot, dest, diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index b5827d3980..cb9b6f0147 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -546,6 +546,8 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions, result->parallelModeNeeded = glob->parallelModeNeeded; result->planTree = top_plan; result->rtable = glob->finalrtable; + result->unprunableRelids = bms_difference(bms_add_range(NULL, 1, list_length(result->rtable)), + glob->prunableRelids); result->permInfos = glob->finalrteperminfos; 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 7aed84584c..b6be0e5730 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -154,6 +154,9 @@ static Plan *set_append_references(PlannerInfo *root, static Plan *set_mergeappend_references(PlannerInfo *root, MergeAppend *mplan, int rtoffset); +static void set_part_prune_references(PartitionPruneInfo *pinfo, + PlannerGlobal *glob, + int rtoffset); static void set_hash_references(PlannerInfo *root, Plan *plan, int rtoffset); static Relids offset_relid_set(Relids relids, int rtoffset); static Node *fix_scan_expr(PlannerInfo *root, Node *node, @@ -1783,20 +1786,8 @@ set_append_references(PlannerInfo *root, aplan->apprelids = offset_relid_set(aplan->apprelids, rtoffset); if (aplan->part_prune_info) - { - foreach(l, aplan->part_prune_info->prune_infos) - { - List *prune_infos = lfirst(l); - ListCell *l2; - - foreach(l2, prune_infos) - { - PartitionedRelPruneInfo *pinfo = lfirst(l2); - - pinfo->rtindex += rtoffset; - } - } - } + set_part_prune_references(aplan->part_prune_info, root->glob, + rtoffset); /* We don't need to recurse to lefttree or righttree ... */ Assert(aplan->plan.lefttree == NULL); @@ -1859,20 +1850,8 @@ set_mergeappend_references(PlannerInfo *root, mplan->apprelids = offset_relid_set(mplan->apprelids, rtoffset); if (mplan->part_prune_info) - { - foreach(l, mplan->part_prune_info->prune_infos) - { - List *prune_infos = lfirst(l); - ListCell *l2; - - foreach(l2, prune_infos) - { - PartitionedRelPruneInfo *pinfo = lfirst(l2); - - pinfo->rtindex += rtoffset; - } - } - } + set_part_prune_references(mplan->part_prune_info, root->glob, + rtoffset); /* We don't need to recurse to lefttree or righttree ... */ Assert(mplan->plan.lefttree == NULL); @@ -1881,6 +1860,33 @@ set_mergeappend_references(PlannerInfo *root, return (Plan *) mplan; } +/* + * Updates RT indexes in PartitionedRelPruneInfos contained in pinfo and adds + * the RT indexes of "prunable" relations into glob->prunableRelids. + */ +static void +set_part_prune_references(PartitionPruneInfo *pinfo, PlannerGlobal *glob, + int rtoffset) +{ + ListCell *l; + + foreach(l, pinfo->prune_infos) + { + List *prune_infos = lfirst(l); + ListCell *l2; + + foreach(l2, prune_infos) + { + PartitionedRelPruneInfo *prelinfo = lfirst(l2); + + prelinfo->rtindex += rtoffset; + if (prelinfo->initial_pruning_steps != NIL) + glob->prunableRelids = bms_add_members(glob->prunableRelids, + prelinfo->present_part_rtis); + } + } +} + /* * set_hash_references * Do set_plan_references processing on a Hash node diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 9a1a7faac7..8e27e35df2 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -634,6 +634,7 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, PartitionedRelPruneInfo *pinfo = lfirst(lc); RelOptInfo *subpart = find_base_rel(root, pinfo->rtindex); Bitmapset *present_parts; + Bitmapset *present_part_rtis; int nparts = subpart->nparts; int *subplan_map; int *subpart_map; @@ -650,7 +651,7 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, subpart_map = (int *) palloc(nparts * sizeof(int)); memset(subpart_map, -1, nparts * sizeof(int)); relid_map = (Oid *) palloc0(nparts * sizeof(Oid)); - present_parts = NULL; + present_parts = present_part_rtis = NULL; i = -1; while ((i = bms_next_member(subpart->live_parts, i)) >= 0) @@ -664,15 +665,35 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; relid_map[i] = planner_rt_fetch(partrel->relid, root)->relid; + + /* + * Track the RT indexes of partitions to ensure they are included + * in the prunableRelids set of relations that are locked during + * execution. This ensures that if the plan is cached, these + * partitions are locked when the plan is reused. + * + * Partitions without a subplan and sub-partitioned partitions + * where none of the sub-partitions have a subplan due to + * constraint exclusion are not included in this set. Instead, + * they are added to the unprunableRelids set, and the relations + * in this set are locked by AcquireExecutorLocks() before + * executing a cached plan. + */ if (subplanidx >= 0) { present_parts = bms_add_member(present_parts, i); + present_part_rtis = bms_add_member(present_part_rtis, + partrel->relid); /* Record finding this subplan */ subplansfound = bms_add_member(subplansfound, subplanidx); } else if (subpartidx >= 0) + { present_parts = bms_add_member(present_parts, i); + present_part_rtis = bms_add_member(present_part_rtis, + partrel->relid); + } } /* @@ -684,6 +705,7 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, /* Record the maps and other information. */ pinfo->present_parts = present_parts; + pinfo->present_part_rtis = present_part_rtis; pinfo->nparts = nparts; pinfo->subplan_map = subplan_map; pinfo->subpart_map = subpart_map; diff --git a/src/backend/storage/lmgr/lmgr.c b/src/backend/storage/lmgr/lmgr.c index 094522acb4..a1c89f5d72 100644 --- a/src/backend/storage/lmgr/lmgr.c +++ b/src/backend/storage/lmgr/lmgr.c @@ -26,6 +26,7 @@ #include "storage/procarray.h" #include "storage/sinvaladt.h" #include "utils/inval.h" +#include "utils/lsyscache.h" /* diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c index a1f8d03db1..6e8f6b1b8f 100644 --- a/src/backend/tcop/pquery.c +++ b/src/backend/tcop/pquery.c @@ -36,6 +36,7 @@ Portal ActivePortal = NULL; static void ProcessQuery(PlannedStmt *plan, + CachedPlan *cplan, const char *sourceText, ParamListInfo params, QueryEnvironment *queryEnv, @@ -65,6 +66,7 @@ static void DoPortalRewind(Portal portal); */ QueryDesc * CreateQueryDesc(PlannedStmt *plannedstmt, + CachedPlan *cplan, const char *sourceText, Snapshot snapshot, Snapshot crosscheck_snapshot, @@ -77,6 +79,7 @@ CreateQueryDesc(PlannedStmt *plannedstmt, qd->operation = plannedstmt->commandType; /* operation */ qd->plannedstmt = plannedstmt; /* plan */ + qd->cplan = cplan; /* CachedPlan supplying the plannedstmt */ qd->sourceText = sourceText; /* query text */ qd->snapshot = RegisterSnapshot(snapshot); /* snapshot */ /* RI check snapshot */ @@ -122,6 +125,7 @@ FreeQueryDesc(QueryDesc *qdesc) * PORTAL_ONE_RETURNING, or PORTAL_ONE_MOD_WITH portal * * plan: the plan tree for the query + * cplan: CachedPlan supplying the plan * sourceText: the source text of the query * params: any parameters needed * dest: where to send results @@ -134,6 +138,7 @@ FreeQueryDesc(QueryDesc *qdesc) */ static void ProcessQuery(PlannedStmt *plan, + CachedPlan *cplan, const char *sourceText, ParamListInfo params, QueryEnvironment *queryEnv, @@ -145,7 +150,7 @@ ProcessQuery(PlannedStmt *plan, /* * Create the QueryDesc object */ - queryDesc = CreateQueryDesc(plan, sourceText, + queryDesc = CreateQueryDesc(plan, cplan, sourceText, GetActiveSnapshot(), InvalidSnapshot, dest, params, queryEnv, 0); @@ -493,6 +498,7 @@ PortalStart(Portal portal, ParamListInfo params, * the destination to DestNone. */ queryDesc = CreateQueryDesc(linitial_node(PlannedStmt, portal->stmts), + portal->cplan, portal->sourceText, GetActiveSnapshot(), InvalidSnapshot, @@ -1276,6 +1282,7 @@ PortalRunMulti(Portal portal, { /* statement can set tag string */ ProcessQuery(pstmt, + portal->cplan, portal->sourceText, portal->portalParams, portal->queryEnv, @@ -1285,6 +1292,7 @@ PortalRunMulti(Portal portal, { /* stmt added by rewrite cannot set tag */ ProcessQuery(pstmt, + portal->cplan, portal->sourceText, portal->portalParams, portal->queryEnv, diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 48a280d089..f647821382 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -2113,7 +2113,6 @@ get_rel_relam(Oid relid) return result; } - /* ---------- TRANSFORM CACHE ---------- */ Oid diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c index 5af1a168ec..6d2e385fe8 100644 --- a/src/backend/utils/cache/plancache.c +++ b/src/backend/utils/cache/plancache.c @@ -104,7 +104,8 @@ static List *RevalidateCachedQuery(CachedPlanSource *plansource, QueryEnvironment *queryEnv); static bool CheckCachedPlan(CachedPlanSource *plansource); static CachedPlan *BuildCachedPlan(CachedPlanSource *plansource, List *qlist, - ParamListInfo boundParams, QueryEnvironment *queryEnv); + ParamListInfo boundParams, QueryEnvironment *queryEnv, + bool generic); static bool choose_custom_plan(CachedPlanSource *plansource, ParamListInfo boundParams); static double cached_plan_cost(CachedPlan *plan, bool include_planner); @@ -904,7 +905,8 @@ CheckCachedPlan(CachedPlanSource *plansource) */ static CachedPlan * BuildCachedPlan(CachedPlanSource *plansource, List *qlist, - ParamListInfo boundParams, QueryEnvironment *queryEnv) + ParamListInfo boundParams, QueryEnvironment *queryEnv, + bool generic) { CachedPlan *plan; List *plist; @@ -1026,6 +1028,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist, plan->refcount = 0; plan->context = plan_context; plan->is_oneshot = plansource->is_oneshot; + plan->is_generic = generic; plan->is_saved = false; plan->is_valid = true; @@ -1196,7 +1199,7 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams, else { /* Build a new generic plan */ - plan = BuildCachedPlan(plansource, qlist, NULL, queryEnv); + plan = BuildCachedPlan(plansource, qlist, NULL, queryEnv, true); /* Just make real sure plansource->gplan is clear */ ReleaseGenericPlan(plansource); /* Link the new generic plan into the plansource */ @@ -1241,7 +1244,7 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams, if (customplan) { /* Build a custom plan */ - plan = BuildCachedPlan(plansource, qlist, boundParams, queryEnv); + plan = BuildCachedPlan(plansource, qlist, boundParams, queryEnv, true); /* Accumulate total costs of custom plans */ plansource->total_custom_cost += cached_plan_cost(plan, true); @@ -1387,8 +1390,8 @@ CachedPlanAllowsSimpleValidityCheck(CachedPlanSource *plansource, } /* - * Reject if AcquireExecutorLocks would have anything to do. This is - * probably unnecessary given the previous check, but let's be safe. + * Reject if there are any lockable relations. This is probably + * unnecessary given the previous check, but let's be safe. */ foreach(lc, plan->stmt_list) { @@ -1776,7 +1779,7 @@ AcquireExecutorLocks(List *stmt_list, bool acquire) foreach(lc1, stmt_list) { PlannedStmt *plannedstmt = lfirst_node(PlannedStmt, lc1); - ListCell *lc2; + int rtindex; if (plannedstmt->commandType == CMD_UTILITY) { @@ -1794,9 +1797,13 @@ AcquireExecutorLocks(List *stmt_list, bool acquire) continue; } - foreach(lc2, plannedstmt->rtable) + rtindex = -1; + while ((rtindex = bms_next_member(plannedstmt->unprunableRelids, + rtindex)) >= 0) { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc2); + RangeTblEntry *rte = list_nth_node(RangeTblEntry, + plannedstmt->rtable, + rtindex - 1); if (!(rte->rtekind == RTE_RELATION || (rte->rtekind == RTE_SUBQUERY && OidIsValid(rte->relid)))) diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h index 9b8b351d9a..bf326eeb70 100644 --- a/src/include/commands/explain.h +++ b/src/include/commands/explain.h @@ -101,8 +101,9 @@ extern void ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es, const char *queryString, ParamListInfo params, QueryEnvironment *queryEnv); -extern void ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, - ExplainState *es, const char *queryString, +extern void ExplainOnePlan(PlannedStmt *plannedstmt, CachedPlan *cplan, + IntoClause *into, ExplainState *es, + const char *queryString, ParamListInfo params, QueryEnvironment *queryEnv, const instr_time *planduration, const BufferUsage *bufusage, diff --git a/src/include/executor/execdesc.h b/src/include/executor/execdesc.h index 0a7274e26c..0e7245435d 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) */ + CachedPlan *cplan; /* CachedPlan that supplies the 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, + CachedPlan *cplan, const char *sourceText, Snapshot snapshot, Snapshot crosscheck_snapshot, diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index af7d8fd1e7..ee089505a0 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -42,6 +42,7 @@ #include "storage/condition_variable.h" #include "utils/hsearch.h" #include "utils/queryenvironment.h" +#include "utils/plancache.h" #include "utils/reltrigger.h" #include "utils/sharedtuplestore.h" #include "utils/snapshot.h" @@ -633,6 +634,7 @@ typedef struct EState * ExecRowMarks, or NULL if none */ List *es_rteperminfos; /* List of RTEPermissionInfo */ PlannedStmt *es_plannedstmt; /* link to top of plan tree */ + CachedPlan *es_cachedplan; /* CachedPlan supplying the plannedstmt */ const char *es_sourceText; /* Source text from QueryDesc */ JunkFilter *es_junkFilter; /* top-level junk filter, if any */ diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 540d021592..2466157b25 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -116,6 +116,12 @@ typedef struct PlannerGlobal /* "flat" rangetable for executor */ List *finalrtable; + /* + * RT indexes of relations subject to removal from the plan due to runtime + * pruning at plan initialization time + */ + Bitmapset *prunableRelids; + /* "flat" list of RTEPermissionInfos */ List *finalrteperminfos; diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 62cd6a6666..ae608812f1 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -71,6 +71,10 @@ typedef struct PlannedStmt List *rtable; /* list of RangeTblEntry nodes */ + Bitmapset *unprunableRelids; /* RT indexes of relations that are not + * subject to runtime pruning; for + * AcquireExecutorLocks() */ + List *permInfos; /* list of RTEPermissionInfo nodes for rtable * entries needing one */ @@ -1459,6 +1463,13 @@ typedef struct PartitionedRelPruneInfo /* Indexes of all partitions which subplans or subparts are present for */ Bitmapset *present_parts; + /* + * RT indexes of all partitions which subplans or subparts are present + * for; only used during planning to help in the construction of + * PlannerGlobal.prunableRelids. + */ + Bitmapset *present_part_rtis; + /* Length of the following arrays: */ int nparts; diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h index a90dfdf906..0b5ee007ca 100644 --- a/src/include/utils/plancache.h +++ b/src/include/utils/plancache.h @@ -149,6 +149,7 @@ typedef struct CachedPlan int magic; /* should equal CACHEDPLAN_MAGIC */ List *stmt_list; /* list of PlannedStmts */ bool is_oneshot; /* is it a "oneshot" plan? */ + bool is_generic; /* is it a reusable generic plan? */ bool is_saved; /* is CachedPlan in a long-lived context? */ bool is_valid; /* is the stmt_list currently valid? */ Oid planRoleId; /* Role ID the plan was created for */ @@ -235,4 +236,13 @@ extern bool CachedPlanIsSimplyValid(CachedPlanSource *plansource, extern CachedExpression *GetCachedExpression(Node *expr); extern void FreeCachedExpression(CachedExpression *cexpr); +/* + * CachedPlanRequiresLocking: should the executor acquire locks? + */ +static inline bool +CachedPlanRequiresLocking(CachedPlan *cplan) +{ + return cplan->is_generic; +} + #endif /* PLANCACHE_H */ -- 2.43.0