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 e79ede4cb8..f97d62efc8 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 d901dc4a50..18b0697591 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -1988,6 +1988,11 @@ ExplainNode(PlanState *planstate, List *ancestors, list_length(((MergeAppend *) plan)->mergeplans), es); break; + case T_ModifyTable: + ExplainMissingMembers(((ModifyTableState *) planstate)->mt_nplans, + list_length(((ModifyTable *) plan)->plans), + es); + break; default: break; } @@ -3244,14 +3249,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 d71c0a4322..0d987e9651 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -57,6 +57,8 @@ #include "utils/memutils.h" #include "utils/rel.h" + /* Special value for mt_whichplan */ +#define WHICHPLAN_CHOOSE_SUBPLAN -1 static bool ExecOnConflictUpdate(ModifyTableState *mtstate, ResultRelInfo *resultRelInfo, @@ -1198,7 +1200,6 @@ lreplace:; TupleTableSlot *ret_slot; TupleTableSlot *epqslot = NULL; PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing; - int map_index; TupleConversionMap *tupconv_map; /* @@ -1277,17 +1278,8 @@ lreplace:; if (mtstate->mt_transition_capture) saved_tcs_map = mtstate->mt_transition_capture->tcs_map; - /* - * resultRelInfo is one of the per-subplan resultRelInfos. So we - * should convert the tuple into root's tuple descriptor, since - * ExecInsert() starts the search from root. The tuple conversion - * map list is in the order of mtstate->resultRelInfo[], so to - * retrieve the one for this resultRel, we need to know the - * position of the resultRel in mtstate->resultRelInfo[]. - */ - map_index = resultRelInfo - mtstate->resultRelInfo; - Assert(map_index >= 0 && map_index < mtstate->mt_nplans); - tupconv_map = tupconv_map_for_subplan(mtstate, map_index); + /* Fetch the tuple conversion map for the current subplan */ + tupconv_map = tupconv_map_for_subplan(mtstate, mtstate->mt_whichplan); if (tupconv_map != NULL) slot = execute_attr_map_slot(tupconv_map->attrMap, slot, @@ -1731,12 +1723,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; @@ -1774,13 +1766,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]; } /* @@ -1959,7 +1952,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; @@ -1979,7 +1972,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); } } @@ -2055,8 +2048,33 @@ ExecModifyTable(PlanState *pstate) node->fireBSTriggers = false; } + + /* Handle choosing the valid subplans */ + if (node->mt_whichplan == WHICHPLAN_CHOOSE_SUBPLAN) + { + PartitionPruneState *prunestate = node->mt_prune_state; + + /* Bail if all partitions were run-time pruned */ + if (node->mt_nplans == 0) + goto done; + + /* + * mt_whichplan should never get set to WHICHPLAN_CHOOSE_SUBPLAN + * unless we're performing do_exec_prune + */ + Assert(prunestate != NULL && prunestate->do_exec_prune); + + /* Determine the minimum set of matching subplans */ + 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; + } + /* 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; @@ -2097,11 +2115,13 @@ ExecModifyTable(PlanState *pstate) if (TupIsNull(planSlot)) { - /* advance to next subplan if any */ - node->mt_whichplan++; - if (node->mt_whichplan < node->mt_nplans) + /* advance to the next valid subplan if any */ + 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; @@ -2271,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. */ @@ -2293,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; @@ -2314,8 +2338,74 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) mtstate->canSetTag = node->canSetTag; mtstate->mt_done = false; + /* 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)); + + 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); + } + + /* + * When no run-time pruning is required and there's at least one + * subplan, we can fill mt_valid_subplans immediately, preventing + * later calls to ExecFindMatchingSubPlans. Additionally, we can also + * select the first subplan to execute. + */ + if (!prunestate->do_exec_prune && nplans > 0) + { + mtstate->mt_valid_subplans = bms_add_range(NULL, 0, nplans - 1); + mtstate->mt_whichplan = 0; + } + else + { + /* + * When performing do_exec_prune or we pruned all subplans above + * then we set mt_whichplan to WHICHPLAN_CHOOSE_SUBPLAN so that + * we properly handle those cases during execution. + */ + mtstate->mt_valid_subplans = NULL; + mtstate->mt_whichplan = WHICHPLAN_CHOOSE_SUBPLAN; + } + } + 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_valid_subplans = validsubplans; + mtstate->mt_prune_state = NULL; + + /* Also set the plan to execute first */ + mtstate->mt_whichplan = 0; + } + 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 */ @@ -2342,11 +2432,14 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) */ saved_resultRelInfo = estate->es_result_relation_info; - resultRelInfo = mtstate->resultRelInfo; - i = 0; - foreach(l, node->plans) + j = 0; + i = -1; + while ((i = bms_next_member(validsubplans, i)) >= 0) { - subplan = (Plan *) lfirst(l); + subplan = (Plan *) list_nth(node->plans, i); + + resultRelInfo = &estate->es_result_relations[node->resultRelIndex + i]; + mtstate->resultRelInfos[j] = resultRelInfo; /* Initialize the usesFdwDirectModify flag */ resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i, @@ -2384,9 +2477,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 */ @@ -2402,9 +2495,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) i, eflags); } - - resultRelInfo++; - i++; + j++; } estate->es_result_relation_info = saved_resultRelInfo; @@ -2453,33 +2544,35 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) /* * Initialize any WITH CHECK OPTION constraints if needed. */ - resultRelInfo = mtstate->resultRelInfo; - i = 0; - foreach(l, node->withCheckOptionLists) + if (node->withCheckOptionLists != NIL) { - List *wcoList = (List *) lfirst(l); - List *wcoExprs = NIL; - ListCell *ll; - - foreach(ll, wcoList) + j = 0; + i = -1; + while ((i = bms_next_member(validsubplans, i)) >= 0) { - WithCheckOption *wco = (WithCheckOption *) lfirst(ll); - ExprState *wcoExpr = ExecInitQual((List *) wco->qual, - &mtstate->ps); + List *wcoList = (List *) list_nth(node->withCheckOptionLists, i);; + List *wcoExprs = NIL; - wcoExprs = lappend(wcoExprs, wcoExpr); - } + foreach(l, wcoList) + { + WithCheckOption *wco = (WithCheckOption *) lfirst(l); + ExprState *wcoExpr = ExecInitQual((List *) wco->qual, + &mtstate->ps); + + wcoExprs = lappend(wcoExprs, wcoExpr); + } - resultRelInfo->ri_WithCheckOptions = wcoList; - resultRelInfo->ri_WithCheckOptionExprs = wcoExprs; - resultRelInfo++; - i++; + resultRelInfo = mtstate->resultRelInfos[j]; + resultRelInfo->ri_WithCheckOptions = wcoList; + resultRelInfo->ri_WithCheckOptionExprs = wcoExprs; + j++; + } } /* * Initialize RETURNING projections if needed. */ - if (node->returningLists) + if (node->returningLists != NIL) { TupleTableSlot *slot; ExprContext *econtext; @@ -2502,16 +2595,18 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) /* * Build a projection for each result rel. */ - resultRelInfo = mtstate->resultRelInfo; - foreach(l, node->returningLists) + j = 0; + i = -1; + while ((i = bms_next_member(validsubplans, i)) >= 0) { - List *rlist = (List *) lfirst(l); + List *rlist = (List *) list_nth(node->returningLists, i); + 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 @@ -2522,12 +2617,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; @@ -2618,7 +2711,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]); @@ -2667,12 +2759,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, @@ -2715,13 +2807,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); } } @@ -2760,7 +2851,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 eaab97f753..8270bf53eb 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 e084c3f069..0e9da7f086 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); @@ -2095,6 +2096,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 d5b23a3479..03ecf56fef 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 fc25908dc6..c1096023c6 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); @@ -1236,6 +1237,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags) if (prunequal != NIL) partpruneinfo = make_partition_pruneinfo(root, rel, + NIL, best_path->subpaths, best_path->partitioned_rels, prunequal); @@ -1402,6 +1404,7 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path, if (prunequal != NIL) partpruneinfo = make_partition_pruneinfo(root, rel, + NIL, best_path->subpaths, best_path->partitioned_rels, prunequal); @@ -2587,6 +2590,7 @@ static ModifyTable * create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path) { ModifyTable *plan; + PartitionPruneInfo *partpruneinfos = NULL; List *subplans = NIL; ListCell *subpaths, *subroots; @@ -2618,6 +2622,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->resultRelations, + best_path->subpaths, + best_path->partitioned_rels, + prunequal); + } + plan = make_modifytable(root, best_path->operation, best_path->canSetTag, @@ -2631,7 +2659,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); @@ -6624,7 +6653,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; @@ -6685,6 +6715,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 b44efd6314..6e1300f792 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_modifytbl = 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; } /*---------- @@ -1386,6 +1396,9 @@ inheritance_planner(PlannerInfo *root) { AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); RangeTblEntry *child_rte; + int old_rti; + int new_rti; + /* append_rel_list contains all append rels; ignore others */ if (!bms_is_member(appinfo->parent_relid, parent_relids)) @@ -1400,11 +1413,18 @@ inheritance_planner(PlannerInfo *root) /* and append it to the original rtable */ parse->rtable = lappend(parse->rtable, child_rte); - /* remember child's index in the SELECT rtable */ - old_child_rtis = lappend_int(old_child_rtis, appinfo->child_relid); + old_rti = appinfo->child_relid; + new_rti = list_length(parse->rtable); - /* and its new index in the final rtable */ - new_child_rtis = lappend_int(new_child_rtis, list_length(parse->rtable)); + /* record RT indexes for any RTEs that have changed RT index */ + if (old_rti != new_rti) + { + /* remember child's index in the SELECT rtable */ + old_child_rtis = lappend_int(old_child_rtis, old_rti); + + /* and its new index in the final rtable */ + new_child_rtis = lappend_int(new_child_rtis, new_rti); + } /* if child is itself partitioned, update parent_relids */ if (child_rte->inh) @@ -1426,9 +1446,6 @@ inheritance_planner(PlannerInfo *root) int old_child_rti = lfirst_int(lc); int new_child_rti = lfirst_int(lc2); - if (old_child_rti == new_child_rti) - continue; /* nothing to do */ - Assert(old_child_rti > new_child_rti); ChangeVarNodes((Node *) child_appinfos, @@ -1745,6 +1762,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 modify table */ + dummy_modifytbl = true; } else { @@ -1784,6 +1804,105 @@ 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 makes use of PlannerInfo fields + * from the SELECT planning invocation we performed above. We must + * translate the RT indexes to allow us to use these in 'root'. + */ + if (partition_root && !dummy_modifytbl) + { + RelOptInfo *parent_rel; + int *relid_map; + int i; + + /* create a map to translate old RT indexes into new ones */ + relid_map = palloc(sizeof(int) * partition_root->simple_rel_array_size); + + /* + * initialize the map assuming everything is the same. We'll make + * the required adjustments by looking at the old_child_rtis and + * new_child_rtis lists shortly. + */ + for (i = 1; i < partition_root->simple_rel_array_size; i++) + relid_map[i] = i; + + /* + * Fetch the target partitioned table's RelOptInfo. This may have + * base quals which we can use for run-time pruning. + */ + parent_rel = partition_root->simple_rel_array[top_parentRTindex]; + + final_rel->baserestrictinfo = parent_rel->baserestrictinfo; + + /* adjust varnos of any RTEs that have changed their RT index */ + forboth(lc, old_child_rtis, lc2, new_child_rtis) + { + int old_child_rti = lfirst_int(lc); + int new_child_rti = lfirst_int(lc2); + + Assert(old_child_rti > new_child_rti); + + ChangeVarNodes((Node *) final_rel->baserestrictinfo, + old_child_rti, new_child_rti, 0); + + /* + * While we're at it, adjust the map to account for the changed RT + * index. + */ + relid_map[old_child_rti] = new_child_rti; + } + + /* + * Build a list of each non-leaf partitioned rels which are being + * UPDATEd / DELETEd. + */ + i = -1; + while ((i = bms_next_member(parent_relids, i)) > 0) + partitioned_rels = lappend_int(partitioned_rels, relid_map[i]); + + /* + * In order to build the run-time pruning data we'll need append rels + * of 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) + { + int new_rti = relid_map[i]; + + Assert(root->simple_rel_array[new_rti] == NULL); + + root->simple_rel_array[new_rti] = 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[new_rti] == NULL); + root->append_rel_array[new_rti] = partition_root->append_rel_array[i]; + } + } + + pfree(relid_map); + } + /* Create Path representing a ModifyTable to do the UPDATE/DELETE work */ add_path(final_rel, (Path *) create_modifytable_path(root, final_rel, @@ -1793,6 +1912,7 @@ inheritance_planner(PlannerInfo *root) rootRelation, root->partColsUpdated, resultRelations, + partitioned_rels, subpaths, subroots, withCheckOptionLists, @@ -2376,6 +2496,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 d9ce516211..6fa7de546d 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -3433,6 +3433,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) @@ -3446,8 +3449,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) @@ -3515,6 +3518,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..ecda23f878 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -214,7 +214,12 @@ 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 RT indexes is used to + * build the mapping structures. Otherwise the 'subpaths' List is used. Both + * of these lists correspond to the planner node's subplans list that we're + * building the PartitionPruneInfo for. + * + * 'partitioned_rels' is a List containing Lists of RT indexes 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. @@ -228,8 +233,8 @@ static void partkey_datum_from_expr(PartitionPruneContext *context, */ PartitionPruneInfo * make_partition_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, - List *subpaths, List *partitioned_rels, - List *prunequal) + List *resultRelations, List *subpaths, + List *partitioned_rels, List *prunequal) { PartitionPruneInfo *pruneinfo; Bitmapset *allmatchedsubplans = NULL; @@ -246,21 +251,39 @@ 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. Later, this will allow us to map partition indexes + * into the executor node's subpath list index during query execution. */ - i = 1; - foreach(lc, subpaths) + if (resultRelations != NIL) { - Path *path = (Path *) lfirst(lc); - RelOptInfo *pathrel = path->parent; + i = 1; + foreach(lc, resultRelations) + { + int resultrel = lfirst_int(lc); - Assert(IS_SIMPLE_REL(pathrel)); - Assert(pathrel->relid < root->simple_rel_array_size); - /* No duplicates please */ - Assert(relid_subplan_map[pathrel->relid] == 0); + Assert(resultrel < root->simple_rel_array_size); + /* No duplicates please */ + Assert(relid_subplan_map[resultrel] == 0); + + 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++; + relid_subplan_map[pathrel->relid] = i++; + } } /* We now build a PartitionedRelPruneInfo for each partitioned rel. */ @@ -404,9 +427,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, in that case, + * we're only working with a single partition hierarchy. */ - 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 cd3ddf781f..eb0975f47d 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1169,7 +1169,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 */ @@ -1193,6 +1193,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 execute. */ } ModifyTableState; /* ---------------- diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 0ceb809644..31b0f8fde0 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -1779,6 +1779,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 */ @@ -1788,6 +1790,11 @@ typedef struct ModifyTablePath int epqParam; /* ID of Param for EvalPlanQual re-eval */ } ModifyTablePath; +#define IS_DUMMY_MODIFYTABLE(p) \ + (IsA((p), ModifyTablePath) && \ + list_length((p)->subpaths) == 1 && \ + IS_DUMMY_APPEND(linitial((p)->subpaths))) + /* * LimitPath represents applying LIMIT/OFFSET restrictions */ diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 4869fe7b6d..e350acd7ff 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 */ + /* Info for run-time subplan pruning; NULL if we're not doing that */ + 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..e6e1240135 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -254,8 +254,9 @@ extern ModifyTablePath *create_modifytable_path(PlannerInfo *root, 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); diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h index babdad2c3e..92bf3522a2 100644 --- a/src/include/partitioning/partprune.h +++ b/src/include/partitioning/partprune.h @@ -70,6 +70,7 @@ typedef struct PartitionPruneContext extern PartitionPruneInfo *make_partition_pruneinfo(struct PlannerInfo *root, struct RelOptInfo *parentrel, + List *resultRelations, List *subpaths, List *partitioned_rels, List *prunequal); diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out index 1338b2b23e..b6a13fa596 100644 --- a/src/test/regress/expected/insert_conflict.out +++ b/src/test/regress/expected/insert_conflict.out @@ -211,6 +211,7 @@ explain (costs off, format json) insert into insertconflicttest values (0, 'Bilb "Conflict Resolution": "UPDATE", + "Conflict Arbiter Indexes": ["key_index"], + "Conflict Filter": "(insertconflicttest.fruit <> 'Lime'::text)",+ + "Subplans Removed": 0, + "Plans": [ + { + "Node Type": "Result", + diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index 9c8f80da87..33afaa03f6 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -3196,6 +3196,107 @@ 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) + Subplans Removed: 3 +(2 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;