From f15febf30d6c4b1544fe7b075c55deea4a3b542d Mon Sep 17 00:00:00 2001 From: amitlan Date: Thu, 2 Jul 2020 10:51:45 +0900 Subject: [PATCH v15 2/2] Initialize result relation information lazily Currently, all elements of the ModifyTableState.resultRelInfo array are initialized in ExecInitModifyTable(), possibly wastefully, because only one or a handful of potentially many child result relations appearing in that array may actually have any rows to update or delete. This commit refactors all places that directly access the individual elements of the array to instead go through a lazy-initialization-on- access function, such that only the elements corresponding to result relations that are actually operated on are initialized. This also makes changes a few more things to be performed lazily: * ModifyTableState.mt_partition_tuple_routing in the cross-partition UPDATE case to the first time ExecCrossPartitionUpdate() is called, which allows to get rid of the somewhat convoluted logic used to decide whether ExecInitModifyTable() should initialize it. * ri_ChildToRootMap is now initialized lazily using a lazy- initializing-getter for it. There is a regression test output change in update.out resulting from this change -- whereas previously the error resulting from partition constraint violation of the target table (a sub-partitioned partition that is modified directly) would be shown as occurring on a leaf partition of that table, it is now shown as occurring on that table itself. * Delay the opening of result relation indices, ExecOpenIndices(), to the first time ExecInsert() or ExecUpdate() is called. Couple of other changes: * ri_RootResultRelInfo is now set for *all* child result relations, instead of only those that are tuple routing target relations. This allow code that doesn't get passed ModifyTableState to have access to the command's "root" result relation. That leads to a couple of harmless regression test output changes; see diffs for inherit.out and privilege.out. To distinguish tuple routing target child relations from those owned by a ModifyTable node, the code now must check ri_RangeTableIndex which is only valid (> 0) in the latter. * PartitionTupleRouting.subplan_resultrel_htab is removed in favor of using ModifyTableState.mt_resultOidHash to look up an UPDATE result relation by OID. --- doc/src/sgml/fdwhandler.sgml | 11 +- src/backend/commands/copyfrom.c | 2 +- src/backend/commands/explain.c | 9 +- src/backend/commands/trigger.c | 2 +- src/backend/executor/execMain.c | 11 + src/backend/executor/execPartition.c | 159 +--- src/backend/executor/execUtils.c | 23 + src/backend/executor/nodeModifyTable.c | 1030 +++++++++++----------- src/backend/replication/logical/worker.c | 2 +- src/include/executor/execPartition.h | 1 - src/include/executor/executor.h | 6 +- src/include/nodes/execnodes.h | 7 +- src/test/regress/expected/inherit.out | 2 +- src/test/regress/expected/privileges.out | 2 +- src/test/regress/expected/update.out | 12 +- 15 files changed, 645 insertions(+), 634 deletions(-) diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml index 5e3ec5c0e9..3d20e5b7e0 100644 --- a/doc/src/sgml/fdwhandler.sgml +++ b/doc/src/sgml/fdwhandler.sgml @@ -525,12 +525,13 @@ BeginForeignModify(ModifyTableState *mtstate, int eflags); - Begin executing a foreign table modification operation. This routine is - called during executor startup. It should perform any initialization - needed prior to the actual table modifications. Subsequently, - ExecForeignInsert/ExecForeignBatchInsert, + Begin executing a foreign table modification operation. This is called + right before executing the subplan to fetch the tuples to be modified. + It should perform any initialization needed prior to the actual table + modifications. Subsequently, ExecForeignInsert/ + ExecForeignBatchInsert, ExecForeignUpdate or - ExecForeignDelete will be called for tuple(s) to be + ExecForeignDelete will be called for each tuple to be inserted, updated, or deleted. diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c index 74dbb709fe..f430042da3 100644 --- a/src/backend/commands/copyfrom.c +++ b/src/backend/commands/copyfrom.c @@ -696,7 +696,7 @@ CopyFrom(CopyFromState cstate) * CopyFrom tuple routing. */ if (cstate->rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - proute = ExecSetupPartitionTupleRouting(estate, NULL, cstate->rel); + proute = ExecSetupPartitionTupleRouting(estate, cstate->rel); if (cstate->whereClause) cstate->qualexpr = ExecInitQual(castNode(List, cstate->whereClause), diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 872aaa7aed..1aac176d96 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -18,6 +18,7 @@ #include "commands/createas.h" #include "commands/defrem.h" #include "commands/prepare.h" +#include "executor/executor.h" #include "executor/nodeHash.h" #include "foreign/fdwapi.h" #include "jit/jit.h" @@ -3690,6 +3691,7 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors, int j; List *idxNames = NIL; ListCell *lst; + ResultRelInfo *firstResultRel; switch (node->operation) { @@ -3711,17 +3713,20 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors, break; } + Assert(mtstate->rootResultRelInfo != NULL); + firstResultRel = ExecGetResultRelation(mtstate, 0); + /* Should we explicitly label target relations? */ labeltargets = (mtstate->mt_nrels > 1 || (mtstate->mt_nrels == 1 && - mtstate->resultRelInfo[0].ri_RangeTableIndex != node->nominalRelation)); + firstResultRel->ri_RangeTableIndex != node->nominalRelation)); if (labeltargets) ExplainOpenGroup("Target Tables", "Target Tables", false, es); for (j = 0; j < mtstate->mt_nrels; j++) { - ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j; + ResultRelInfo *resultRelInfo = ExecGetResultRelation(mtstate, j); FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine; if (labeltargets) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index a5ceb1698c..3421014e47 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -5479,7 +5479,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, if (row_trigger && transition_capture != NULL) { TupleTableSlot *original_insert_tuple = transition_capture->tcs_original_insert_tuple; - TupleConversionMap *map = relinfo->ri_ChildToRootMap; + TupleConversionMap *map = ExecGetChildToRootMap(relinfo); bool delete_old_table = transition_capture->tcs_delete_old_table; bool update_old_table = transition_capture->tcs_update_old_table; bool update_new_table = transition_capture->tcs_update_new_table; diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 163242f54e..3437600314 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -1231,11 +1231,17 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo, resultRelInfo->ri_ReturningSlot = NULL; resultRelInfo->ri_TrigOldSlot = NULL; resultRelInfo->ri_TrigNewSlot = NULL; + /* + * Only ExecInitPartitionInfo() passes partition_root_rri. For child + * relations that are not tuple routing target relations, this is set in + * ExecGetResultRelation(). + */ resultRelInfo->ri_RootResultRelInfo = partition_root_rri; resultRelInfo->ri_RootToPartitionMap = NULL; /* set by * ExecInitRoutingInfo */ resultRelInfo->ri_PartitionTupleSlot = NULL; /* ditto */ resultRelInfo->ri_ChildToRootMap = NULL; + resultRelInfo->ri_ChildToRootMapValid = false; resultRelInfo->ri_CopyMultiInsertBuffer = NULL; } @@ -1429,6 +1435,11 @@ ExecCloseResultRelations(EState *estate) ResultRelInfo *resultRelInfo = lfirst(l); ExecCloseIndices(resultRelInfo); + if (!resultRelInfo->ri_usesFdwDirectModify && + resultRelInfo->ri_FdwRoutine != NULL && + resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL) + resultRelInfo->ri_FdwRoutine->EndForeignModify(estate, + resultRelInfo); } /* Close any relations that have been opened by ExecGetTriggerResultRel(). */ diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c index 558060e080..a3a65e488f 100644 --- a/src/backend/executor/execPartition.c +++ b/src/backend/executor/execPartition.c @@ -68,9 +68,15 @@ * Array of 'max_partitions' elements containing a pointer to a * ResultRelInfo for every leaf partitions touched by tuple routing. * Some of these are pointers to ResultRelInfos which are borrowed out of - * 'subplan_resultrel_htab'. The remainder have been built especially - * for tuple routing. See comment for PartitionDispatchData->indexes for - * details on how this array is indexed. + * the owning ModifyTableState node. The remainder have been built + * especially for tuple routing. See comment for + * PartitionDispatchData->indexes for details on how this array is + * indexed. + * + * is_update_rel + * Array of 'max_partitions' booleans recording whether a given entry + * in 'partitions' is a ResultRelInfo pointer borrowed from a matching + * UPDATE result relation in the owning ModifyTableState node * * num_partitions * The current number of items stored in the 'partitions' array. Also @@ -80,12 +86,6 @@ * max_partitions * The current allocated size of the 'partitions' array. * - * subplan_resultrel_htab - * Hash table to store subplan ResultRelInfos by Oid. This is used to - * cache ResultRelInfos from targets of an UPDATE ModifyTable node; - * NULL in other cases. Some of these may be useful for tuple routing - * to save having to build duplicates. - * * memcxt * Memory context used to allocate subsidiary structs. *----------------------- @@ -98,9 +98,9 @@ struct PartitionTupleRouting int num_dispatch; int max_dispatch; ResultRelInfo **partitions; + bool *is_update_rel; int num_partitions; int max_partitions; - HTAB *subplan_resultrel_htab; MemoryContext memcxt; }; @@ -153,16 +153,7 @@ typedef struct PartitionDispatchData int indexes[FLEXIBLE_ARRAY_MEMBER]; } PartitionDispatchData; -/* struct to hold result relations coming from UPDATE subplans */ -typedef struct SubplanResultRelHashElem -{ - Oid relid; /* hash key -- must be first */ - ResultRelInfo *rri; -} SubplanResultRelHashElem; - -static void ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate, - PartitionTupleRouting *proute); static ResultRelInfo *ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, PartitionTupleRouting *proute, PartitionDispatch dispatch, @@ -173,7 +164,7 @@ static void ExecInitRoutingInfo(ModifyTableState *mtstate, PartitionTupleRouting *proute, PartitionDispatch dispatch, ResultRelInfo *partRelInfo, - int partidx); + int partidx, bool is_update_rel); static PartitionDispatch ExecInitPartitionDispatchInfo(EState *estate, PartitionTupleRouting *proute, Oid partoid, PartitionDispatch parent_pd, @@ -215,11 +206,9 @@ static void find_matching_subplans_recurse(PartitionPruningData *prunedata, * it should be estate->es_query_cxt. */ PartitionTupleRouting * -ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate, - Relation rel) +ExecSetupPartitionTupleRouting(EState *estate, Relation rel) { PartitionTupleRouting *proute; - ModifyTable *node = mtstate ? (ModifyTable *) mtstate->ps.plan : NULL; /* * Here we attempt to expend as little effort as possible in setting up @@ -241,17 +230,6 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate, ExecInitPartitionDispatchInfo(estate, proute, RelationGetRelid(rel), NULL, 0, NULL); - /* - * If performing an UPDATE with tuple routing, we can reuse partition - * sub-plan result rels. We build a hash table to map the OIDs of - * partitions present in mtstate->resultRelInfo to their ResultRelInfos. - * Every time a tuple is routed to a partition that we've yet to set the - * ResultRelInfo for, before we go to the trouble of making one, we check - * for a pre-made one in the hash table. - */ - if (node && node->operation == CMD_UPDATE) - ExecHashSubPlanResultRelsByOid(mtstate, proute); - return proute; } @@ -351,7 +329,6 @@ ExecFindPartition(ModifyTableState *mtstate, is_leaf = partdesc->is_leaf[partidx]; if (is_leaf) { - /* * We've reached the leaf -- hurray, we're done. Look to see if * we've already got a ResultRelInfo for this partition. @@ -368,20 +345,18 @@ ExecFindPartition(ModifyTableState *mtstate, /* * We have not yet set up a ResultRelInfo for this partition, - * but if we have a subplan hash table, we might have one - * there. If not, we'll have to create one. + * but if the partition is also an UPDATE result relation, use + * the one in mtstate->resultRelInfo instead of creating a new + * one with ExecInitPartitionInfo(). */ - if (proute->subplan_resultrel_htab) + if (mtstate->operation == CMD_UPDATE && mtstate->ps.plan) { Oid partoid = partdesc->oids[partidx]; - SubplanResultRelHashElem *elem; - elem = hash_search(proute->subplan_resultrel_htab, - &partoid, HASH_FIND, NULL); - if (elem) + rri = ExecLookupResultRelByOid(mtstate, partoid, true); + if (rri) { found = true; - rri = elem->rri; /* Verify this ResultRelInfo allows INSERTs */ CheckValidResultRel(rri, CMD_INSERT); @@ -391,7 +366,7 @@ ExecFindPartition(ModifyTableState *mtstate, * subsequent tuples routed to this partition. */ ExecInitRoutingInfo(mtstate, estate, proute, dispatch, - rri, partidx); + rri, partidx, true); } } @@ -509,50 +484,6 @@ ExecFindPartition(ModifyTableState *mtstate, return rri; } -/* - * ExecHashSubPlanResultRelsByOid - * Build a hash table to allow fast lookups of subplan ResultRelInfos by - * partition Oid. We also populate the subplan ResultRelInfo with an - * ri_PartitionRoot. - */ -static void -ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate, - PartitionTupleRouting *proute) -{ - HASHCTL ctl; - HTAB *htab; - int i; - - ctl.keysize = sizeof(Oid); - ctl.entrysize = sizeof(SubplanResultRelHashElem); - ctl.hcxt = CurrentMemoryContext; - - htab = hash_create("PartitionTupleRouting table", mtstate->mt_nrels, - &ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); - proute->subplan_resultrel_htab = htab; - - /* Hash all subplans by their Oid */ - for (i = 0; i < mtstate->mt_nrels; i++) - { - ResultRelInfo *rri = &mtstate->resultRelInfo[i]; - bool found; - Oid partoid = RelationGetRelid(rri->ri_RelationDesc); - SubplanResultRelHashElem *elem; - - elem = (SubplanResultRelHashElem *) - hash_search(htab, &partoid, HASH_ENTER, &found); - Assert(!found); - elem->rri = rri; - - /* - * This is required in order to convert the partition's tuple to be - * compatible with the root partitioned table's tuple descriptor. When - * generating the per-subplan result rels, this was not set. - */ - rri->ri_RootResultRelInfo = mtstate->rootResultRelInfo; - } -} - /* * ExecInitPartitionInfo * Lock the partition and initialize ResultRelInfo. Also setup other @@ -571,8 +502,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, ModifyTable *node = (ModifyTable *) mtstate->ps.plan; Oid partOid = dispatch->partdesc->oids[partidx]; Relation partrel; - int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex; - Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc; + Relation firstResultRel = NULL; + Index firstVarno = 0; ResultRelInfo *leaf_part_rri; MemoryContext oldcxt; AttrMap *part_attmap = NULL; @@ -608,12 +539,21 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, (node != NULL && node->onConflictAction != ONCONFLICT_NONE)); + if (node) + { + ResultRelInfo *firstResultRelInfo = + ExecGetResultRelation(mtstate, 0); + + firstResultRel = firstResultRelInfo->ri_RelationDesc; + firstVarno = firstResultRelInfo->ri_RangeTableIndex; + } + /* * Build WITH CHECK OPTION constraints for the partition. Note that we * didn't build the withCheckOptionList for partitions within the planner, * but simple translation of varattnos will suffice. This only occurs for * the INSERT case or in the case of UPDATE tuple routing where we didn't - * find a result rel to reuse in ExecSetupPartitionTupleRouting(). + * find a result rel to reuse. */ if (node && node->withCheckOptionLists != NIL) { @@ -734,7 +674,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, /* Set up information needed for routing tuples to the partition. */ ExecInitRoutingInfo(mtstate, estate, proute, dispatch, - leaf_part_rri, partidx); + leaf_part_rri, partidx, false); /* * If there is an ON CONFLICT clause, initialize state for it. @@ -910,15 +850,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, } } - /* - * Also, if transition capture is required, store a map to convert tuples - * from partition's rowtype to the root partition table's. - */ - if (mtstate->mt_transition_capture || mtstate->mt_oc_transition_capture) - leaf_part_rri->ri_ChildToRootMap = - convert_tuples_by_name(RelationGetDescr(leaf_part_rri->ri_RelationDesc), - RelationGetDescr(rootResultRelInfo->ri_RelationDesc)); - /* * Since we've just initialized this ResultRelInfo, it's not in any list * attached to the estate as yet. Add it, so that it can be found later. @@ -949,7 +880,7 @@ ExecInitRoutingInfo(ModifyTableState *mtstate, PartitionTupleRouting *proute, PartitionDispatch dispatch, ResultRelInfo *partRelInfo, - int partidx) + int partidx, bool is_update_rel) { ResultRelInfo *rootRelInfo = partRelInfo->ri_RootResultRelInfo; MemoryContext oldcxt; @@ -1029,6 +960,8 @@ ExecInitRoutingInfo(ModifyTableState *mtstate, proute->max_partitions = 8; proute->partitions = (ResultRelInfo **) palloc(sizeof(ResultRelInfo *) * proute->max_partitions); + proute->is_update_rel = (bool *) + palloc(sizeof(bool) * proute->max_partitions); } else { @@ -1036,10 +969,14 @@ ExecInitRoutingInfo(ModifyTableState *mtstate, proute->partitions = (ResultRelInfo **) repalloc(proute->partitions, sizeof(ResultRelInfo *) * proute->max_partitions); + proute->is_update_rel = (bool *) + repalloc(proute->is_update_rel, sizeof(bool) * + proute->max_partitions); } } proute->partitions[rri_index] = partRelInfo; + proute->is_update_rel[rri_index] = is_update_rel; dispatch->indexes[partidx] = rri_index; MemoryContextSwitchTo(oldcxt); @@ -1199,7 +1136,6 @@ void ExecCleanupTupleRouting(ModifyTableState *mtstate, PartitionTupleRouting *proute) { - HTAB *htab = proute->subplan_resultrel_htab; int i; /* @@ -1230,20 +1166,11 @@ ExecCleanupTupleRouting(ModifyTableState *mtstate, resultRelInfo); /* - * Check if this result rel is one belonging to the node's subplans, - * if so, let ExecEndPlan() clean it up. + * Close it if not one of the result relations borrowed from the owning + * ModifyTableState, because those are closed by ExecEndPlan(). */ - if (htab) - { - Oid partoid; - bool found; - - partoid = RelationGetRelid(resultRelInfo->ri_RelationDesc); - - (void) hash_search(htab, &partoid, HASH_FIND, &found); - if (found) - continue; - } + if (proute->is_update_rel[i]) + continue; ExecCloseIndices(resultRelInfo); table_close(resultRelInfo->ri_RelationDesc, NoLock); diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index 42632cb4d8..dbaef76448 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -1323,3 +1323,26 @@ ExecGetAllUpdatedCols(ResultRelInfo *relinfo, EState *estate) return bms_union(ExecGetUpdatedCols(relinfo, estate), ExecGetExtraUpdatedCols(relinfo, estate)); } + +/* + * Returns the map needed to convert given child relation's tuples to the + * query's main target ("root") relation's format, possibly initializing it + * if not already done. + */ +TupleConversionMap * +ExecGetChildToRootMap(ResultRelInfo *resultRelInfo) +{ + if (!resultRelInfo->ri_ChildToRootMapValid && + resultRelInfo->ri_RootResultRelInfo) + { + ResultRelInfo *targetRelInfo; + + targetRelInfo = resultRelInfo->ri_RootResultRelInfo; + resultRelInfo->ri_ChildToRootMap = + convert_tuples_by_name(RelationGetDescr(resultRelInfo->ri_RelationDesc), + RelationGetDescr(targetRelInfo->ri_RelationDesc)); + resultRelInfo->ri_ChildToRootMapValid = true; + } + + return resultRelInfo->ri_ChildToRootMap; +} diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index bf65785e64..01eef7b468 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -190,6 +190,361 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo, return ExecProject(projectReturning); } +/* + * ExecGetResultRelation + * Returns mtstate->resultRelInfo[whichrel], possibly initializing it + * if being requested for the first time + */ +ResultRelInfo * +ExecGetResultRelation(ModifyTableState *mtstate, int whichrel) +{ + EState *estate = mtstate->ps.state; + ModifyTable *plan = (ModifyTable *) mtstate->ps.plan; + Index rti; + ResultRelInfo *resultRelInfo = NULL; + + /* + * Initialized result relations are added to es_result_relations, so check + * there first. Remember that es_result_relations is indexed by RT index, + * so fetch the relation's RT index from the plan. + */ + Assert(plan != NULL); + Assert(whichrel >= 0 && whichrel < mtstate->mt_nrels); + rti = list_nth_int(plan->resultRelations, whichrel); + if (estate->es_result_relations) + resultRelInfo = estate->es_result_relations[rti - 1]; + + if (resultRelInfo == NULL) + { + /* Nope, so initialize. */ + int eflags = estate->es_top_eflags; + CmdType operation = mtstate->operation; + PlanState *subplanstate = outerPlanState(mtstate); + Plan *subplan = subplanstate->plan; + ListCell *l; + MemoryContext oldcxt; + + Assert(whichrel >= 0); + resultRelInfo = &mtstate->resultRelInfo[whichrel]; + + /* Things built here have to last for the query duration. */ + oldcxt = MemoryContextSwitchTo(estate->es_query_cxt); + + /* + * Perform InitResultRelInfo() and save the pointer in + * es_result_relations. + */ + ExecInitResultRelation(estate, resultRelInfo, rti); + + /* + * A few more initializations that are not handled by + * InitResultRelInfo() follow. + */ + + /* + * Verify result relation is a valid target for the current operation. + */ + CheckValidResultRel(resultRelInfo, operation); + + /* + * For child result relations, store the root result relation pointer + * to be used in places where the mtstate is not available. + */ + if (resultRelInfo != mtstate->rootResultRelInfo) + resultRelInfo->ri_RootResultRelInfo = mtstate->rootResultRelInfo; + + /* Initialize the usesFdwDirectModify flag */ + resultRelInfo->ri_usesFdwDirectModify = bms_is_member(whichrel, + plan->fdwDirectModifyPlans); + + /* Also let FDWs init themselves for foreign-table result rels */ + if (!resultRelInfo->ri_usesFdwDirectModify && + resultRelInfo->ri_FdwRoutine != NULL && + resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL) + { + List *fdw_private = (List *) list_nth(plan->fdwPrivLists, + whichrel); + + resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate, + resultRelInfo, + fdw_private, + whichrel, + eflags); + } + + /* Initilize WITH CHECK OPTIONS expressions. */ + if (plan->withCheckOptionLists) + { + List *wcoList; + List *wcoExprs = NIL; + ListCell *ll; + + wcoList = (List *) list_nth(plan->withCheckOptionLists, whichrel); + foreach(ll, wcoList) + { + WithCheckOption *wco = (WithCheckOption *) lfirst(ll); + ExprState *wcoExpr = ExecInitQual((List *) wco->qual, + &mtstate->ps); + + wcoExprs = lappend(wcoExprs, wcoExpr); + } + + resultRelInfo->ri_WithCheckOptions = wcoList; + resultRelInfo->ri_WithCheckOptionExprs = wcoExprs; + } + + /* Initilize RETURNING expressions. */ + if (plan->returningLists) + { + List *rlist; + TupleTableSlot *slot; + ExprContext *econtext; + + rlist = (List *) list_nth(plan->returningLists, whichrel); + slot = mtstate->ps.ps_ResultTupleSlot; + Assert(slot != NULL); + econtext = mtstate->ps.ps_ExprContext; + Assert(econtext != NULL); + + resultRelInfo->ri_returningList = rlist; + resultRelInfo->ri_projectReturning = + ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps, + resultRelInfo->ri_RelationDesc->rd_att); + } + + /* + * Determine if the FDW supports batch insert and determine the batch + * size (a FDW may support batching, but it may be disabled for the + * server/table). + * + * We only do this for INSERT, so that for UPDATE/DELETE the batch + * size remains set to 0. + */ + if (operation == CMD_INSERT) + { + if (!resultRelInfo->ri_usesFdwDirectModify && + resultRelInfo->ri_FdwRoutine != NULL && + resultRelInfo->ri_FdwRoutine->GetForeignModifyBatchSize && + resultRelInfo->ri_FdwRoutine->ExecForeignBatchInsert) + resultRelInfo->ri_BatchSize = + resultRelInfo->ri_FdwRoutine->GetForeignModifyBatchSize(resultRelInfo); + else + resultRelInfo->ri_BatchSize = 1; + + Assert(resultRelInfo->ri_BatchSize >= 1); + } + + /* Set the list of arbiter indexes if needed for ON CONFLICT */ + if (plan->onConflictAction != ONCONFLICT_NONE) + resultRelInfo->ri_onConflictArbiterIndexes = plan->arbiterIndexes; + + /* + * If needed, Initialize target list, projection and qual for ON CONFLICT + * DO UPDATE. + */ + if (plan->onConflictAction == ONCONFLICT_UPDATE) + { + ExprContext *econtext; + TupleDesc relationDesc; + TupleDesc tupDesc; + + /* + * insert may only have one relation, inheritance is not expanded. + */ + Assert(mtstate->mt_nrels == 1); + + /* already exists if created by RETURNING processing above */ + if (mtstate->ps.ps_ExprContext == NULL) + ExecAssignExprContext(estate, &mtstate->ps); + + econtext = mtstate->ps.ps_ExprContext; + relationDesc = resultRelInfo->ri_RelationDesc->rd_att; + + /* create state for DO UPDATE SET operation */ + resultRelInfo->ri_onConflict = makeNode(OnConflictSetState); + + /* initialize slot for the existing tuple */ + resultRelInfo->ri_onConflict->oc_Existing = + table_slot_create(resultRelInfo->ri_RelationDesc, + &mtstate->ps.state->es_tupleTable); + + /* + * Create the tuple slot for the UPDATE SET projection. We want a + * slot of the table's type here, because the slot will be used to + * insert into the table, and for RETURNING processing - which may + * access system attributes. + */ + tupDesc = ExecTypeFromTL((List *) plan->onConflictSet); + resultRelInfo->ri_onConflict->oc_ProjSlot = + ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc, + table_slot_callbacks(resultRelInfo->ri_RelationDesc)); + + /* build UPDATE SET projection state */ + resultRelInfo->ri_onConflict->oc_ProjInfo = + ExecBuildProjectionInfo(plan->onConflictSet, econtext, + resultRelInfo->ri_onConflict->oc_ProjSlot, + &mtstate->ps, + relationDesc); + + /* initialize state to evaluate the WHERE clause, if any */ + if (plan->onConflictWhere) + { + ExprState *qualexpr; + + qualexpr = ExecInitQual((List *) plan->onConflictWhere, + &mtstate->ps); + resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr; + } + } + + /* + * Initialize projection(s) to create tuples suitable for result + * rel(s). INSERT queries may need a projection to filter out junk + * attrs in the tlist. UPDATE always needs a projection, because (1) + * there's always some junk attrs, and (2) we may need to merge values + * of not-updated columns from the old tuple into the final tuple. In + * UPDATE, the tuple arriving from the subplan contains only new values + * for the changed columns, plus row identity info in the junk attrs. + * + * If there are multiple result relations, each one needs its own + * projection. Note multiple rels are only possible for UPDATE/DELETE, + * so we can't be fooled by some needing a projection and some not. + * + * This section of code is also a convenient place to verify that the + * output of an INSERT or UPDATE matches the target table(s). + */ + + /* + * Prepare to generate tuples suitable for the target relation. + */ + if (operation == CMD_INSERT) + { + List *insertTargetList = NIL; + bool need_projection = false; + + foreach(l, subplan->targetlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(l); + + if (!tle->resjunk) + insertTargetList = lappend(insertTargetList, tle); + else + need_projection = true; + } + + /* + * The junk-free list must produce a tuple suitable for the result + * relation. + */ + ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, + insertTargetList); + + /* We'll need a slot matching the table's format. */ + resultRelInfo->ri_newTupleSlot = + table_slot_create(resultRelInfo->ri_RelationDesc, + &mtstate->ps.state->es_tupleTable); + + /* Build ProjectionInfo if needed (it probably isn't). */ + if (need_projection) + { + TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc); + + /* need an expression context to do the projection */ + if (mtstate->ps.ps_ExprContext == NULL) + ExecAssignExprContext(estate, &mtstate->ps); + + resultRelInfo->ri_projectNew = + ExecBuildProjectionInfo(insertTargetList, + mtstate->ps.ps_ExprContext, + resultRelInfo->ri_newTupleSlot, + &mtstate->ps, + relDesc); + } + } + else if (operation == CMD_UPDATE) + { + List *updateColnos; + TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc); + + updateColnos = (List *) list_nth(plan->updateColnosLists, + whichrel); + + /* + * For UPDATE, we use the old tuple to fill up missing values in + * the tuple produced by the plan to get the new tuple. We need + * two slots, both matching the table's desired format. + */ + resultRelInfo->ri_oldTupleSlot = + table_slot_create(resultRelInfo->ri_RelationDesc, + &mtstate->ps.state->es_tupleTable); + resultRelInfo->ri_newTupleSlot = + table_slot_create(resultRelInfo->ri_RelationDesc, + &mtstate->ps.state->es_tupleTable); + + /* need an expression context to do the projection */ + if (mtstate->ps.ps_ExprContext == NULL) + ExecAssignExprContext(estate, &mtstate->ps); + + resultRelInfo->ri_projectNew = + ExecBuildUpdateProjection(subplan->targetlist, + updateColnos, + relDesc, + mtstate->ps.ps_ExprContext, + resultRelInfo->ri_newTupleSlot, + &mtstate->ps); + } + + /* + * For UPDATE/DELETE, find the appropriate junk attr now, either a + * 'ctid' or 'wholerow' attribute depending on relkind. For foreign + * tables, the FDW might have created additional junk attr(s), but + * those are no concern of ours. + */ + if (operation == CMD_UPDATE || operation == CMD_DELETE) + { + char relkind; + + relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind; + if (relkind == RELKIND_RELATION || + relkind == RELKIND_MATVIEW || + relkind == RELKIND_PARTITIONED_TABLE) + { + resultRelInfo->ri_RowIdAttNo = + ExecFindJunkAttributeInTlist(subplan->targetlist, "ctid"); + if (!AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo)) + elog(ERROR, "could not find junk ctid column"); + } + else if (relkind == RELKIND_FOREIGN_TABLE) + { + /* + * When there is a row-level trigger, there should be a + * wholerow attribute. We also require it to be present in + * UPDATE, so we can get the values of unchanged columns. + */ + resultRelInfo->ri_RowIdAttNo = + ExecFindJunkAttributeInTlist(subplan->targetlist, + "wholerow"); + if (mtstate->operation == CMD_UPDATE && + !AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo)) + elog(ERROR, "could not find junk wholerow column"); + } + else + { + /* Other valid target relkinds must provide wholerow */ + resultRelInfo->ri_RowIdAttNo = + ExecFindJunkAttributeInTlist(subplan->targetlist, + "wholerow"); + if (!AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo)) + elog(ERROR, "could not find junk wholerow column"); + } + } + + MemoryContextSwitchTo(oldcxt); + } + + return resultRelInfo; +} + /* * ExecCheckTupleVisible -- verify tuple is visible * @@ -488,6 +843,9 @@ ExecInsert(ModifyTableState *mtstate, resultRelInfo = partRelInfo; } + if (resultRelInfo->ri_IndexRelationDescs == NULL) + ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE); + ExecMaterializeSlot(slot); resultRelationDesc = resultRelInfo->ri_RelationDesc; @@ -641,7 +999,7 @@ ExecInsert(ModifyTableState *mtstate, * if there's no BR trigger defined on the partition. */ if (resultRelationDesc->rd_rel->relispartition && - (resultRelInfo->ri_RootResultRelInfo == NULL || + (resultRelInfo->ri_RangeTableIndex != 0 || (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_insert_before_row))) ExecPartitionCheck(resultRelInfo, slot, estate, true); @@ -1276,7 +1634,6 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate, TupleTableSlot **inserted_tuple) { EState *estate = mtstate->ps.state; - PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing; TupleConversionMap *tupconv_map; bool tuple_deleted; TupleTableSlot *epqslot = NULL; @@ -1295,13 +1652,27 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate, errmsg("invalid ON UPDATE specification"), errdetail("The result tuple would appear in a different partition than the original tuple."))); - /* - * When an UPDATE is run on a leaf partition, we will not have partition - * tuple routing set up. In that case, fail with partition constraint - * violation error. - */ - if (proute == NULL) - ExecPartitionCheckEmitError(resultRelInfo, slot, estate); + /* Initialize tuple routing info if not already done. */ + if (mtstate->mt_partition_tuple_routing == NULL) + { + Relation targetRel = mtstate->rootResultRelInfo->ri_RelationDesc; + MemoryContext oldcxt; + + /* Things built here have to last for the query duration. */ + oldcxt = MemoryContextSwitchTo(estate->es_query_cxt); + + mtstate->mt_partition_tuple_routing = + ExecSetupPartitionTupleRouting(estate, targetRel); + + /* + * Before a partition's tuple can be re-routed, it must first + * be converted to the root's format and we need a slot for + * storing such tuple. + */ + Assert(mtstate->mt_root_tuple_slot == NULL); + mtstate->mt_root_tuple_slot = table_slot_create(targetRel, NULL); + MemoryContextSwitchTo(oldcxt); + } /* * Row movement, part 1. Delete the tuple, but skip RETURNING processing. @@ -1364,7 +1735,7 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate, * convert the tuple into root's tuple descriptor if needed, since * ExecInsert() starts the search from root. */ - tupconv_map = resultRelInfo->ri_ChildToRootMap; + tupconv_map = ExecGetChildToRootMap(resultRelInfo); if (tupconv_map != NULL) slot = execute_attr_map_slot(tupconv_map->attrMap, slot, @@ -1434,6 +1805,9 @@ ExecUpdate(ModifyTableState *mtstate, if (IsBootstrapProcessingMode()) elog(ERROR, "cannot UPDATE during bootstrap"); + if (resultRelInfo->ri_IndexRelationDescs == NULL) + ExecOpenIndices(resultRelInfo, false); + ExecMaterializeSlot(slot); /* BEFORE ROW UPDATE Triggers */ @@ -1547,6 +1921,13 @@ lreplace:; *retry_slot; bool retry; + /* + * When an UPDATE is run directly on a leaf partition, simply fail + * with partition constraint violation error. + */ + if (resultRelInfo == mtstate->rootResultRelInfo) + ExecPartitionCheckEmitError(resultRelInfo, slot, estate); + /* * ExecCrossPartitionUpdate will first DELETE the row from the * partition it's currently in and then insert it back into the @@ -2148,7 +2529,7 @@ ExecModifyTable(PlanState *pstate) ModifyTableState *node = castNode(ModifyTableState, pstate); EState *estate = node->ps.state; CmdType operation = node->operation; - ResultRelInfo *resultRelInfo; + ResultRelInfo *resultRelInfo = NULL; PlanState *subplanstate; TupleTableSlot *slot; TupleTableSlot *planSlot; @@ -2194,7 +2575,6 @@ ExecModifyTable(PlanState *pstate) } /* Preload local variables */ - resultRelInfo = node->resultRelInfo + node->mt_lastResultIndex; subplanstate = outerPlanState(node); /* @@ -2244,39 +2624,14 @@ ExecModifyTable(PlanState *pstate) /* If it's not the same as last time, we need to locate the rel */ if (resultoid != node->mt_lastResultOid) - { - if (node->mt_resultOidHash) - { - /* Use the pre-built hash table to locate the rel */ - MTTargetRelLookup *mtlookup; - - mtlookup = (MTTargetRelLookup *) - hash_search(node->mt_resultOidHash, &resultoid, - HASH_FIND, NULL); - if (!mtlookup) - elog(ERROR, "incorrect result rel OID %u", resultoid); - node->mt_lastResultOid = resultoid; - node->mt_lastResultIndex = mtlookup->relationIndex; - resultRelInfo = node->resultRelInfo + mtlookup->relationIndex; - } - else - { - /* With few target rels, just do a simple search */ - int ndx; + resultRelInfo = ExecLookupResultRelByOid(node, resultoid, + false); + } - for (ndx = 0; ndx < node->mt_nrels; ndx++) - { - resultRelInfo = node->resultRelInfo + ndx; - if (RelationGetRelid(resultRelInfo->ri_RelationDesc) == resultoid) - break; - } - if (ndx >= node->mt_nrels) - elog(ERROR, "incorrect result rel OID %u", resultoid); - node->mt_lastResultOid = resultoid; - node->mt_lastResultIndex = ndx; - } - } - } + if (resultRelInfo == NULL) + resultRelInfo = ExecGetResultRelation(node, + node->mt_lastResultIndex); + Assert(resultRelInfo != NULL); /* * If resultRelInfo->ri_usesFdwDirectModify is true, all we need to do @@ -2477,12 +2832,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) Plan *subplan = outerPlan(node); CmdType operation = node->operation; int nrels = list_length(node->resultRelations); - ResultRelInfo *resultRelInfo; List *arowmarks; ListCell *l; int i; Relation rel; - bool update_tuple_routing_needed = node->partColsUpdated; /* check for unsupported flags */ Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK))); @@ -2503,6 +2856,39 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) mtstate->resultRelInfo = (ResultRelInfo *) palloc(nrels * sizeof(ResultRelInfo)); + /* Initialize some global state for RETURNING projections. */ + if (node->returningLists) + { + /* + * 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); + + /* Set up a slot for the output of the RETURNING projection(s) */ + ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual); + + /* Need an econtext too */ + if (mtstate->ps.ps_ExprContext == NULL) + ExecAssignExprContext(estate, &mtstate->ps); + } + else + { + /* + * We still must construct a dummy result tuple type, because InitPlan + * expects one (maybe should change that?). + */ + mtstate->ps.plan->targetlist = NIL; + ExecInitResultTypeTL(&mtstate->ps); + + mtstate->ps.ps_ExprContext = NULL; + } + + /* + * Initialize the subplan. Must do before allocating any ResultRelInfos. + */ + outerPlanState(mtstate) = ExecInitNode(subplan, estate, eflags); + /*---------- * Resolve the target relation. This is the same as: * @@ -2511,13 +2897,18 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) * must be converted, and * - the root partitioned table used for tuple routing. * - * If it's a partitioned table, the root partition doesn't appear - * elsewhere in the plan and its RT index is given explicitly in - * node->rootRelation. Otherwise (i.e. table inheritance) the target - * relation is the first relation in the node->resultRelations list. + * If it's a partitioned table, given that its RT index doesn't appear in + * node->resultRelations unless for an INSERT, the node->resultRelInfo + * array doesn't have a slot for it, so we must allocate a ResultRelInfo + * for it separately. It need not be initialized fully, so it suffices + * to perform just ExecInitResultRelation(). In other cases, including + * the case of INSERT into a partitioned table, initialize the first + * element of node->resultRelInfo corresponding to the first relation in + * node->resultRelations, using ExecGetResultRelation() so that all the + * necessary initializations are performed. *---------- */ - if (node->rootRelation > 0) + if (node->rootRelation > 0 && operation != CMD_INSERT) { mtstate->rootResultRelInfo = makeNode(ResultRelInfo); ExecInitResultRelation(estate, mtstate->rootResultRelInfo, @@ -2526,8 +2917,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) else { mtstate->rootResultRelInfo = mtstate->resultRelInfo; - ExecInitResultRelation(estate, mtstate->resultRelInfo, - linitial_int(node->resultRelations)); + (void) ExecGetResultRelation(mtstate, 0); } /* set up epqstate with dummy subplan data for the moment */ @@ -2541,266 +2931,18 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY)) ExecSetupTransitionCaptureState(mtstate, estate); - /* - * Open all the result relations and initialize the ResultRelInfo structs. - * (But root relation was initialized above, if it's part of the array.) - * We must do this before initializing the subplan, because direct-modify - * FDWs expect their ResultRelInfos to be available. - */ - resultRelInfo = mtstate->resultRelInfo; - i = 0; - foreach(l, node->resultRelations) - { - Index resultRelation = lfirst_int(l); - - if (resultRelInfo != mtstate->rootResultRelInfo) - ExecInitResultRelation(estate, resultRelInfo, resultRelation); - - /* Initialize the usesFdwDirectModify flag */ - resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i, - node->fdwDirectModifyPlans); - - /* - * Verify result relation is a valid target for the current operation - */ - CheckValidResultRel(resultRelInfo, operation); - - resultRelInfo++; - i++; - } - - /* - * Now we may initialize the subplan. - */ - outerPlanState(mtstate) = ExecInitNode(subplan, estate, eflags); - - /* - * Do additional per-result-relation initialization. - */ - for (i = 0; i < nrels; i++) - { - resultRelInfo = &mtstate->resultRelInfo[i]; - - /* - * If there are indices on the result relation, open them and save - * descriptors in the result relation info, so that we can add new - * index entries for the tuples we add/update. We need not do this - * for a DELETE, however, since deletion doesn't affect indexes. Also, - * inside an EvalPlanQual operation, the indexes might be open - * already, since we share the resultrel state with the original - * query. - */ - if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex && - operation != CMD_DELETE && - resultRelInfo->ri_IndexRelationDescs == NULL) - ExecOpenIndices(resultRelInfo, - node->onConflictAction != ONCONFLICT_NONE); - - /* - * If this is an UPDATE and a BEFORE UPDATE trigger is present, the - * trigger itself might modify the partition-key values. So arrange - * for tuple routing. - */ - if (resultRelInfo->ri_TrigDesc && - resultRelInfo->ri_TrigDesc->trig_update_before_row && - operation == CMD_UPDATE) - update_tuple_routing_needed = true; - - /* Also let FDWs init themselves for foreign-table result rels */ - if (!resultRelInfo->ri_usesFdwDirectModify && - resultRelInfo->ri_FdwRoutine != NULL && - resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL) - { - List *fdw_private = (List *) list_nth(node->fdwPrivLists, i); - - resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate, - resultRelInfo, - fdw_private, - i, - eflags); - } - - /* - * If needed, initialize a map to convert tuples in the child format - * to the format of the table mentioned in the query (root relation). - * It's needed for update tuple routing, because the routing starts - * from the root relation. It's also needed for capturing transition - * tuples, because the transition tuple store can only store tuples in - * the root table format. - * - * For INSERT, the map is only initialized for a given partition when - * the partition itself is first initialized by ExecFindPartition(). - */ - if (update_tuple_routing_needed || - (mtstate->mt_transition_capture && - mtstate->operation != CMD_INSERT)) - resultRelInfo->ri_ChildToRootMap = - convert_tuples_by_name(RelationGetDescr(resultRelInfo->ri_RelationDesc), - RelationGetDescr(mtstate->rootResultRelInfo->ri_RelationDesc)); - } - /* Get the root target relation */ rel = mtstate->rootResultRelInfo->ri_RelationDesc; /* - * If it's not a partitioned table after all, UPDATE tuple routing should - * not be attempted. - */ - if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) - update_tuple_routing_needed = false; - - /* - * Build state for tuple routing if it's an INSERT or if it's an UPDATE of - * partition key. + * Build state for tuple routing if it's an INSERT. An UPDATE might need + * it too, but it's initialized only when it actually ends up moving + * tuples between partitions; see ExecCrossPartitionUpdate(). */ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && - (operation == CMD_INSERT || update_tuple_routing_needed)) + operation == CMD_INSERT) mtstate->mt_partition_tuple_routing = - ExecSetupPartitionTupleRouting(estate, mtstate, rel); - - /* - * For update row movement we'll need a dedicated slot to store the tuples - * that have been converted from partition format to the root table - * format. - */ - if (update_tuple_routing_needed) - mtstate->mt_root_tuple_slot = table_slot_create(rel, NULL); - - /* - * Initialize any WITH CHECK OPTION constraints if needed. - */ - resultRelInfo = mtstate->resultRelInfo; - foreach(l, node->withCheckOptionLists) - { - List *wcoList = (List *) lfirst(l); - List *wcoExprs = NIL; - ListCell *ll; - - foreach(ll, wcoList) - { - WithCheckOption *wco = (WithCheckOption *) lfirst(ll); - ExprState *wcoExpr = ExecInitQual((List *) wco->qual, - &mtstate->ps); - - wcoExprs = lappend(wcoExprs, wcoExpr); - } - - resultRelInfo->ri_WithCheckOptions = wcoList; - resultRelInfo->ri_WithCheckOptionExprs = wcoExprs; - resultRelInfo++; - } - - /* - * Initialize RETURNING projections if needed. - */ - if (node->returningLists) - { - TupleTableSlot *slot; - ExprContext *econtext; - - /* - * 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); - - /* Set up a slot for the output of the RETURNING projection(s) */ - ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual); - slot = mtstate->ps.ps_ResultTupleSlot; - - /* Need an econtext too */ - if (mtstate->ps.ps_ExprContext == NULL) - ExecAssignExprContext(estate, &mtstate->ps); - econtext = mtstate->ps.ps_ExprContext; - - /* - * Build a projection for each result rel. - */ - resultRelInfo = mtstate->resultRelInfo; - foreach(l, node->returningLists) - { - List *rlist = (List *) lfirst(l); - - resultRelInfo->ri_returningList = rlist; - resultRelInfo->ri_projectReturning = - ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps, - resultRelInfo->ri_RelationDesc->rd_att); - resultRelInfo++; - } - } - else - { - /* - * We still must construct a dummy result tuple type, because InitPlan - * expects one (maybe should change that?). - */ - 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; - if (node->onConflictAction != ONCONFLICT_NONE) - resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes; - - /* - * If needed, Initialize target list, projection and qual for ON CONFLICT - * DO UPDATE. - */ - if (node->onConflictAction == ONCONFLICT_UPDATE) - { - ExprContext *econtext; - TupleDesc relationDesc; - TupleDesc tupDesc; - - /* insert may only have one relation, inheritance is not expanded */ - Assert(nrels == 1); - - /* already exists if created by RETURNING processing above */ - if (mtstate->ps.ps_ExprContext == NULL) - ExecAssignExprContext(estate, &mtstate->ps); - - econtext = mtstate->ps.ps_ExprContext; - relationDesc = resultRelInfo->ri_RelationDesc->rd_att; - - /* create state for DO UPDATE SET operation */ - resultRelInfo->ri_onConflict = makeNode(OnConflictSetState); - - /* initialize slot for the existing tuple */ - resultRelInfo->ri_onConflict->oc_Existing = - table_slot_create(resultRelInfo->ri_RelationDesc, - &mtstate->ps.state->es_tupleTable); - - /* - * Create the tuple slot for the UPDATE SET projection. We want a slot - * of the table's type here, because the slot will be used to insert - * into the table, and for RETURNING processing - which may access - * system attributes. - */ - tupDesc = ExecTypeFromTL((List *) node->onConflictSet); - resultRelInfo->ri_onConflict->oc_ProjSlot = - ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc, - table_slot_callbacks(resultRelInfo->ri_RelationDesc)); - - /* build UPDATE SET projection state */ - resultRelInfo->ri_onConflict->oc_ProjInfo = - ExecBuildProjectionInfo(node->onConflictSet, econtext, - resultRelInfo->ri_onConflict->oc_ProjSlot, - &mtstate->ps, - relationDesc); - - /* initialize state to evaluate the WHERE clause, if any */ - if (node->onConflictWhere) - { - ExprState *qualexpr; - - qualexpr = ExecInitQual((List *) node->onConflictWhere, - &mtstate->ps); - resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr; - } - } + ExecSetupPartitionTupleRouting(estate, rel); /* * If we have any secondary relations in an UPDATE or DELETE, they need to @@ -2827,151 +2969,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) EvalPlanQualSetPlan(&mtstate->mt_epqstate, subplan, arowmarks); - /* - * Initialize projection(s) to create tuples suitable for result rel(s). - * INSERT queries may need a projection to filter out junk attrs in the - * tlist. UPDATE always needs a projection, because (1) there's always - * some junk attrs, and (2) we may need to merge values of not-updated - * columns from the old tuple into the final tuple. In UPDATE, the tuple - * arriving from the subplan contains only new values for the changed - * columns, plus row identity info in the junk attrs. - * - * If there are multiple result relations, each one needs its own - * projection. Note multiple rels are only possible for UPDATE/DELETE, so - * we can't be fooled by some needing a projection and some not. - * - * This section of code is also a convenient place to verify that the - * output of an INSERT or UPDATE matches the target table(s). - */ - for (i = 0; i < nrels; i++) - { - resultRelInfo = &mtstate->resultRelInfo[i]; - - /* - * Prepare to generate tuples suitable for the target relation. - */ - if (operation == CMD_INSERT) - { - List *insertTargetList = NIL; - bool need_projection = false; - - foreach(l, subplan->targetlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(l); - - if (!tle->resjunk) - insertTargetList = lappend(insertTargetList, tle); - else - need_projection = true; - } - - /* - * The junk-free list must produce a tuple suitable for the result - * relation. - */ - ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, - insertTargetList); - - /* We'll need a slot matching the table's format. */ - resultRelInfo->ri_newTupleSlot = - table_slot_create(resultRelInfo->ri_RelationDesc, - &mtstate->ps.state->es_tupleTable); - - /* Build ProjectionInfo if needed (it probably isn't). */ - if (need_projection) - { - TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc); - - /* need an expression context to do the projection */ - if (mtstate->ps.ps_ExprContext == NULL) - ExecAssignExprContext(estate, &mtstate->ps); - - resultRelInfo->ri_projectNew = - ExecBuildProjectionInfo(insertTargetList, - mtstate->ps.ps_ExprContext, - resultRelInfo->ri_newTupleSlot, - &mtstate->ps, - relDesc); - } - } - else if (operation == CMD_UPDATE) - { - List *updateColnos; - TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc); - - updateColnos = (List *) list_nth(node->updateColnosLists, i); - - /* - * For UPDATE, we use the old tuple to fill up missing values in - * the tuple produced by the plan to get the new tuple. We need - * two slots, both matching the table's desired format. - */ - resultRelInfo->ri_oldTupleSlot = - table_slot_create(resultRelInfo->ri_RelationDesc, - &mtstate->ps.state->es_tupleTable); - resultRelInfo->ri_newTupleSlot = - table_slot_create(resultRelInfo->ri_RelationDesc, - &mtstate->ps.state->es_tupleTable); - - /* need an expression context to do the projection */ - if (mtstate->ps.ps_ExprContext == NULL) - ExecAssignExprContext(estate, &mtstate->ps); - - resultRelInfo->ri_projectNew = - ExecBuildUpdateProjection(subplan->targetlist, - updateColnos, - relDesc, - mtstate->ps.ps_ExprContext, - resultRelInfo->ri_newTupleSlot, - &mtstate->ps); - } - - /* - * For UPDATE/DELETE, find the appropriate junk attr now, either a - * 'ctid' or 'wholerow' attribute depending on relkind. For foreign - * tables, the FDW might have created additional junk attr(s), but - * those are no concern of ours. - */ - if (operation == CMD_UPDATE || operation == CMD_DELETE) - { - char relkind; - - relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind; - if (relkind == RELKIND_RELATION || - relkind == RELKIND_MATVIEW || - relkind == RELKIND_PARTITIONED_TABLE) - { - resultRelInfo->ri_RowIdAttNo = - ExecFindJunkAttributeInTlist(subplan->targetlist, "ctid"); - if (!AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo)) - elog(ERROR, "could not find junk ctid column"); - } - else if (relkind == RELKIND_FOREIGN_TABLE) - { - /* - * When there is a row-level trigger, there should be a - * wholerow attribute. We also require it to be present in - * UPDATE, so we can get the values of unchanged columns. - */ - resultRelInfo->ri_RowIdAttNo = - ExecFindJunkAttributeInTlist(subplan->targetlist, - "wholerow"); - if (mtstate->operation == CMD_UPDATE && - !AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo)) - elog(ERROR, "could not find junk wholerow column"); - } - else - { - /* Other valid target relkinds must provide wholerow */ - resultRelInfo->ri_RowIdAttNo = - ExecFindJunkAttributeInTlist(subplan->targetlist, - "wholerow"); - if (!AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo)) - elog(ERROR, "could not find junk wholerow column"); - } - } - } - /* * If this is an inherited update/delete, there will be a junk attribute * named "tableoid" present in the subplan's targetlist. It will be used @@ -2984,6 +2981,20 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) mtstate->mt_lastResultOid = InvalidOid; /* force lookup at first tuple */ mtstate->mt_lastResultIndex = 0; /* must be zero if no such attr */ + /* + * While we normally don't allocate ResultRelInfos for a child relation + * until ExecModifyTable() gets a tuple to update/delete from the subplan + * that belongs to that relation, that strategy is futile for child + * relations that are foreign tables whose update/delete have been pushed + * to the remote side. The ForeignScan node which performs a given + * "direct" update/delete operation expects that the ResultRelInfo for the + * foreign table has been initialized and placed in + * estate->es_result_relations[], so we allocate them here. + */ + i = -1; + while((i = bms_next_member(node->fdwDirectModifyPlans, i)) >= 0) + (void) ExecGetResultRelation(mtstate, i); + /* * If there are a lot of result relations, use a hash table to speed the * lookups. If there are not a lot, a simple linear search is faster. @@ -3008,49 +3019,33 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) hash_create("ModifyTable target hash", nrels, &hash_ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); - for (i = 0; i < nrels; i++) + i = 0; + foreach(l, node->resultRelations) { - Oid hashkey; + Index resultRelation = lfirst_int(l); + Oid hashkey = exec_rt_fetch(resultRelation, estate)->relid; MTTargetRelLookup *mtlookup; bool found; - resultRelInfo = &mtstate->resultRelInfo[i]; - hashkey = RelationGetRelid(resultRelInfo->ri_RelationDesc); mtlookup = (MTTargetRelLookup *) hash_search(mtstate->mt_resultOidHash, &hashkey, HASH_ENTER, &found); Assert(!found); - mtlookup->relationIndex = i; + mtlookup->relationIndex = i++; } + mtstate->mt_resultOidArray = NULL; } else - mtstate->mt_resultOidHash = NULL; - - /* - * Determine if the FDW supports batch insert and determine the batch - * size (a FDW may support batching, but it may be disabled for the - * server/table). - * - * We only do this for INSERT, so that for UPDATE/DELETE the batch - * size remains set to 0. - */ - if (operation == CMD_INSERT) { - resultRelInfo = mtstate->resultRelInfo; - for (i = 0; i < nrels; i++) + mtstate->mt_resultOidHash = NULL; + mtstate->mt_resultOidArray = (Oid *) palloc(nrels * sizeof(Oid)); + i = 0; + foreach(l, node->resultRelations) { - if (!resultRelInfo->ri_usesFdwDirectModify && - resultRelInfo->ri_FdwRoutine != NULL && - resultRelInfo->ri_FdwRoutine->GetForeignModifyBatchSize && - resultRelInfo->ri_FdwRoutine->ExecForeignBatchInsert) - resultRelInfo->ri_BatchSize = - resultRelInfo->ri_FdwRoutine->GetForeignModifyBatchSize(resultRelInfo); - else - resultRelInfo->ri_BatchSize = 1; + Index resultRelation = lfirst_int(l); - Assert(resultRelInfo->ri_BatchSize >= 1); - - resultRelInfo++; + mtstate->mt_resultOidArray[i++] = + exec_rt_fetch(resultRelation, estate)->relid; } } @@ -3081,22 +3076,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) void ExecEndModifyTable(ModifyTableState *node) { - int i; - - /* - * Allow any FDWs to shut down - */ - for (i = 0; i < node->mt_nrels; i++) - { - ResultRelInfo *resultRelInfo = node->resultRelInfo + i; - - if (!resultRelInfo->ri_usesFdwDirectModify && - resultRelInfo->ri_FdwRoutine != NULL && - resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL) - resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state, - resultRelInfo); - } - /* * Close all the partitioned tables, leaf partitions, and their indices * and release the slot used for tuple routing, if set. @@ -3140,3 +3119,62 @@ ExecReScanModifyTable(ModifyTableState *node) */ elog(ERROR, "ExecReScanModifyTable is not implemented"); } + +/* + * ExecLookupResultRelByOid + * If the table with given OID is among the result relations to be + * updated by the given ModifyTable node, return its ResultRelInfo, NULL + * otherwise. + */ +ResultRelInfo * +ExecLookupResultRelByOid(ModifyTableState *node, Oid resultoid, + bool missing_ok) +{ + bool found; + + if (node->mt_resultOidHash) + { + /* Use the pre-built hash table to locate the rel */ + MTTargetRelLookup *mtlookup; + + mtlookup = (MTTargetRelLookup *) + hash_search(node->mt_resultOidHash, &resultoid, HASH_FIND, + &found); + if (found) + { + Assert(mtlookup != NULL); + node->mt_lastResultOid = resultoid; + node->mt_lastResultIndex = mtlookup->relationIndex; + } + else if (!missing_ok) + elog(ERROR, "incorrect result rel OID %u", resultoid); + } + else + { + /* With few target rels, search in the pre-built OID array */ + int ndx; + + found = false; + for (ndx = 0; ndx < node->mt_nrels; ndx++) + { + if (node->mt_resultOidArray[ndx] == resultoid) + { + found = true; + break; + } + } + if (found) + { + Assert(ndx < node->mt_nrels); + node->mt_lastResultOid = resultoid; + node->mt_lastResultIndex = ndx; + } + else if (!missing_ok) + elog(ERROR, "incorrect result rel OID %u", resultoid); + } + + if (!found) + return NULL; + + return ExecGetResultRelation(node, node->mt_lastResultIndex); +} diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c index 354fbe4b4b..e901823b63 100644 --- a/src/backend/replication/logical/worker.c +++ b/src/backend/replication/logical/worker.c @@ -1583,7 +1583,7 @@ apply_handle_tuple_routing(ResultRelInfo *relinfo, mtstate->ps.state = estate; mtstate->operation = operation; mtstate->resultRelInfo = relinfo; - proute = ExecSetupPartitionTupleRouting(estate, mtstate, parentrel); + proute = ExecSetupPartitionTupleRouting(estate, parentrel); /* * Find the partition to which the "search tuple" belongs. diff --git a/src/include/executor/execPartition.h b/src/include/executor/execPartition.h index d30ffde7d9..694e38b7dd 100644 --- a/src/include/executor/execPartition.h +++ b/src/include/executor/execPartition.h @@ -111,7 +111,6 @@ typedef struct PartitionPruneState } PartitionPruneState; extern PartitionTupleRouting *ExecSetupPartitionTupleRouting(EState *estate, - ModifyTableState *mtstate, Relation rel); extern ResultRelInfo *ExecFindPartition(ModifyTableState *mtstate, ResultRelInfo *rootResultRelInfo, diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 34dd861eff..7516aeffe6 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -589,6 +589,7 @@ extern int ExecCleanTargetListLength(List *targetlist); extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relInfo); extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo); extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo); +extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo); extern Bitmapset *ExecGetInsertedCols(ResultRelInfo *relinfo, EState *estate); extern Bitmapset *ExecGetUpdatedCols(ResultRelInfo *relinfo, EState *estate); @@ -638,9 +639,12 @@ extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd); extern void CheckSubscriptionRelkind(char relkind, const char *nspname, const char *relname); -/* needed by trigger.c */ +/* prototypes from nodeModifyTable.c */ extern TupleTableSlot *ExecGetUpdateNewTuple(ResultRelInfo *relinfo, TupleTableSlot *planSlot, TupleTableSlot *oldSlot); +extern ResultRelInfo *ExecGetResultRelation(ModifyTableState *mtstate, int whichrel); +extern ResultRelInfo *ExecLookupResultRelByOid(ModifyTableState *node, Oid resultoid, + bool missing_ok); #endif /* EXECUTOR_H */ diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 3b39369a49..2bfe1d75c4 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -515,6 +515,7 @@ typedef struct ResultRelInfo * transition tuple capture or update partition row movement is active. */ TupleConversionMap *ri_ChildToRootMap; + bool ri_ChildToRootMapValid; /* has the map been initialized? */ /* for use by copyfrom.c when performing multi-inserts */ struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer; @@ -1179,7 +1180,8 @@ typedef struct ModifyTableState CmdType operation; /* INSERT, UPDATE, or DELETE */ bool canSetTag; /* do we set the command tag/es_processed? */ bool mt_done; /* are we done? */ - int mt_nrels; /* number of entries in resultRelInfo[] */ + int mt_nrels; /* number of entries in resultRelInfo[] and + * resultOidArray[] */ ResultRelInfo *resultRelInfo; /* info about target relation(s) */ /* @@ -1197,12 +1199,13 @@ typedef struct ModifyTableState * These fields are used for inherited UPDATE and DELETE, to track which * target relation a given tuple is from. If there are a lot of target * relations, we use a hash table to translate table OIDs to - * resultRelInfo[] indexes; otherwise mt_resultOidHash is NULL. + * resultRelInfo[] indexes; otherwise an array. */ int mt_resultOidAttno; /* resno of "tableoid" junk attr */ Oid mt_lastResultOid; /* last-seen value of tableoid */ int mt_lastResultIndex; /* corresponding index in resultRelInfo[] */ HTAB *mt_resultOidHash; /* optional hash table to speed lookups */ + Oid *mt_resultOidArray; /* array when hash table is not used */ /* * Slot for storing tuples in the root partitioned table's rowtype during diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out index 1c703c351f..06f44287bc 100644 --- a/src/test/regress/expected/inherit.out +++ b/src/test/regress/expected/inherit.out @@ -2492,7 +2492,7 @@ ERROR: new row for relation "errtst_child_plaindef" violates check constraint " DETAIL: Failing row contains (10, 1, 15). UPDATE errtst_parent SET data = data + 10 WHERE partid = 20; ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check" -DETAIL: Failing row contains (15, 1, 20). +DETAIL: Failing row contains (20, 1, 15). -- direct leaf partition update, without partition id violation BEGIN; UPDATE errtst_child_fastdef SET partid = 1 WHERE partid = 0; diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out index 89f3d5da46..6372e0ed6a 100644 --- a/src/test/regress/expected/privileges.out +++ b/src/test/regress/expected/privileges.out @@ -685,7 +685,7 @@ DETAIL: Failing row contains (a, b, c) = (aaa, null, null). -- simple update. UPDATE errtst SET b = NULL; ERROR: null value in column "b" of relation "errtst_part_1" violates not-null constraint -DETAIL: Failing row contains (b) = (null). +DETAIL: Failing row contains (a, b, c) = (aaa, null, ccc). -- partitioning key is updated, doesn't move the row. UPDATE errtst SET a = 'aaa', b = NULL; ERROR: null value in column "b" of relation "errtst_part_1" violates not-null constraint diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out index dc34ac67b3..ad91e5aedb 100644 --- a/src/test/regress/expected/update.out +++ b/src/test/regress/expected/update.out @@ -342,8 +342,8 @@ DETAIL: Failing row contains (105, 85, null, b, 15). -- fail, no partition key update, so no attempt to move tuple, -- but "a = 'a'" violates partition constraint enforced by root partition) UPDATE part_b_10_b_20 set a = 'a'; -ERROR: new row for relation "part_c_1_100" violates partition constraint -DETAIL: Failing row contains (null, 1, 96, 12, a). +ERROR: new row for relation "part_b_10_b_20" violates partition constraint +DETAIL: Failing row contains (null, 96, a, 12, 1). -- ok, partition key update, no constraint violation UPDATE range_parted set d = d - 10 WHERE d > 10; -- ok, no partition key update, no constraint violation @@ -373,8 +373,8 @@ UPDATE part_b_10_b_20 set c = c + 20 returning c, b, a; -- fail, row movement happens only within the partition subtree. UPDATE part_b_10_b_20 set b = b - 6 WHERE c > 116 returning *; -ERROR: new row for relation "part_d_1_15" violates partition constraint -DETAIL: Failing row contains (2, 117, 2, b, 7). +ERROR: new row for relation "part_b_10_b_20" violates partition constraint +DETAIL: Failing row contains (2, 117, b, 7, 2). -- ok, row movement, with subset of rows moved into different partition. UPDATE range_parted set b = b - 6 WHERE c > 116 returning a, b + c; a | ?column? @@ -815,8 +815,8 @@ INSERT into sub_parted VALUES (1,2,10); -- Test partition constraint violation when intermediate ancestor is used and -- constraint is inherited from upper root. UPDATE sub_parted set a = 2 WHERE c = 10; -ERROR: new row for relation "sub_part2" violates partition constraint -DETAIL: Failing row contains (2, 10, 2). +ERROR: new row for relation "sub_parted" violates partition constraint +DETAIL: Failing row contains (2, 2, 10). -- Test update-partition-key, where the unpruned partitions do not have their -- partition keys updated. SELECT tableoid::regclass::text, * FROM list_parted WHERE a = 2 ORDER BY 1; -- 2.24.1