Track unpruned relids to avoid processing pruned relations
authorAmit Langote <amitlan@postgresql.org>
Fri, 7 Feb 2025 08:15:09 +0000 (17:15 +0900)
committerAmit Langote <amitlan@postgresql.org>
Fri, 7 Feb 2025 08:15:09 +0000 (17:15 +0900)
This commit introduces changes to track unpruned relations explicitly,
making it possible for top-level plan nodes, such as ModifyTable and
LockRows, to avoid processing partitions pruned during initial
pruning.  Scan-level nodes, such as Append and MergeAppend, already
avoid the unnecessary processing by accessing partition pruning
results directly via part_prune_index. In contrast, top-level nodes
cannot access pruning results directly and need to determine which
partitions remain unpruned.

To address this, this commit introduces a new bitmapset field,
es_unpruned_relids, which the executor uses to track the set of
unpruned relations.  This field is referenced during plan
initialization to skip initializing certain nodes for pruned
partitions. It is initialized with PlannedStmt.unprunableRelids,
a new field that the planner populates with RT indexes of relations
that cannot be pruned during runtime pruning. These include relations
not subject to partition pruning and those required for execution
regardless of pruning.

PlannedStmt.unprunableRelids is computed during set_plan_refs() by
removing the RT indexes of runtime-prunable relations, identified
from PartitionPruneInfos, from the full set of relation RT indexes.
ExecDoInitialPruning() then updates es_unpruned_relids by adding
partitions that survive initial pruning.

To support this, PartitionedRelPruneInfo and PartitionedRelPruningData
now include a leafpart_rti_map[] array that maps partition indexes to
their corresponding RT indexes. The former is used in set_plan_refs()
when constructing unprunableRelids, while the latter is used in
ExecDoInitialPruning() to convert partition indexes returned by
get_matching_partitions() into RT indexes, which are then added to
es_unpruned_relids.

These changes make it possible for ModifyTable and LockRows nodes to
process only relations that remain unpruned after initial pruning.
ExecInitModifyTable() trims lists, such as resultRelations,
withCheckOptionLists, returningLists, and updateColnosLists, to
consider only unpruned partitions. It also creates ResultRelInfo
structs only for these partitions. Similarly, child RowMarks for
pruned relations are skipped.

By avoiding unnecessary initialization of structures for pruned
partitions, these changes improve the performance of updates and
deletes on partitioned tables during initial runtime pruning.

Due to ExecInitModifyTable() changes as described above, EXPLAIN on a
plan for UPDATE and DELETE that uses runtime initial pruning no longer
lists partitions pruned during initial pruning.

Reviewed-by: Robert Haas <robertmhaas@gmail.com> (earlier versions)
Reviewed-by: Tomas Vondra <tomas@vondra.me>
Discussion: https://postgr.es/m/CA+HiwqFGkMSge6TgC9KQzde0ohpAycLQuV7ooitEEpbKB0O_mg@mail.gmail.com

21 files changed:
src/backend/commands/copyfrom.c
src/backend/executor/execMain.c
src/backend/executor/execParallel.c
src/backend/executor/execPartition.c
src/backend/executor/execUtils.c
src/backend/executor/nodeAppend.c
src/backend/executor/nodeLockRows.c
src/backend/executor/nodeMergeAppend.c
src/backend/executor/nodeModifyTable.c
src/backend/optimizer/plan/planner.c
src/backend/optimizer/plan/setrefs.c
src/backend/partitioning/partprune.c
src/backend/replication/logical/worker.c
src/backend/replication/pgoutput/pgoutput.c
src/include/executor/execPartition.h
src/include/executor/executor.h
src/include/nodes/execnodes.h
src/include/nodes/pathnodes.h
src/include/nodes/plannodes.h
src/test/regress/expected/partition_prune.out
src/test/regress/sql/partition_prune.sql

index b70f4691b72f463cf895d869c4601b8c5aa09409..8875d79d59ad78bb4b221e86bad9aeaa7dfbefb6 100644 (file)
@@ -774,7 +774,8 @@ CopyFrom(CopyFromState cstate)
     * index-entry-making machinery.  (There used to be a huge amount of code
     * here that basically duplicated execUtils.c ...)
     */
-   ExecInitRangeTable(estate, cstate->range_table, cstate->rteperminfos);
+   ExecInitRangeTable(estate, cstate->range_table, cstate->rteperminfos,
+                      bms_make_singleton(1));
    resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo);
    ExecInitResultRelation(estate, resultRelInfo, 1);
 
index 604cb0625b803a847b1eb7b8280587bbc669ddbb..74ef35cd250c5e1f0e33204fbda74a3a4940af5c 100644 (file)
@@ -851,7 +851,8 @@ InitPlan(QueryDesc *queryDesc, int eflags)
    /*
     * initialize the node's execution state
     */
-   ExecInitRangeTable(estate, rangeTable, plannedstmt->permInfos);
+   ExecInitRangeTable(estate, rangeTable, plannedstmt->permInfos,
+                      bms_copy(plannedstmt->unprunableRelids));
 
    estate->es_plannedstmt = plannedstmt;
    estate->es_part_prune_infos = plannedstmt->partPruneInfos;
@@ -881,8 +882,13 @@ InitPlan(QueryDesc *queryDesc, int eflags)
            Relation    relation;
            ExecRowMark *erm;
 
-           /* ignore "parent" rowmarks; they are irrelevant at runtime */
-           if (rc->isParent)
+           /*
+            * Ignore "parent" rowmarks, because they are irrelevant at
+            * runtime.  Also ignore the rowmarks belonging to child tables
+            * that have been pruned in ExecDoInitialPruning().
+            */
+           if (rc->isParent ||
+               !bms_is_member(rc->rti, estate->es_unpruned_relids))
                continue;
 
            /* get relation's OID (will produce InvalidOid if subquery) */
@@ -2933,6 +2939,13 @@ EvalPlanQualStart(EPQState *epqstate, Plan *planTree)
        }
    }
 
+   /*
+    * Copy es_unpruned_relids so that pruned relations are ignored by
+    * ExecInitLockRows() and ExecInitModifyTable() when initializing the plan
+    * trees below.
+    */
+   rcestate->es_unpruned_relids = parentestate->es_unpruned_relids;
+
    /*
     * Initialize private state information for each SubPlan.  We must do this
     * before running ExecInitNode on the main query tree, since
index 9c313d81315cfcb26c33f1312bef0dc8b50dbd25..134ff62f5cb35d06ca85b5f63e4a209e168dcf14 100644 (file)
@@ -183,6 +183,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
    pstmt->planTree = plan;
    pstmt->partPruneInfos = estate->es_part_prune_infos;
    pstmt->rtable = estate->es_range_table;
+   pstmt->unprunableRelids = estate->es_unpruned_relids;
    pstmt->permInfos = estate->es_rteperminfos;
    pstmt->resultRelations = NIL;
    pstmt->appendRelations = NIL;
index 57245349cec62816a7792638ba398cdb6c4182fe..b6e89d0620d6569f1914fddd2b7f0d2a3ec0efa0 100644 (file)
@@ -182,7 +182,8 @@ static char *ExecBuildSlotPartitionKeyDescription(Relation rel,
 static List *adjust_partition_colnos(List *colnos, ResultRelInfo *leaf_part_rri);
 static List *adjust_partition_colnos_using_map(List *colnos, AttrMap *attrMap);
 static PartitionPruneState *CreatePartitionPruneState(EState *estate,
-                                                     PartitionPruneInfo *pruneinfo);
+                                                     PartitionPruneInfo *pruneinfo,
+                                                     Bitmapset **all_leafpart_rtis);
 static void InitPartitionPruneContext(PartitionPruneContext *context,
                                      List *pruning_steps,
                                      PartitionDesc partdesc,
@@ -196,7 +197,8 @@ static void InitExecPartitionPruneContexts(PartitionPruneState *prunstate,
 static void find_matching_subplans_recurse(PartitionPruningData *prunedata,
                                           PartitionedRelPruningData *pprune,
                                           bool initial_prune,
-                                          Bitmapset **validsubplans);
+                                          Bitmapset **validsubplans,
+                                          Bitmapset **validsubplan_rtis);
 
 
 /*
@@ -1820,9 +1822,12 @@ ExecDoInitialPruning(EState *estate)
        PartitionPruneInfo *pruneinfo = lfirst_node(PartitionPruneInfo, lc);
        PartitionPruneState *prunestate;
        Bitmapset  *validsubplans = NULL;
+       Bitmapset  *all_leafpart_rtis = NULL;
+       Bitmapset  *validsubplan_rtis = NULL;
 
        /* Create and save the PartitionPruneState. */
-       prunestate = CreatePartitionPruneState(estate, pruneinfo);
+       prunestate = CreatePartitionPruneState(estate, pruneinfo,
+                                              &all_leafpart_rtis);
        estate->es_part_prune_states = lappend(estate->es_part_prune_states,
                                               prunestate);
 
@@ -1831,7 +1836,13 @@ ExecDoInitialPruning(EState *estate)
         * bitmapset or NULL as described in the header comment.
         */
        if (prunestate->do_initial_prune)
-           validsubplans = ExecFindMatchingSubPlans(prunestate, true);
+           validsubplans = ExecFindMatchingSubPlans(prunestate, true,
+                                                    &validsubplan_rtis);
+       else
+           validsubplan_rtis = all_leafpart_rtis;
+
+       estate->es_unpruned_relids = bms_add_members(estate->es_unpruned_relids,
+                                                    validsubplan_rtis);
        estate->es_part_prune_results = lappend(estate->es_part_prune_results,
                                                validsubplans);
    }
@@ -1944,9 +1955,16 @@ ExecInitPartitionExecPruning(PlanState *planstate,
  * initialized here. Those required for exec pruning are initialized later in
  * ExecInitPartitionExecPruning(), as they depend on the availability of the
  * parent plan node's PlanState.
+ *
+ * If initial pruning steps are to be skipped (e.g., during EXPLAIN
+ * (GENERIC_PLAN)), *all_leafpart_rtis will be populated with the RT indexes of
+ * all leaf partitions whose scanning subnode is included in the parent plan
+ * node's list of child plans. The caller must add these RT indexes to
+ * estate->es_unpruned_relids.
  */
 static PartitionPruneState *
-CreatePartitionPruneState(EState *estate, PartitionPruneInfo *pruneinfo)
+CreatePartitionPruneState(EState *estate, PartitionPruneInfo *pruneinfo,
+                         Bitmapset **all_leafpart_rtis)
 {
    PartitionPruneState *prunestate;
    int         n_part_hierarchies;
@@ -2039,8 +2057,8 @@ CreatePartitionPruneState(EState *estate, PartitionPruneInfo *pruneinfo)
             * The set of partitions that exist now might not be the same that
             * existed when the plan was made.  The normal case is that it is;
             * optimize for that case with a quick comparison, and just copy
-            * the subplan_map and make subpart_map point to the one in
-            * PruneInfo.
+            * the subplan_map and make subpart_map, leafpart_rti_map point to
+            * the ones in PruneInfo.
             *
             * For the case where they aren't identical, we could have more
             * partitions on either side; or even exactly the same number of
@@ -2059,6 +2077,7 @@ CreatePartitionPruneState(EState *estate, PartitionPruneInfo *pruneinfo)
                       sizeof(int) * partdesc->nparts) == 0)
            {
                pprune->subpart_map = pinfo->subpart_map;
+               pprune->leafpart_rti_map = pinfo->leafpart_rti_map;
                memcpy(pprune->subplan_map, pinfo->subplan_map,
                       sizeof(int) * pinfo->nparts);
            }
@@ -2079,6 +2098,7 @@ CreatePartitionPruneState(EState *estate, PartitionPruneInfo *pruneinfo)
                 * mismatches.
                 */
                pprune->subpart_map = palloc(sizeof(int) * partdesc->nparts);
+               pprune->leafpart_rti_map = palloc(sizeof(int) * partdesc->nparts);
 
                for (pp_idx = 0; pp_idx < partdesc->nparts; pp_idx++)
                {
@@ -2096,6 +2116,8 @@ CreatePartitionPruneState(EState *estate, PartitionPruneInfo *pruneinfo)
                            pinfo->subplan_map[pd_idx];
                        pprune->subpart_map[pp_idx] =
                            pinfo->subpart_map[pd_idx];
+                       pprune->leafpart_rti_map[pp_idx] =
+                           pinfo->leafpart_rti_map[pd_idx];
                        pd_idx++;
                        continue;
                    }
@@ -2133,6 +2155,7 @@ CreatePartitionPruneState(EState *estate, PartitionPruneInfo *pruneinfo)
 
                    pprune->subpart_map[pp_idx] = -1;
                    pprune->subplan_map[pp_idx] = -1;
+                   pprune->leafpart_rti_map[pp_idx] = 0;
                }
            }
 
@@ -2174,6 +2197,25 @@ CreatePartitionPruneState(EState *estate, PartitionPruneInfo *pruneinfo)
            prunestate->execparamids = bms_add_members(prunestate->execparamids,
                                                       pinfo->execparamids);
 
+           /*
+            * Return all leaf partition indexes if we're skipping pruning in
+            * the EXPLAIN (GENERIC_PLAN) case.
+            */
+           if (pinfo->initial_pruning_steps && !prunestate->do_initial_prune)
+           {
+               int         part_index = -1;
+
+               while ((part_index = bms_next_member(pprune->present_parts,
+                                                    part_index)) >= 0)
+               {
+                   Index       rtindex = pprune->leafpart_rti_map[part_index];
+
+                   if (rtindex)
+                       *all_leafpart_rtis = bms_add_member(*all_leafpart_rtis,
+                                                           rtindex);
+               }
+           }
+
            j++;
        }
        i++;
@@ -2439,10 +2481,15 @@ InitExecPartitionPruneContexts(PartitionPruneState *prunestate,
  * Pass initial_prune if PARAM_EXEC Params cannot yet be evaluated.  This
  * differentiates the initial executor-time pruning step from later
  * runtime pruning.
+ *
+ * The caller must pass a non-NULL validsubplan_rtis during initial pruning
+ * to collect the RT indexes of leaf partitions whose subnodes will be
+ * executed.  These RT indexes are later added to EState.es_unpruned_relids.
  */
 Bitmapset *
 ExecFindMatchingSubPlans(PartitionPruneState *prunestate,
-                        bool initial_prune)
+                        bool initial_prune,
+                        Bitmapset **validsubplan_rtis)
 {
    Bitmapset  *result = NULL;
    MemoryContext oldcontext;
@@ -2454,6 +2501,7 @@ ExecFindMatchingSubPlans(PartitionPruneState *prunestate,
     * evaluated *and* there are steps in which to do so.
     */
    Assert(initial_prune || prunestate->do_exec_prune);
+   Assert(validsubplan_rtis != NULL || !initial_prune);
 
    /*
     * Switch to a temp context to avoid leaking memory in the executor's
@@ -2477,7 +2525,7 @@ ExecFindMatchingSubPlans(PartitionPruneState *prunestate,
         */
        pprune = &prunedata->partrelprunedata[0];
        find_matching_subplans_recurse(prunedata, pprune, initial_prune,
-                                      &result);
+                                      &result, validsubplan_rtis);
 
        /*
         * Expression eval may have used space in ExprContext too. Avoid
@@ -2495,6 +2543,8 @@ ExecFindMatchingSubPlans(PartitionPruneState *prunestate,
 
    /* Copy result out of the temp context before we reset it */
    result = bms_copy(result);
+   if (validsubplan_rtis)
+       *validsubplan_rtis = bms_copy(*validsubplan_rtis);
 
    MemoryContextReset(prunestate->prune_context);
 
@@ -2505,13 +2555,16 @@ ExecFindMatchingSubPlans(PartitionPruneState *prunestate,
  * find_matching_subplans_recurse
  *     Recursive worker function for ExecFindMatchingSubPlans
  *
- * Adds valid (non-prunable) subplan IDs to *validsubplans
+ * Adds valid (non-prunable) subplan IDs to *validsubplans and the RT indexes
+ * of their corresponding leaf partitions to *validsubplan_rtis if
+ * it's non-NULL.
  */
 static void
 find_matching_subplans_recurse(PartitionPruningData *prunedata,
                               PartitionedRelPruningData *pprune,
                               bool initial_prune,
-                              Bitmapset **validsubplans)
+                              Bitmapset **validsubplans,
+                              Bitmapset **validsubplan_rtis)
 {
    Bitmapset  *partset;
    int         i;
@@ -2538,8 +2591,13 @@ find_matching_subplans_recurse(PartitionPruningData *prunedata,
    while ((i = bms_next_member(partset, i)) >= 0)
    {
        if (pprune->subplan_map[i] >= 0)
+       {
            *validsubplans = bms_add_member(*validsubplans,
                                            pprune->subplan_map[i]);
+           if (validsubplan_rtis)
+               *validsubplan_rtis = bms_add_member(*validsubplan_rtis,
+                                                   pprune->leafpart_rti_map[i]);
+       }
        else
        {
            int         partidx = pprune->subpart_map[i];
@@ -2547,7 +2605,8 @@ find_matching_subplans_recurse(PartitionPruningData *prunedata,
            if (partidx >= 0)
                find_matching_subplans_recurse(prunedata,
                                               &prunedata->partrelprunedata[partidx],
-                                              initial_prune, validsubplans);
+                                              initial_prune, validsubplans,
+                                              validsubplan_rtis);
            else
            {
                /*
index 00564985668e51c444ec82d76eea50b3b28b4fc3..c9c756f856866a6814495ecedf02cfa47a529505 100644 (file)
@@ -771,7 +771,8 @@ ExecOpenScanRelation(EState *estate, Index scanrelid, int eflags)
  * indexed by rangetable index.
  */
 void
-ExecInitRangeTable(EState *estate, List *rangeTable, List *permInfos)
+ExecInitRangeTable(EState *estate, List *rangeTable, List *permInfos,
+                  Bitmapset *unpruned_relids)
 {
    /* Remember the range table List as-is */
    estate->es_range_table = rangeTable;
@@ -782,6 +783,15 @@ ExecInitRangeTable(EState *estate, List *rangeTable, List *permInfos)
    /* Set size of associated arrays */
    estate->es_range_table_size = list_length(rangeTable);
 
+   /*
+    * Initialize the bitmapset of RT indexes (es_unpruned_relids)
+    * representing relations that will be scanned during execution. This set
+    * is initially populated by the caller and may be extended later by
+    * ExecDoInitialPruning() to include RT indexes of unpruned leaf
+    * partitions.
+    */
+   estate->es_unpruned_relids = unpruned_relids;
+
    /*
     * Allocate an array to store an open Relation corresponding to each
     * rangetable entry, and initialize entries to NULL.  Relations are opened
index 2397e5e17b0be723a1a9d341ff8f8506f0739495..15c4227cc627cc79e5de0362a1dd6d56ec242644 100644 (file)
@@ -595,7 +595,7 @@ choose_next_subplan_locally(AppendState *node)
        else if (!node->as_valid_subplans_identified)
        {
            node->as_valid_subplans =
-               ExecFindMatchingSubPlans(node->as_prune_state, false);
+               ExecFindMatchingSubPlans(node->as_prune_state, false, NULL);
            node->as_valid_subplans_identified = true;
        }
 
@@ -662,7 +662,7 @@ choose_next_subplan_for_leader(AppendState *node)
        if (!node->as_valid_subplans_identified)
        {
            node->as_valid_subplans =
-               ExecFindMatchingSubPlans(node->as_prune_state, false);
+               ExecFindMatchingSubPlans(node->as_prune_state, false, NULL);
            node->as_valid_subplans_identified = true;
 
            /*
@@ -738,7 +738,7 @@ choose_next_subplan_for_worker(AppendState *node)
    else if (!node->as_valid_subplans_identified)
    {
        node->as_valid_subplans =
-           ExecFindMatchingSubPlans(node->as_prune_state, false);
+           ExecFindMatchingSubPlans(node->as_prune_state, false, NULL);
        node->as_valid_subplans_identified = true;
 
        mark_invalid_subplans_as_finished(node);
@@ -891,7 +891,7 @@ ExecAppendAsyncBegin(AppendState *node)
    if (!node->as_valid_subplans_identified)
    {
        node->as_valid_subplans =
-           ExecFindMatchingSubPlans(node->as_prune_state, false);
+           ExecFindMatchingSubPlans(node->as_prune_state, false, NULL);
        node->as_valid_subplans_identified = true;
 
        classify_matching_subplans(node);
index 4e4e3db0b386919862aca7f2954c8baf8bed6578..a8afbf93b4882c9023a60f7571964d28ae81831c 100644 (file)
@@ -347,8 +347,13 @@ ExecInitLockRows(LockRows *node, EState *estate, int eflags)
        ExecRowMark *erm;
        ExecAuxRowMark *aerm;
 
-       /* ignore "parent" rowmarks; they are irrelevant at runtime */
-       if (rc->isParent)
+       /*
+        * Ignore "parent" rowmarks, because they are irrelevant at runtime.
+        * Also ignore the rowmarks belonging to child tables that have been
+        * pruned in ExecDoInitialPruning().
+        */
+       if (rc->isParent ||
+           !bms_is_member(rc->rti, estate->es_unpruned_relids))
            continue;
 
        /* find ExecRowMark and build ExecAuxRowMark */
index b2dc6626c993fe9d0446df4435cc8871be4d16a9..405e8f942857f3b2713163293cbb678d9e7388e5 100644 (file)
@@ -233,7 +233,7 @@ ExecMergeAppend(PlanState *pstate)
         */
        if (node->ms_valid_subplans == NULL)
            node->ms_valid_subplans =
-               ExecFindMatchingSubPlans(node->ms_prune_state, false);
+               ExecFindMatchingSubPlans(node->ms_prune_state, false, NULL);
 
        /*
         * First time through: pull the first tuple from each valid subplan,
index bc82e035ba281fd8c26ea9322a97a139b7e736f9..349ed2d6d2c5f54fa31d0a4bf1700ad1fad3655a 100644 (file)
@@ -690,7 +690,7 @@ ExecInitUpdateProjection(ModifyTableState *mtstate,
        Assert(whichrel >= 0 && whichrel < mtstate->mt_nrels);
    }
 
-   updateColnos = (List *) list_nth(node->updateColnosLists, whichrel);
+   updateColnos = (List *) list_nth(mtstate->mt_updateColnosLists, whichrel);
 
    /*
     * For UPDATE, we use the old tuple to fill up missing values in the tuple
@@ -4453,7 +4453,11 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
    ModifyTableState *mtstate;
    Plan       *subplan = outerPlan(node);
    CmdType     operation = node->operation;
-   int         nrels = list_length(node->resultRelations);
+   int         nrels;
+   List       *resultRelations = NIL;
+   List       *withCheckOptionLists = NIL;
+   List       *returningLists = NIL;
+   List       *updateColnosLists = NIL;
    ResultRelInfo *resultRelInfo;
    List       *arowmarks;
    ListCell   *l;
@@ -4463,6 +4467,45 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
    /* check for unsupported flags */
    Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
+   /*
+    * Only consider unpruned relations for initializing their ResultRelInfo
+    * struct and other fields such as withCheckOptions, etc.
+    */
+   i = 0;
+   foreach(l, node->resultRelations)
+   {
+       Index       rti = lfirst_int(l);
+
+       if (bms_is_member(rti, estate->es_unpruned_relids))
+       {
+           resultRelations = lappend_int(resultRelations, rti);
+           if (node->withCheckOptionLists)
+           {
+               List       *withCheckOptions = list_nth_node(List,
+                                                            node->withCheckOptionLists,
+                                                            i);
+
+               withCheckOptionLists = lappend(withCheckOptionLists, withCheckOptions);
+           }
+           if (node->returningLists)
+           {
+               List       *returningList = list_nth_node(List,
+                                                         node->returningLists,
+                                                         i);
+
+               returningLists = lappend(returningLists, returningList);
+           }
+           if (node->updateColnosLists)
+           {
+               List       *updateColnosList = list_nth(node->updateColnosLists, i);
+
+               updateColnosLists = lappend(updateColnosLists, updateColnosList);
+           }
+       }
+       i++;
+   }
+   nrels = list_length(resultRelations);
+
    /*
     * create state structure
     */
@@ -4483,6 +4526,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
    mtstate->mt_merge_inserted = 0;
    mtstate->mt_merge_updated = 0;
    mtstate->mt_merge_deleted = 0;
+   mtstate->mt_updateColnosLists = updateColnosLists;
 
    /*----------
     * Resolve the target relation. This is the same as:
@@ -4500,6 +4544,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
     */
    if (node->rootRelation > 0)
    {
+       Assert(bms_is_member(node->rootRelation, estate->es_unpruned_relids));
        mtstate->rootResultRelInfo = makeNode(ResultRelInfo);
        ExecInitResultRelation(estate, mtstate->rootResultRelInfo,
                               node->rootRelation);
@@ -4514,7 +4559,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
    /* set up epqstate with dummy subplan data for the moment */
    EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, NIL,
-                    node->epqParam, node->resultRelations);
+                    node->epqParam, resultRelations);
    mtstate->fireBSTriggers = true;
 
    /*
@@ -4532,7 +4577,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
     */
    resultRelInfo = mtstate->resultRelInfo;
    i = 0;
-   foreach(l, node->resultRelations)
+   foreach(l, resultRelations)
    {
        Index       resultRelation = lfirst_int(l);
        List       *mergeActions = NIL;
@@ -4676,7 +4721,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
     * Initialize any WITH CHECK OPTION constraints if needed.
     */
    resultRelInfo = mtstate->resultRelInfo;
-   foreach(l, node->withCheckOptionLists)
+   foreach(l, withCheckOptionLists)
    {
        List       *wcoList = (List *) lfirst(l);
        List       *wcoExprs = NIL;
@@ -4699,7 +4744,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
    /*
     * Initialize RETURNING projections if needed.
     */
-   if (node->returningLists)
+   if (returningLists)
    {
        TupleTableSlot *slot;
        ExprContext *econtext;
@@ -4708,7 +4753,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
         * Initialize result tuple slot and assign its rowtype using the first
         * RETURNING list.  We assume the rest will look the same.
         */
-       mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
+       mtstate->ps.plan->targetlist = (List *) linitial(returningLists);
 
        /* Set up a slot for the output of the RETURNING projection(s) */
        ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
@@ -4723,7 +4768,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
         * Build a projection for each result rel.
         */
        resultRelInfo = mtstate->resultRelInfo;
-       foreach(l, node->returningLists)
+       foreach(l, returningLists)
        {
            List       *rlist = (List *) lfirst(l);
 
@@ -4824,8 +4869,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
        ExecRowMark *erm;
        ExecAuxRowMark *aerm;
 
-       /* ignore "parent" rowmarks; they are irrelevant at runtime */
-       if (rc->isParent)
+       /*
+        * Ignore "parent" rowmarks, because they are irrelevant at runtime.
+        * Also ignore the rowmarks belonging to child tables that have been
+        * pruned in ExecDoInitialPruning().
+        */
+       if (rc->isParent ||
+           !bms_is_member(rc->rti, estate->es_unpruned_relids))
            continue;
 
        /* Find ExecRowMark and build ExecAuxRowMark */
index ffd7517ea976391f8797f7ddb33594b4d5529b18..7b1a8a0a9f18e5265908a4132d7313a88ca4613f 100644 (file)
@@ -557,6 +557,8 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
    result->planTree = top_plan;
    result->partPruneInfos = glob->partPruneInfos;
    result->rtable = glob->finalrtable;
+   result->unprunableRelids = bms_difference(glob->allRelids,
+                                             glob->prunableRelids);
    result->permInfos = glob->finalrteperminfos;
    result->resultRelations = glob->resultRelations;
    result->appendRelations = glob->appendRelations;
index 0868249be94b5827b02935ee6b7f687d46c0742e..999a5a8ab5a2e1c716b43d782edd057cf0f23bbc 100644 (file)
@@ -564,7 +564,9 @@ add_rte_to_flat_rtable(PlannerGlobal *glob, List *rteperminfos,
 
    /*
     * If it's a plain relation RTE (or a subquery that was once a view
-    * reference), add the relation OID to relationOids.
+    * reference), add the relation OID to relationOids.  Also add its new RT
+    * index to the set of relations to be potentially accessed during
+    * execution.
     *
     * We do this even though the RTE might be unreferenced in the plan tree;
     * this would correspond to cases such as views that were expanded, child
@@ -576,7 +578,11 @@ add_rte_to_flat_rtable(PlannerGlobal *glob, List *rteperminfos,
     */
    if (newrte->rtekind == RTE_RELATION ||
        (newrte->rtekind == RTE_SUBQUERY && OidIsValid(newrte->relid)))
+   {
        glob->relationOids = lappend_oid(glob->relationOids, newrte->relid);
+       glob->allRelids = bms_add_member(glob->allRelids,
+                                        list_length(glob->finalrtable));
+   }
 
    /*
     * Add a copy of the RTEPermissionInfo, if any, corresponding to this RTE
@@ -1740,6 +1746,10 @@ set_customscan_references(PlannerInfo *root,
  *
  * Also update the RT indexes present in PartitionedRelPruneInfos to add the
  * offset.
+ *
+ * Finally, if there are initial pruning steps, add the RT indexes of the
+ * leaf partitions to the set of relations that are prunable at execution
+ * startup time.
  */
 static int
 register_partpruneinfo(PlannerInfo *root, int part_prune_index, int rtoffset)
@@ -1762,6 +1772,7 @@ register_partpruneinfo(PlannerInfo *root, int part_prune_index, int rtoffset)
        foreach(l2, prune_infos)
        {
            PartitionedRelPruneInfo *prelinfo = lfirst(l2);
+           int         i;
 
            prelinfo->rtindex += rtoffset;
            prelinfo->initial_pruning_steps =
@@ -1770,6 +1781,22 @@ register_partpruneinfo(PlannerInfo *root, int part_prune_index, int rtoffset)
            prelinfo->exec_pruning_steps =
                fix_scan_list(root, prelinfo->exec_pruning_steps,
                              rtoffset, 1);
+
+           for (i = 0; i < prelinfo->nparts; i++)
+           {
+               /*
+                * Non-leaf partitions and partitions that do not have a
+                * subplan are not included in this map as mentioned in
+                * make_partitionedrel_pruneinfo().
+                */
+               if (prelinfo->leafpart_rti_map[i])
+               {
+                   prelinfo->leafpart_rti_map[i] += rtoffset;
+                   if (prelinfo->initial_pruning_steps)
+                       glob->prunableRelids = bms_add_member(glob->prunableRelids,
+                                                             prelinfo->leafpart_rti_map[i]);
+               }
+           }
        }
    }
 
index 4693eef0c587502a42d9fdacb48a161daeb014b7..ff926732f367eb6836320282b9c7059fca452e04 100644 (file)
@@ -645,6 +645,7 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
        int        *subplan_map;
        int        *subpart_map;
        Oid        *relid_map;
+       int        *leafpart_rti_map;
 
        /*
         * Construct the subplan and subpart maps for this partitioning level.
@@ -657,6 +658,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));
+       leafpart_rti_map = (int *) palloc0(nparts * sizeof(int));
        present_parts = NULL;
 
        i = -1;
@@ -671,9 +673,21 @@ 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 "leaf" partitions so they can be
+            * included in the PlannerGlobal.prunableRelids set, indicating
+            * relations that may be pruned during executor startup.
+            *
+            * Only leaf partitions with a valid subplan that are prunable
+            * using initial pruning are added to prunableRelids. So
+            * partitions without a subplan due to constraint exclusion will
+            * remain in PlannedStmt.unprunableRelids.
+            */
            if (subplanidx >= 0)
            {
                present_parts = bms_add_member(present_parts, i);
+               leafpart_rti_map[i] = (int) partrel->relid;
 
                /* Record finding this subplan  */
                subplansfound = bms_add_member(subplansfound, subplanidx);
@@ -695,6 +709,7 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
        pinfo->subplan_map = subplan_map;
        pinfo->subpart_map = subpart_map;
        pinfo->relid_map = relid_map;
+       pinfo->leafpart_rti_map = leafpart_rti_map;
    }
 
    pfree(relid_subpart_map);
index 6966037d2eff3f69d134989accd3d70b80bd5b2f..f09ab41c6058bc1386e665a3f67c425c43764472 100644 (file)
@@ -668,7 +668,8 @@ create_edata_for_relation(LogicalRepRelMapEntry *rel)
 
    addRTEPermissionInfo(&perminfos, rte);
 
-   ExecInitRangeTable(estate, list_make1(rte), perminfos);
+   ExecInitRangeTable(estate, list_make1(rte), perminfos,
+                      bms_make_singleton(1));
 
    edata->targetRelInfo = resultRelInfo = makeNode(ResultRelInfo);
 
index 0227fcbca3d5632b42a8f7afde1fc4d82095c411..2f89996a75719637103d02cd6c9ebace82eb204c 100644 (file)
@@ -820,7 +820,8 @@ create_estate_for_relation(Relation rel)
 
    addRTEPermissionInfo(&perminfos, rte);
 
-   ExecInitRangeTable(estate, list_make1(rte), perminfos);
+   ExecInitRangeTable(estate, list_make1(rte), perminfos,
+                      bms_make_singleton(1));
 
    estate->es_output_cid = GetCurrentCommandId(false);
 
index 855fed4fea548ad36958a4bc5d0334c2643e4e57..626613012f91f124314f1386090376f56d794ea4 100644 (file)
@@ -48,6 +48,7 @@ extern void ExecCleanupTupleRouting(ModifyTableState *mtstate,
  * nparts                      Length of subplan_map[] and subpart_map[].
  * subplan_map                 Subplan index by partition index, or -1.
  * subpart_map                 Subpart index by partition index, or -1.
+ * leafpart_rti_map                RT index by partition index, or 0.
  * present_parts               A Bitmapset of the partition indexes that we
  *                             have subplans or subparts for.
  * initial_pruning_steps       List of PartitionPruneSteps used to
@@ -65,6 +66,7 @@ typedef struct PartitionedRelPruningData
    int         nparts;
    int        *subplan_map;
    int        *subpart_map;
+   int        *leafpart_rti_map;
    Bitmapset  *present_parts;
    List       *initial_pruning_steps;
    List       *exec_pruning_steps;
@@ -135,6 +137,7 @@ extern PartitionPruneState *ExecInitPartitionExecPruning(PlanState *planstate,
                                                         Bitmapset *relids,
                                                         Bitmapset **initially_valid_subplans);
 extern Bitmapset *ExecFindMatchingSubPlans(PartitionPruneState *prunestate,
-                                          bool initial_prune);
+                                          bool initial_prune,
+                                          Bitmapset **validsubplan_rtis);
 
 #endif                         /* EXECPARTITION_H */
index 45b80e6b98ee2f90556e948abb89faea6866a79f..30e2a82346f4898850e269618721a0a5b88ae70b 100644 (file)
@@ -595,7 +595,8 @@ extern bool ExecRelationIsTargetRelation(EState *estate, Index scanrelid);
 
 extern Relation ExecOpenScanRelation(EState *estate, Index scanrelid, int eflags);
 
-extern void ExecInitRangeTable(EState *estate, List *rangeTable, List *permInfos);
+extern void ExecInitRangeTable(EState *estate, List *rangeTable, List *permInfos,
+                              Bitmapset *unpruned_relids);
 extern void ExecCloseRangeTableRelations(EState *estate);
 extern void ExecCloseResultRelations(EState *estate);
 
index aca15f771a2683b3e1c96811f6220b04196f03e4..a2cba97e3d5a5a560578a8aaf55465a0f926723e 100644 (file)
@@ -658,6 +658,10 @@ typedef struct EState
    List       *es_part_prune_infos;    /* List of PartitionPruneInfo */
    List       *es_part_prune_states;   /* List of PartitionPruneState */
    List       *es_part_prune_results;  /* List of Bitmapset */
+   Bitmapset  *es_unpruned_relids; /* PlannedStmt.unprunableRelids + RT
+                                    * indexes of leaf partitions that survive
+                                    * initial pruning; see
+                                    * ExecDoInitialPruning() */
    const char *es_sourceText;  /* Source text from QueryDesc */
 
    JunkFilter *es_junkFilter;  /* top-level junk filter, if any */
@@ -1440,6 +1444,12 @@ typedef struct ModifyTableState
    double      mt_merge_inserted;
    double      mt_merge_updated;
    double      mt_merge_deleted;
+
+   /*
+    * List of valid updateColnosLists.  Contains only those belonging to
+    * unpruned relations from ModifyTable.updateColnosLists.
+    */
+   List       *mt_updateColnosLists;
 } ModifyTableState;
 
 /* ----------------
index 52d44f43021f9c363a2503d57761e38fdb58f627..00c700cc3e7d30b180bc74479b0a6ef6fe32ad4f 100644 (file)
@@ -116,6 +116,19 @@ typedef struct PlannerGlobal
    /* "flat" rangetable for executor */
    List       *finalrtable;
 
+   /*
+    * RT indexes of all relation RTEs in finalrtable (RTE_RELATION and
+    * RTE_SUBQUERY RTEs of views)
+    */
+   Bitmapset  *allRelids;
+
+   /*
+    * RT indexes of all leaf partitions in nodes that support pruning and are
+    * subject to runtime pruning at plan initialization time ("initial"
+    * pruning).
+    */
+   Bitmapset  *prunableRelids;
+
    /* "flat" list of RTEPermissionInfos */
    List       *finalrteperminfos;
 
index 06d9559ebb95c0e5369c66e7178bd56f8e464a99..2a2cf816cb6e79854cb179e8301b24d8de2cd81c 100644 (file)
@@ -74,6 +74,10 @@ typedef struct PlannedStmt
 
    List       *rtable;         /* list of RangeTblEntry nodes */
 
+   Bitmapset  *unprunableRelids;   /* RT indexes of relations that are not
+                                    * subject to runtime pruning or are
+                                    * needed to perform runtime pruning */
+
    List       *permInfos;      /* list of RTEPermissionInfo nodes for rtable
                                 * entries needing one */
 
@@ -1449,18 +1453,22 @@ typedef struct PartitionPruneInfo
  * PartitionedRelPruneInfo - Details required to allow the executor to prune
  * partitions for a single partitioned table.
  *
- * subplan_map[] and subpart_map[] are indexed by partition index of the
- * partitioned table referenced by 'rtindex', the partition index being the
- * order that the partitions are defined in the table's PartitionDesc.  For a
- * leaf partition p, subplan_map[p] contains the zero-based index of the
- * partition's subplan in the parent plan's subplan list; it is -1 if the
- * partition is non-leaf or has been pruned.  For a non-leaf partition p,
- * subpart_map[p] contains the zero-based index of that sub-partition's
- * PartitionedRelPruneInfo in the hierarchy's PartitionedRelPruneInfo list;
- * it is -1 if the partition is a leaf or has been pruned.  Note that subplan
- * indexes, as stored in 'subplan_map', are global across the parent plan
- * node, but partition indexes are valid only within a particular hierarchy.
- * relid_map[p] contains the partition's OID, or 0 if the partition was pruned.
+ * subplan_map[], subpart_map[], and leafpart_rti_map[] are indexed by partition
+ * index of the partitioned table referenced by 'rtindex', the partition index
+ * being the order that the partitions are defined in the table's
+ * PartitionDesc.  For a leaf partition p, subplan_map[p] contains the
+ * zero-based index of the partition's subplan in the parent plan's subplan
+ * list; it is -1 if the partition is non-leaf or has been pruned.  For a
+ * non-leaf partition p, subpart_map[p] contains the zero-based index of that
+ * sub-partition's PartitionedRelPruneInfo in the hierarchy's
+ * PartitionedRelPruneInfo list; it is -1 if the partition is a leaf or has
+ * been pruned.  leafpart_rti_map[p] contains the RT index of a leaf partition
+ * if its subplan is in the parent plan' subplan list; it is 0 either if the
+ * partition is non-leaf or it is leaf but has been pruned during planning.
+ * Note that subplan indexes, as stored in 'subplan_map', are global across the
+ * parent plan node, but partition indexes are valid only within a particular
+ * hierarchy.  relid_map[p] contains the partition's OID, or 0 if the partition
+ * was pruned.
  */
 typedef struct PartitionedRelPruneInfo
 {
@@ -1483,6 +1491,9 @@ typedef struct PartitionedRelPruneInfo
    /* subpart index by partition index, or -1 */
    int        *subpart_map pg_node_attr(array_size(nparts));
 
+   /* RT index by partition index, or 0 */
+   int        *leafpart_rti_map pg_node_attr(array_size(nparts));
+
    /* relation OID by partition index, or 0 */
    Oid        *relid_map pg_node_attr(array_size(nparts));
 
index f0707e7f7ea83cd70d4ef4b3e6a90348bc48f29d..e667503c9610e658bb3b9d5e0264ddb12755650d 100644 (file)
@@ -4469,3 +4469,49 @@ drop table hp_contradict_test;
 drop operator class part_test_int4_ops2 using hash;
 drop operator ===(int4, int4);
 drop function explain_analyze(text);
+-- Runtime pruning on UPDATE using WITH CHECK OPTIONS and RETURNING
+create table part_abc (a int, b text, c bool) partition by list (a);
+create table part_abc_1 (b text, a int, c bool);
+create table part_abc_2 (a int, c bool, b text);
+alter table part_abc attach partition part_abc_1 for values in (1);
+alter table part_abc attach partition part_abc_2 for values in (2);
+insert into part_abc values (1, 'b', true);
+insert into part_abc values (2, 'c', true);
+create view part_abc_view as select * from part_abc where b <> 'a' with check option;
+prepare update_part_abc_view as update part_abc_view set b = $2 where a = $1 returning *;
+-- Only the unpruned partition should be shown in the list of relations to be
+-- updated
+explain (costs off) execute update_part_abc_view (1, 'd');
+                      QUERY PLAN                       
+-------------------------------------------------------
+ Update on part_abc
+   Update on part_abc_1
+   ->  Append
+         Subplans Removed: 1
+         ->  Seq Scan on part_abc_1
+               Filter: ((b <> 'a'::text) AND (a = $1))
+(6 rows)
+
+execute update_part_abc_view (1, 'd');
+ a | b | c 
+---+---+---
+ 1 | d | t
+(1 row)
+
+explain (costs off) execute update_part_abc_view (2, 'a');
+                      QUERY PLAN                       
+-------------------------------------------------------
+ Update on part_abc
+   Update on part_abc_2 part_abc_1
+   ->  Append
+         Subplans Removed: 1
+         ->  Seq Scan on part_abc_2 part_abc_1
+               Filter: ((b <> 'a'::text) AND (a = $1))
+(6 rows)
+
+execute update_part_abc_view (2, 'a');
+ERROR:  new row violates check option for view "part_abc_view"
+DETAIL:  Failing row contains (2, a, t).
+deallocate update_part_abc_view;
+drop view part_abc_view;
+drop table part_abc;
index ea9a4fe4a2327d187bc4dc4dc581ec600d042c17..730545e86a77408f755b2a3f42cf5fafe70af8e0 100644 (file)
@@ -1354,3 +1354,23 @@ drop operator class part_test_int4_ops2 using hash;
 drop operator ===(int4, int4);
 
 drop function explain_analyze(text);
+
+-- Runtime pruning on UPDATE using WITH CHECK OPTIONS and RETURNING
+create table part_abc (a int, b text, c bool) partition by list (a);
+create table part_abc_1 (b text, a int, c bool);
+create table part_abc_2 (a int, c bool, b text);
+alter table part_abc attach partition part_abc_1 for values in (1);
+alter table part_abc attach partition part_abc_2 for values in (2);
+insert into part_abc values (1, 'b', true);
+insert into part_abc values (2, 'c', true);
+create view part_abc_view as select * from part_abc where b <> 'a' with check option;
+prepare update_part_abc_view as update part_abc_view set b = $2 where a = $1 returning *;
+-- Only the unpruned partition should be shown in the list of relations to be
+-- updated
+explain (costs off) execute update_part_abc_view (1, 'd');
+execute update_part_abc_view (1, 'd');
+explain (costs off) execute update_part_abc_view (2, 'a');
+execute update_part_abc_view (2, 'a');
+deallocate update_part_abc_view;
+drop view part_abc_view;
+drop table part_abc;