diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index 2175dff824..77f7366cf8 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -1939,7 +1939,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate, if (plan && plan->operation == CMD_UPDATE && (resultRelInfo->ri_usesFdwDirectModify || resultRelInfo->ri_FdwState) && - resultRelInfo > mtstate->resultRelInfo + mtstate->mt_whichplan) + resultRelInfo > mtstate->resultRelInfos[mtstate->mt_whichplan]) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot route tuples into foreign table to be updated \"%s\"", @@ -1993,7 +1993,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate, */ if (plan && plan->operation == CMD_UPDATE && resultRelation == plan->rootRelation) - resultRelation = mtstate->resultRelInfo[0].ri_RangeTableIndex; + resultRelation = mtstate->resultRelInfos[0]->ri_RangeTableIndex; } /* Construct the SQL command string. */ diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 40a8ec1abd..e2f348570e 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -2854,7 +2854,7 @@ CopyFrom(CopyState cstate) mtstate->ps.plan = NULL; mtstate->ps.state = estate; mtstate->operation = CMD_INSERT; - mtstate->resultRelInfo = estate->es_result_relations; + mtstate->resultRelInfos = &estate->es_result_relations; if (resultRelInfo->ri_FdwRoutine != NULL && resultRelInfo->ri_FdwRoutine->BeginForeignInsert != NULL) diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index d189b8d573..f6371318d0 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -3216,14 +3216,14 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors, /* Should we explicitly label target relations? */ labeltargets = (mtstate->mt_nplans > 1 || (mtstate->mt_nplans == 1 && - mtstate->resultRelInfo->ri_RangeTableIndex != node->nominalRelation)); + mtstate->resultRelInfos[0]->ri_RangeTableIndex != node->nominalRelation)); if (labeltargets) ExplainOpenGroup("Target Tables", "Target Tables", false, es); for (j = 0; j < mtstate->mt_nplans; j++) { - ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j; + ResultRelInfo *resultRelInfo = mtstate->resultRelInfos[j]; FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine; if (labeltargets) diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c index c13b1d3501..f84110a54c 100644 --- a/src/backend/executor/execPartition.c +++ b/src/backend/executor/execPartition.c @@ -470,7 +470,7 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate, /* Hash all subplans by their Oid */ for (i = 0; i < mtstate->mt_nplans; i++) { - ResultRelInfo *rri = &mtstate->resultRelInfo[i]; + ResultRelInfo *rri = mtstate->resultRelInfos[i]; bool found; Oid partoid = RelationGetRelid(rri->ri_RelationDesc); SubplanResultRelHashElem *elem; @@ -507,7 +507,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, ModifyTable *node = (ModifyTable *) mtstate->ps.plan; Relation rootrel = rootResultRelInfo->ri_RelationDesc, partrel; - Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc; + Relation firstResultRel = mtstate->resultRelInfos[0]->ri_RelationDesc; ResultRelInfo *leaf_part_rri; MemoryContext oldcxt; AttrMap *part_attmap = NULL; @@ -555,7 +555,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, List *wcoList; List *wcoExprs = NIL; ListCell *ll; - int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex; + int firstVarno = mtstate->resultRelInfos[0]->ri_RangeTableIndex; /* * In the case of INSERT on a partitioned table, there is only one @@ -619,7 +619,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, TupleTableSlot *slot; ExprContext *econtext; List *returningList; - int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex; + int firstVarno = mtstate->resultRelInfos[0]->ri_RangeTableIndex; /* See the comment above for WCO lists. */ Assert((node->operation == CMD_INSERT && @@ -678,7 +678,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, */ if (node && node->onConflictAction != ONCONFLICT_NONE) { - int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex; + int firstVarno = mtstate->resultRelInfos[0]->ri_RangeTableIndex; TupleDesc partrelDesc = RelationGetDescr(partrel); ExprContext *econtext = mtstate->ps.ps_ExprContext; ListCell *lc; diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 59d1a31c97..806f29f6b3 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -57,6 +57,9 @@ #include "utils/memutils.h" #include "utils/rel.h" + /* Special values for mt_whichplan */ +#define WHICHPLAN_CHOOSE_PARTITIONS -1 +#define WHICHPLAN_NO_MATCHING_PARTITIONS -2 static bool ExecOnConflictUpdate(ModifyTableState *mtstate, ResultRelInfo *resultRelInfo, @@ -1260,7 +1263,7 @@ lreplace:; * retrieve the one for this resultRel, we need to know the * position of the resultRel in mtstate->resultRelInfo[]. */ - map_index = resultRelInfo - mtstate->resultRelInfo; + map_index = mtstate->mt_whichplan; Assert(map_index >= 0 && map_index < mtstate->mt_nplans); tupconv_map = tupconv_map_for_subplan(mtstate, map_index); if (tupconv_map != NULL) @@ -1706,12 +1709,12 @@ static void fireBSTriggers(ModifyTableState *node) { ModifyTable *plan = (ModifyTable *) node->ps.plan; - ResultRelInfo *resultRelInfo = node->resultRelInfo; + ResultRelInfo *resultRelInfo = node->resultRelInfos[0]; /* * If the node modifies a partitioned table, we must fire its triggers. - * Note that in that case, node->resultRelInfo points to the first leaf - * partition, not the root table. + * Note that in that case, node->resultRelInfos[0] points to the first + * leaf partition, not the root table. */ if (node->rootResultRelInfo != NULL) resultRelInfo = node->rootResultRelInfo; @@ -1749,13 +1752,14 @@ static ResultRelInfo * getTargetResultRelInfo(ModifyTableState *node) { /* - * Note that if the node modifies a partitioned table, node->resultRelInfo - * points to the first leaf partition, not the root table. + * Note that if the node modifies a partitioned table, + * node->resultRelInfos[0] points to the first leaf partition, not the + * root table. */ if (node->rootResultRelInfo != NULL) return node->rootResultRelInfo; else - return node->resultRelInfo; + return node->resultRelInfos[0]; } /* @@ -1934,7 +1938,7 @@ static void ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate) { ResultRelInfo *targetRelInfo = getTargetResultRelInfo(mtstate); - ResultRelInfo *resultRelInfos = mtstate->resultRelInfo; + ResultRelInfo **resultRelInfos = mtstate->resultRelInfos; TupleDesc outdesc; int numResultRelInfos = mtstate->mt_nplans; int i; @@ -1954,7 +1958,7 @@ ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate) for (i = 0; i < numResultRelInfos; ++i) { mtstate->mt_per_subplan_tupconv_maps[i] = - convert_tuples_by_name(RelationGetDescr(resultRelInfos[i].ri_RelationDesc), + convert_tuples_by_name(RelationGetDescr(resultRelInfos[i]->ri_RelationDesc), outdesc); } } @@ -2030,8 +2034,47 @@ ExecModifyTable(PlanState *pstate) node->fireBSTriggers = false; } + if (node->mt_whichplan < 0) + { + /* Handle choosing the valid partitions */ + if (node->mt_whichplan == WHICHPLAN_CHOOSE_PARTITIONS) + { + PartitionPruneState *prunestate = node->mt_prune_state; + + /* There should always be at least one */ + Assert(node->mt_nplans > 0); + + /* + * When partition pruning is enabled and exec params match the + * partition key then determine the minimum set of matching + * subnodes. Otherwise we match to all subnodes. + */ + if (prunestate != NULL && prunestate->do_exec_prune) + { + node->mt_valid_subplans = ExecFindMatchingSubPlans(prunestate); + node->mt_whichplan = bms_next_member(node->mt_valid_subplans, -1); + + /* If no subplan matches these params then we're done */ + if (node->mt_whichplan < 0) + goto done; + } + else + { + node->mt_valid_subplans = bms_add_range(NULL, 0, + node->mt_nplans - 1); + node->mt_whichplan = 0; + } + } + + /* partition pruning determined that no partitions match */ + else if (node->mt_whichplan == WHICHPLAN_NO_MATCHING_PARTITIONS) + goto done; + else + elog(ERROR, "invalid subplan index: %d", node->mt_whichplan); + } + /* Preload local variables */ - resultRelInfo = node->resultRelInfo + node->mt_whichplan; + resultRelInfo = node->resultRelInfos[node->mt_whichplan]; subplanstate = node->mt_plans[node->mt_whichplan]; junkfilter = resultRelInfo->ri_junkFilter; @@ -2073,10 +2116,12 @@ ExecModifyTable(PlanState *pstate) if (TupIsNull(planSlot)) { /* advance to next subplan if any */ - node->mt_whichplan++; - if (node->mt_whichplan < node->mt_nplans) + node->mt_whichplan = bms_next_member(node->mt_valid_subplans, + node->mt_whichplan); + + if (node->mt_whichplan >= 0) { - resultRelInfo++; + resultRelInfo = node->resultRelInfos[node->mt_whichplan]; subplanstate = node->mt_plans[node->mt_whichplan]; junkfilter = resultRelInfo->ri_junkFilter; estate->es_result_relation_info = resultRelInfo; @@ -2246,6 +2291,8 @@ ExecModifyTable(PlanState *pstate) /* Restore es_result_relation_info before exiting */ estate->es_result_relation_info = saved_resultRelInfo; +done: + /* * We're done, but fire AFTER STATEMENT triggers before exiting. */ @@ -2268,9 +2315,11 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) int nplans = list_length(node->plans); ResultRelInfo *saved_resultRelInfo; ResultRelInfo *resultRelInfo; + Bitmapset *validsubplans; Plan *subplan; ListCell *l; - int i; + int i, + j; Relation rel; bool update_tuple_routing_needed = node->partColsUpdated; @@ -2288,9 +2337,75 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) mtstate->operation = operation; mtstate->canSetTag = node->canSetTag; mtstate->mt_done = false; + mtstate->mt_whichplan = WHICHPLAN_CHOOSE_PARTITIONS; + + /* If run-time partition pruning is enabled, then set that up now */ + if (node->part_prune_info != NULL) + { + PartitionPruneState *prunestate; + + ExecAssignExprContext(estate, &mtstate->ps); + + prunestate = ExecCreatePartitionPruneState(&mtstate->ps, + node->part_prune_info); + mtstate->mt_prune_state = prunestate; + + /* Perform an initial partition prune, if required. */ + if (prunestate->do_initial_prune) + { + /* Determine which subplans match the external params */ + validsubplans = ExecFindInitialMatchingSubPlans(prunestate, + list_length(node->plans)); + + /* + * The case where no subplans survive pruning must be handled + * specially. The problem here is that code in explain.c requires + * an Append to have at least one subplan in order for it to + * properly determine the Vars in that subplan's targetlist. We + * sidestep this issue by just initializing the first subplan and + * setting as_whichplan to NO_MATCHING_SUBPLANS to indicate that + * we don't really need to scan any subnodes. + */ + if (bms_is_empty(validsubplans)) + { + mtstate->mt_whichplan = WHICHPLAN_NO_MATCHING_PARTITIONS; + + /* Mark the first as valid so that it's initialized below */ + validsubplans = bms_make_singleton(0); + } + + nplans = bms_num_members(validsubplans); + } + else + { + /* We'll need to initialize all subplans */ + nplans = list_length(node->plans); + validsubplans = bms_add_range(NULL, 0, nplans - 1); + } + + /* + * If no runtime pruning is required, we can fill mt_valid_subplans + * immediately, preventing later calls to ExecFindMatchingSubPlans. + */ + if (!prunestate->do_exec_prune) + mtstate->mt_valid_subplans = bms_add_range(NULL, 0, nplans - 1); + } + else + { + nplans = list_length(node->plans); + + /* + * When run-time partition pruning is not enabled we can just mark all + * plans as valid, they must also all be initialized. + */ + validsubplans = bms_add_range(NULL, 0, nplans - 1); + mtstate->mt_prune_state = NULL; + } + mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans); - mtstate->resultRelInfo = estate->es_result_relations + node->resultRelIndex; + mtstate->resultRelInfos = (ResultRelInfo **) + palloc(sizeof(ResultRelInfo *) * nplans); mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans); /* If modifying a partitioned table, initialize the root table info */ @@ -2317,11 +2432,18 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) */ saved_resultRelInfo = estate->es_result_relation_info; - resultRelInfo = mtstate->resultRelInfo; - i = 0; + j = i = 0; foreach(l, node->plans) { + if (!bms_is_member(i, validsubplans)) + { + i++; + continue; + } + subplan = (Plan *) lfirst(l); + resultRelInfo = estate->es_result_relations + node->resultRelIndex + i; + mtstate->resultRelInfos[j] = resultRelInfo; /* Initialize the usesFdwDirectModify flag */ resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i, @@ -2359,9 +2481,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) /* Now init the plan for this result rel */ estate->es_result_relation_info = resultRelInfo; - mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags); - mtstate->mt_scans[i] = - ExecInitExtraTupleSlot(mtstate->ps.state, ExecGetResultType(mtstate->mt_plans[i]), + mtstate->mt_plans[j] = ExecInitNode(subplan, estate, eflags); + mtstate->mt_scans[j] = + ExecInitExtraTupleSlot(mtstate->ps.state, ExecGetResultType(mtstate->mt_plans[j]), table_slot_callbacks(resultRelInfo->ri_RelationDesc)); /* Also let FDWs init themselves for foreign-table result rels */ @@ -2377,9 +2499,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) i, eflags); } - - resultRelInfo++; i++; + j++; } estate->es_result_relation_info = saved_resultRelInfo; @@ -2428,14 +2549,21 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) /* * Initialize any WITH CHECK OPTION constraints if needed. */ - resultRelInfo = mtstate->resultRelInfo; - i = 0; + j = i = 0; foreach(l, node->withCheckOptionLists) { - List *wcoList = (List *) lfirst(l); + List *wcoList; List *wcoExprs = NIL; ListCell *ll; + if (!bms_is_member(i, validsubplans)) + { + i++; + continue; + } + + wcoList = (List *) lfirst(l); + foreach(ll, wcoList) { WithCheckOption *wco = (WithCheckOption *) lfirst(ll); @@ -2445,9 +2573,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) wcoExprs = lappend(wcoExprs, wcoExpr); } + resultRelInfo = mtstate->resultRelInfos[j]; resultRelInfo->ri_WithCheckOptions = wcoList; resultRelInfo->ri_WithCheckOptionExprs = wcoExprs; - resultRelInfo++; + j++; i++; } @@ -2477,16 +2606,25 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) /* * Build a projection for each result rel. */ - resultRelInfo = mtstate->resultRelInfo; + j = i = 0; foreach(l, node->returningLists) { - List *rlist = (List *) lfirst(l); + List *rlist; + + if (!bms_is_member(i, validsubplans)) + { + i++; + continue; + } + rlist = (List *) lfirst(l); + + resultRelInfo = mtstate->resultRelInfos[j]; resultRelInfo->ri_returningList = rlist; resultRelInfo->ri_projectReturning = ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps, resultRelInfo->ri_RelationDesc->rd_att); - resultRelInfo++; + j++; } } else @@ -2497,12 +2635,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) */ mtstate->ps.plan->targetlist = NIL; ExecInitResultTypeTL(&mtstate->ps); - - mtstate->ps.ps_ExprContext = NULL; } /* Set the list of arbiter indexes if needed for ON CONFLICT */ - resultRelInfo = mtstate->resultRelInfo; + resultRelInfo = mtstate->resultRelInfos[0]; if (node->onConflictAction != ONCONFLICT_NONE) resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes; @@ -2593,7 +2729,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) } /* select first subplan */ - mtstate->mt_whichplan = 0; subplan = (Plan *) linitial(node->plans); EvalPlanQualSetPlan(&mtstate->mt_epqstate, subplan, mtstate->mt_arowmarks[0]); @@ -2642,12 +2777,12 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) if (junk_filter_needed) { - resultRelInfo = mtstate->resultRelInfo; for (i = 0; i < nplans; i++) { JunkFilter *j; TupleTableSlot *junkresslot; + resultRelInfo = mtstate->resultRelInfos[i]; subplan = mtstate->mt_plans[i]->plan; if (operation == CMD_INSERT || operation == CMD_UPDATE) ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, @@ -2690,13 +2825,12 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) } resultRelInfo->ri_junkFilter = j; - resultRelInfo++; } } else { if (operation == CMD_INSERT) - ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc, + ExecCheckPlanOutput(mtstate->resultRelInfos[0]->ri_RelationDesc, subplan->targetlist); } } @@ -2735,7 +2869,7 @@ ExecEndModifyTable(ModifyTableState *node) */ for (i = 0; i < node->mt_nplans; i++) { - ResultRelInfo *resultRelInfo = node->resultRelInfo + i; + ResultRelInfo *resultRelInfo = node->resultRelInfos[i]; if (!resultRelInfo->ri_usesFdwDirectModify && resultRelInfo->ri_FdwRoutine != NULL && diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 54ad62bb7f..84b0be7a79 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -216,6 +216,7 @@ _copyModifyTable(const ModifyTable *from) COPY_BITMAPSET_FIELD(fdwDirectModifyPlans); COPY_NODE_FIELD(rowMarks); COPY_SCALAR_FIELD(epqParam); + COPY_NODE_FIELD(part_prune_info); COPY_SCALAR_FIELD(onConflictAction); COPY_NODE_FIELD(arbiterIndexes); COPY_NODE_FIELD(onConflictSet); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index d76fae44b8..e673c7fd17 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -417,6 +417,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node) WRITE_BITMAPSET_FIELD(fdwDirectModifyPlans); WRITE_NODE_FIELD(rowMarks); WRITE_INT_FIELD(epqParam); + WRITE_NODE_FIELD(part_prune_info); WRITE_ENUM_FIELD(onConflictAction, OnConflictAction); WRITE_NODE_FIELD(arbiterIndexes); WRITE_NODE_FIELD(onConflictSet); @@ -2092,6 +2093,7 @@ _outModifyTablePath(StringInfo str, const ModifyTablePath *node) WRITE_UINT_FIELD(rootRelation); WRITE_BOOL_FIELD(partColsUpdated); WRITE_NODE_FIELD(resultRelations); + WRITE_NODE_FIELD(partitioned_rels); WRITE_NODE_FIELD(subpaths); WRITE_NODE_FIELD(subroots); WRITE_NODE_FIELD(withCheckOptionLists); diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 551ce6c41c..cbd6ea7a6b 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -1647,6 +1647,7 @@ _readModifyTable(void) READ_BITMAPSET_FIELD(fdwDirectModifyPlans); READ_NODE_FIELD(rowMarks); READ_INT_FIELD(epqParam); + READ_NODE_FIELD(part_prune_info); READ_ENUM_FIELD(onConflictAction, OnConflictAction); READ_NODE_FIELD(arbiterIndexes); READ_NODE_FIELD(onConflictSet); diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index dff826a828..6b0b46d9f3 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -289,7 +289,8 @@ static ModifyTable *make_modifytable(PlannerInfo *root, bool partColsUpdated, List *resultRelations, List *subplans, List *subroots, List *withCheckOptionLists, List *returningLists, - List *rowMarks, OnConflictExpr *onconflict, int epqParam); + List *rowMarks, OnConflictExpr *onconflict, int epqParam, + PartitionPruneInfo *partpruneinfos); static GatherMerge *create_gather_merge_plan(PlannerInfo *root, GatherMergePath *best_path); @@ -1238,6 +1239,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags) make_partition_pruneinfo(root, rel, best_path->subpaths, best_path->partitioned_rels, + NIL, prunequal); } @@ -1404,6 +1406,7 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path, partpruneinfo = make_partition_pruneinfo(root, rel, best_path->subpaths, best_path->partitioned_rels, + NIL, prunequal); } @@ -2586,6 +2589,7 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path) List *subplans = NIL; ListCell *subpaths, *subroots; + PartitionPruneInfo *partpruneinfos = NULL; /* Build the plan for each input path */ forboth(subpaths, best_path->subpaths, @@ -2614,6 +2618,30 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path) subplans = lappend(subplans, subplan); } + if (enable_partition_pruning && + best_path->partitioned_rels != NIL && + !IS_DUMMY_MODIFYTABLE(best_path)) + { + RelOptInfo *rel = best_path->path.parent; + List *prunequal = NIL; + + prunequal = extract_actual_clauses(rel->baserestrictinfo, false); + + /* + * If any quals exist, then these may be useful to allow us to perform + * further partition pruning during execution. We'll generate a + * PartitionPruneInfo for each partitioned rel to store these quals + * and allow translation of partition indexes into subpath indexes. + */ + if (prunequal != NIL) + partpruneinfos = make_partition_pruneinfo(root, + best_path->path.parent, + best_path->subpaths, + best_path->partitioned_rels, + best_path->resultRelations, + prunequal); + } + plan = make_modifytable(root, best_path->operation, best_path->canSetTag, @@ -2627,7 +2655,8 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path) best_path->returningLists, best_path->rowMarks, best_path->onconflict, - best_path->epqParam); + best_path->epqParam, + partpruneinfos); copy_generic_path_info(&plan->plan, &best_path->path); @@ -6621,7 +6650,8 @@ make_modifytable(PlannerInfo *root, bool partColsUpdated, List *resultRelations, List *subplans, List *subroots, List *withCheckOptionLists, List *returningLists, - List *rowMarks, OnConflictExpr *onconflict, int epqParam) + List *rowMarks, OnConflictExpr *onconflict, int epqParam, + PartitionPruneInfo *partpruneinfos) { ModifyTable *node = makeNode(ModifyTable); List *fdw_private_list; @@ -6682,6 +6712,7 @@ make_modifytable(PlannerInfo *root, node->returningLists = returningLists; node->rowMarks = rowMarks; node->epqParam = epqParam; + node->part_prune_info = partpruneinfos; /* * For each result relation that is a foreign table, allow the FDW to diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index d6f2153593..30d15291e3 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -1239,6 +1239,9 @@ inheritance_planner(PlannerInfo *root) RangeTblEntry *parent_rte; Bitmapset *parent_relids; Query **parent_parses; + PlannerInfo *partition_root = NULL; + List *partitioned_rels = NIL; + bool dummy_update = false; /* Should only get here for UPDATE or DELETE */ Assert(parse->commandType == CMD_UPDATE || @@ -1350,6 +1353,13 @@ inheritance_planner(PlannerInfo *root) * expand_partitioned_rtentry for the UPDATE target.) */ root->partColsUpdated = subroot->partColsUpdated; + + /* + * Save this for later so that we can enable run-time pruning on + * the partitioned table(s). + */ + if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) + partition_root = subroot; } /*---------- @@ -1745,6 +1755,9 @@ inheritance_planner(PlannerInfo *root) returningLists = list_make1(parse->returningList); /* Disable tuple routing, too, just to be safe */ root->partColsUpdated = false; + + /* Mark that we're performing a dummy update */ + dummy_update = true; } else { @@ -1784,6 +1797,69 @@ inheritance_planner(PlannerInfo *root) else rowMarks = root->rowMarks; + + /* + * When performing UPDATE/DELETE on a partitioned table, if the query has + * a WHERE clause which supports it, we may be able to perform run-time + * partition pruning. The following code sets things up to allow this to + * be possible. + */ + if (partition_root && !dummy_update) + { + RelOptInfo *parent_rel; + int i; + + /* + * Fetch the target partitioned table from the SELECT version of + * the query which we performed above. This may have the base quals + * which could allow the run-time pruning to work. + */ + parent_rel = partition_root->simple_rel_array[top_parentRTindex]; + + final_rel->baserestrictinfo = parent_rel->baserestrictinfo; + + /* build a list of partitioned rels */ + i = -1; + while ((i = bms_next_member(parent_relids, i)) > 0) + partitioned_rels = lappend_int(partitioned_rels, i); + + + /* + * In order to build the run-time pruning data we'll need append rels + * any sub-partitioned tables. If there are some of those and the + * append_rel_array is not already allocated, then do that now. + */ + if (list_length(partitioned_rels) > 1 && + root->append_rel_array == NULL) + root->append_rel_array = palloc0(sizeof(AppendRelInfo *) * + root->simple_rel_array_size); + + /* + * There can only be a single partition hierarchy, so it's fine to + * just make a single element list of the partitioned_rels. + */ + partitioned_rels = list_make1(partitioned_rels); + + i = -1; + while ((i = bms_next_member(parent_relids, i)) >= 0) + { + Assert(root->simple_rel_array[i] == NULL); + + root->simple_rel_array[i] = partition_root->simple_rel_array[i]; + + /* + * The root partition won't have an append rel entry, so we can + * skip that. We'll need to take the partition_root's version for + * any sub-partitioned table's + */ + if (i != top_parentRTindex) + { + Assert(root->append_rel_array[i] == NULL); + root->append_rel_array[i] = partition_root->append_rel_array[i]; + } + } + } + /* Create Path representing a ModifyTable to do the UPDATE/DELETE work */ add_path(final_rel, (Path *) create_modifytable_path(root, final_rel, @@ -1793,6 +1869,7 @@ inheritance_planner(PlannerInfo *root) rootRelation, root->partColsUpdated, resultRelations, + partitioned_rels, subpaths, subroots, withCheckOptionLists, @@ -2376,6 +2453,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, rootRelation, false, list_make1_int(parse->resultRelation), + NIL, list_make1(path), list_make1(root), withCheckOptionLists, diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index e6d08aede5..63da0396a8 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -3431,6 +3431,9 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel, * 'partColsUpdated' is true if any partitioning columns are being updated, * either from the target relation or a descendent partitioned table. * 'resultRelations' is an integer list of actual RT indexes of target rel(s) + * 'partitioned_rels' is an integer list of RT indexes of non-leaf tables in + * the partition tree, if this is an UPDATE/DELETE to a partitioned table. + * Otherwise NIL. * 'subpaths' is a list of Path(s) producing source data (one per rel) * 'subroots' is a list of PlannerInfo structs (one per rel) * 'withCheckOptionLists' is a list of WCO lists (one per rel) @@ -3444,8 +3447,8 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel, CmdType operation, bool canSetTag, Index nominalRelation, Index rootRelation, bool partColsUpdated, - List *resultRelations, List *subpaths, - List *subroots, + List *resultRelations, List *partitioned_rels, + List *subpaths, List *subroots, List *withCheckOptionLists, List *returningLists, List *rowMarks, OnConflictExpr *onconflict, int epqParam) @@ -3513,6 +3516,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel, pathnode->rootRelation = rootRelation; pathnode->partColsUpdated = partColsUpdated; pathnode->resultRelations = resultRelations; + pathnode->partitioned_rels = list_copy(partitioned_rels); pathnode->subpaths = subpaths; pathnode->subroots = subroots; pathnode->withCheckOptionLists = withCheckOptionLists; diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index eac52e6ec8..ca53f684c2 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -214,7 +214,10 @@ static void partkey_datum_from_expr(PartitionPruneContext *context, * 'parentrel' is the RelOptInfo for an appendrel, and 'subpaths' is the list * of scan paths for its child rels. * - * 'partitioned_rels' is a List containing Lists of relids of partitioned + * If 'resultRelations' is non-NIL, then this List of relids is used to build + * the mapping structures. Otherwise the 'subpaths' List is used. + * + * 'partitioned_rels' is a List containing Lists of relids of partitioned * tables (a/k/a non-leaf partitions) that are parents of some of the child * rels. Here we attempt to populate the PartitionPruneInfo by adding a * 'prune_infos' item for each sublist in the 'partitioned_rels' list. @@ -229,6 +232,7 @@ static void partkey_datum_from_expr(PartitionPruneContext *context, PartitionPruneInfo * make_partition_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, List *subpaths, List *partitioned_rels, + List *resultRelations, List *prunequal) { PartitionPruneInfo *pruneinfo; @@ -246,23 +250,36 @@ make_partition_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, relid_subplan_map = palloc0(sizeof(int) * root->simple_rel_array_size); /* - * relid_subplan_map maps relid of a leaf partition to the index in - * 'subpaths' of the scan plan for that partition. + * If 'resultRelations' are present then map these, otherwise we map the + * 'subpaths' List. */ - i = 1; - foreach(lc, subpaths) + if (resultRelations != NIL) { - Path *path = (Path *) lfirst(lc); - RelOptInfo *pathrel = path->parent; - - Assert(IS_SIMPLE_REL(pathrel)); - Assert(pathrel->relid < root->simple_rel_array_size); - /* No duplicates please */ - Assert(relid_subplan_map[pathrel->relid] == 0); + i = 1; + foreach(lc, resultRelations) + { + int resultrel = lfirst_int(lc); - relid_subplan_map[pathrel->relid] = i++; + Assert(resultrel < root->simple_rel_array_size); + relid_subplan_map[resultrel] = i++; + } } + else + { + i = 1; + foreach(lc, subpaths) + { + Path *path = (Path *)lfirst(lc); + RelOptInfo *pathrel = path->parent; + Assert(IS_SIMPLE_REL(pathrel)); + Assert(pathrel->relid < root->simple_rel_array_size); + /* No duplicates please */ + Assert(relid_subplan_map[pathrel->relid] == 0); + + relid_subplan_map[pathrel->relid] = i++; + } + } /* We now build a PartitionedRelPruneInfo for each partitioned rel. */ prunerelinfos = NIL; foreach(lc, partitioned_rels) @@ -404,9 +421,12 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * an adjust_appendrel_attrs step. But it might not be, and then * we have to translate. We update the prunequal parameter here, * because in later iterations of the loop for child partitions, - * we want to translate from parent to child variables. + * we want to translate from parent to child variables. We don't + * need to do this when planning a non-SELECT as we're only + * working with a single partition hierarchy in that case. */ - if (!bms_equal(parentrel->relids, subpart->relids)) + if (root->parse->commandType == CMD_SELECT && + !bms_equal(parentrel->relids, subpart->relids)) { int nappinfos; AppendRelInfo **appinfos = find_appinfos_by_relids(root, diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 1f6f5bbc20..c9ad649189 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1165,7 +1165,7 @@ typedef struct ModifyTableState int mt_whichplan; /* which one is being executed (0..n-1) */ TupleTableSlot **mt_scans; /* input tuple corresponding to underlying * plans */ - ResultRelInfo *resultRelInfo; /* per-subplan target relations */ + ResultRelInfo **resultRelInfos; /* per-subplan target relations */ ResultRelInfo *rootResultRelInfo; /* root target relation (partitioned * table root) */ List **mt_arowmarks; /* per-subplan ExecAuxRowMark lists */ @@ -1189,6 +1189,14 @@ typedef struct ModifyTableState /* Per plan map for tuple conversion from child to root */ TupleConversionMap **mt_per_subplan_tupconv_maps; + + /* + * Details required to allow partitions to be eliminated from the scan, or + * NULL if not possible. + */ + struct PartitionPruneState *mt_prune_state; + Bitmapset *mt_valid_subplans; /* for runtime pruning, valid mt_plans + * indexes to scan. */ } ModifyTableState; /* ---------------- diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 3d3be197e0..1de53e40f5 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -1382,6 +1382,10 @@ typedef struct AppendPath #define IS_DUMMY_APPEND(p) \ (IsA((p), AppendPath) && ((AppendPath *) (p))->subpaths == NIL) +#define IS_DUMMY_MODIFYTABLE(p) \ + (list_length((p)->subpaths) == 1 && \ + IS_DUMMY_APPEND(linitial((p)->subpaths))) + /* * A relation that's been proven empty will have one path that is dummy * (but might have projection paths on top). For historical reasons, @@ -1777,6 +1781,8 @@ typedef struct ModifyTablePath Index rootRelation; /* Root RT index, if target is partitioned */ bool partColsUpdated; /* some part key in hierarchy updated */ List *resultRelations; /* integer list of RT indexes */ + /* RT indexes of non-leaf tables in a partition tree */ + List *partitioned_rels; List *subpaths; /* Path(s) producing source data */ List *subroots; /* per-target-table PlannerInfos */ List *withCheckOptionLists; /* per-target-table WCO lists */ diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 32c0d87f80..02a0302e12 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -233,6 +233,8 @@ typedef struct ModifyTable Bitmapset *fdwDirectModifyPlans; /* indices of FDW DM plans */ List *rowMarks; /* PlanRowMarks (non-locking only) */ int epqParam; /* ID of Param for EvalPlanQual re-eval */ + /* Mapping details for run-time subplan pruning, one per partitioned_rels */ + struct PartitionPruneInfo *part_prune_info; OnConflictAction onConflictAction; /* ON CONFLICT action */ List *arbiterIndexes; /* List of ON CONFLICT arbiter index OIDs */ List *onConflictSet; /* SET for INSERT ON CONFLICT DO UPDATE */ diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index e450fe112a..145506a634 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -252,10 +252,12 @@ extern LockRowsPath *create_lockrows_path(PlannerInfo *root, RelOptInfo *rel, extern ModifyTablePath *create_modifytable_path(PlannerInfo *root, RelOptInfo *rel, CmdType operation, bool canSetTag, - Index nominalRelation, Index rootRelation, + Index nominalRelation, + Index rootRelation, bool partColsUpdated, - List *resultRelations, List *subpaths, - List *subroots, + List *resultRelations, + List *partitioned_rels, + List *subpaths, List *subroots, List *withCheckOptionLists, List *returningLists, List *rowMarks, OnConflictExpr *onconflict, int epqParam); diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h index babdad2c3e..eb84e176eb 100644 --- a/src/include/partitioning/partprune.h +++ b/src/include/partitioning/partprune.h @@ -72,6 +72,7 @@ extern PartitionPruneInfo *make_partition_pruneinfo(struct PlannerInfo *root, struct RelOptInfo *parentrel, List *subpaths, List *partitioned_rels, + List *resultRelations, List *prunequal); extern Bitmapset *prune_append_rel_partitions(struct RelOptInfo *rel); extern Bitmapset *get_matching_partitions(PartitionPruneContext *context, diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index 424d51d521..9921b68554 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -3196,6 +3196,110 @@ explain (analyze, costs off, summary off, timing off) select * from ma_test wher reset enable_seqscan; reset enable_sort; +-- +-- Test run-time pruning of ModifyTable subnodes +-- +-- Ensure only ma_test_p3 is scanned. +explain (analyze, costs off, summary off, timing off) delete from ma_test where a = (select 29); + QUERY PLAN +---------------------------------------------------------------- + Delete on ma_test (actual rows=0 loops=1) + Delete on ma_test_p1 ma_test_1 + Delete on ma_test_p2 ma_test_2 + Delete on ma_test_p3 ma_test_3 + InitPlan 1 (returns $0) + -> Result (actual rows=1 loops=1) + -> Seq Scan on ma_test_p1 ma_test_1 (never executed) + Filter: (a = $0) + -> Seq Scan on ma_test_p2 ma_test_2 (never executed) + Filter: (a = $0) + -> Seq Scan on ma_test_p3 ma_test_3 (actual rows=1 loops=1) + Filter: (a = $0) + Rows Removed by Filter: 9 +(13 rows) + +-- Ensure no partitions are scanned. +explain (analyze, costs off, summary off, timing off) delete from ma_test where a = (select 30); + QUERY PLAN +--------------------------------------------------------- + Delete on ma_test (actual rows=0 loops=1) + Delete on ma_test_p1 ma_test_1 + Delete on ma_test_p2 ma_test_2 + Delete on ma_test_p3 ma_test_3 + InitPlan 1 (returns $0) + -> Result (actual rows=1 loops=1) + -> Seq Scan on ma_test_p1 ma_test_1 (never executed) + Filter: (a = $0) + -> Seq Scan on ma_test_p2 ma_test_2 (never executed) + Filter: (a = $0) + -> Seq Scan on ma_test_p3 ma_test_3 (never executed) + Filter: (a = $0) +(12 rows) + +-- Ensure partition pruning works with an update of the partition key. +explain (analyze, costs off, summary off, timing off) update ma_test set a = 29 where a = (select 1); + QUERY PLAN +---------------------------------------------------------------- + Update on ma_test (actual rows=0 loops=1) + Update on ma_test_p1 ma_test_1 + Update on ma_test_p2 ma_test_2 + Update on ma_test_p3 ma_test_3 + InitPlan 1 (returns $0) + -> Result (actual rows=1 loops=1) + -> Seq Scan on ma_test_p1 ma_test_1 (actual rows=1 loops=1) + Filter: (a = $0) + Rows Removed by Filter: 9 + -> Seq Scan on ma_test_p2 ma_test_2 (never executed) + Filter: (a = $0) + -> Seq Scan on ma_test_p3 ma_test_3 (never executed) + Filter: (a = $0) +(13 rows) + +-- Verify the above command +select tableoid::regclass,a from ma_test where a = 29; + tableoid | a +------------+---- + ma_test_p3 | 29 +(1 row) + +truncate ma_test; +prepare mt_q1 (int) as +delete from ma_test where a > $1; +set plan_cache_mode = force_generic_plan; +explain (analyze, costs off, summary off, timing off) execute mt_q1(15); + QUERY PLAN +---------------------------------------------------------------- + Delete on ma_test (actual rows=0 loops=1) + Delete on ma_test_p2 ma_test_1 + Delete on ma_test_p3 ma_test_2 + Subplans Removed: 1 + -> Seq Scan on ma_test_p2 ma_test_1 (actual rows=0 loops=1) + Filter: (a > $1) + -> Seq Scan on ma_test_p3 ma_test_2 (actual rows=0 loops=1) + Filter: (a > $1) +(8 rows) + +explain (analyze, costs off, summary off, timing off) execute mt_q1(25); + QUERY PLAN +---------------------------------------------------------------- + Delete on ma_test (actual rows=0 loops=1) + Delete on ma_test_p3 ma_test_1 + Subplans Removed: 2 + -> Seq Scan on ma_test_p3 ma_test_1 (actual rows=0 loops=1) + Filter: (a > $1) +(5 rows) + +-- Ensure ModifyTable behaves correctly when no subplans match exec params +explain (analyze, costs off, summary off, timing off) execute mt_q1(35); + QUERY PLAN +--------------------------------------------------------- + Delete on ma_test (actual rows=0 loops=1) + Delete on ma_test_p1 ma_test_1 + Subplans Removed: 2 + -> Seq Scan on ma_test_p1 ma_test_1 (never executed) + Filter: (a > $1) +(5 rows) + drop table ma_test; reset enable_indexonlyscan; -- diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql index d9daba3af3..047ed00ed3 100644 --- a/src/test/regress/sql/partition_prune.sql +++ b/src/test/regress/sql/partition_prune.sql @@ -851,6 +851,34 @@ explain (analyze, costs off, summary off, timing off) select * from ma_test wher reset enable_seqscan; reset enable_sort; +-- +-- Test run-time pruning of ModifyTable subnodes +-- + +-- Ensure only ma_test_p3 is scanned. +explain (analyze, costs off, summary off, timing off) delete from ma_test where a = (select 29); + +-- Ensure no partitions are scanned. +explain (analyze, costs off, summary off, timing off) delete from ma_test where a = (select 30); + +-- Ensure partition pruning works with an update of the partition key. +explain (analyze, costs off, summary off, timing off) update ma_test set a = 29 where a = (select 1); + +-- Verify the above command +select tableoid::regclass,a from ma_test where a = 29; + +truncate ma_test; + +prepare mt_q1 (int) as +delete from ma_test where a > $1; + +set plan_cache_mode = force_generic_plan; + +explain (analyze, costs off, summary off, timing off) execute mt_q1(15); +explain (analyze, costs off, summary off, timing off) execute mt_q1(25); +-- Ensure ModifyTable behaves correctly when no subplans match exec params +explain (analyze, costs off, summary off, timing off) execute mt_q1(35); + drop table ma_test; reset enable_indexonlyscan;