From dc54b6af2efd2b4a30a046ba3dac4a802c83bd8e Mon Sep 17 00:00:00 2001 From: amitlan Date: Thu, 2 Jul 2020 10:51:45 +0900 Subject: [PATCH v3 4/4] Initialize result relation information lazily Currently, ResultRelInfo of all result relation appearing in PlannedStmt.resultRelations, PlannedStmt.rootResultRelations are initialized before execution begins. That can be wasteful as only one or a handful of potentially many result relations appearing in those lists (especially resultRelations) may actually have any rows to update or delete. This refactors ModifyTable code so as to delay creating ResultRelInfo of a given result relation to when its subplan produces a tuple for the first time, which in turn means that we don't make one for result relations for which no tuples are found to be processed. This can save some amount of work in the cases with many unpruned partitions in the plan of which only one or handful need to be processed, such as with generic plans. (Now, without support for runtime pruning of UPDATE and DELETE subplans, initializing ResultRelInfos may not be too much work when compared to initializing the subplans themselves, but we may have runtime pruning in the future when we will not have to worry about the overhead of initializing result rels.) As part of this, any place that assumes that a given result relation can be accessed by its index in ModifyTableState.resultRelInfo is updated to instead get it using the new function ExecGetResultRelInfo which checks if one exists and create one at the appropriate index if not. ModifyTableState gets a new field mt_done_rels that is a bitmapset of RT indexes of result relations that have been fully processed due to their subplans having exhausted tuples to process. --- contrib/postgres_fdw/postgres_fdw.c | 6 +- src/backend/commands/copy.c | 4 +- src/backend/commands/explain.c | 40 +- src/backend/commands/tablecmds.c | 2 +- src/backend/executor/execMain.c | 123 ++--- src/backend/executor/execPartition.c | 111 ++-- src/backend/executor/execUtils.c | 4 +- src/backend/executor/nodeModifyTable.c | 850 ++++++++++++++++++------------- src/backend/replication/logical/worker.c | 2 +- src/include/executor/executor.h | 4 + src/include/nodes/execnodes.h | 7 +- 11 files changed, 652 insertions(+), 501 deletions(-) diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index fc53c23..abe55b7 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -1943,7 +1943,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate, if (plan && plan->operation == CMD_UPDATE && (resultRelInfo->ri_usesFdwDirectModify || resultRelInfo->ri_FdwState) && - resultRelInfo > mtstate->resultRelInfo + mtstate->mt_whichplan) + !list_member_int(mtstate->mt_done_rels, resultRelation)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot route tuples into foreign table to be updated \"%s\"", @@ -1997,7 +1997,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate, */ if (plan && plan->operation == CMD_UPDATE && resultRelation == plan->rootRelation) - resultRelation = mtstate->resultRelInfo[0].ri_RangeTableIndex; + resultRelation = linitial_int(plan->resultRelations); } /* Construct the SQL command string. */ @@ -2458,7 +2458,7 @@ postgresIterateDirectModify(ForeignScanState *node) { PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state; EState *estate = node->ss.ps.state; - ResultRelInfo *resultRelInfo = &estate->es_result_relations[dmstate->resultRelIndex]; + ResultRelInfo *resultRelInfo = estate->es_result_relations[dmstate->resultRelIndex]; /* The executor must have initialized the ResultRelInfo for us. */ Assert(resultRelInfo != NULL); diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 155ac5b..739e526 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -2838,7 +2838,7 @@ CopyFrom(CopyState cstate) ExecOpenIndices(resultRelInfo, false); - estate->es_result_relations = resultRelInfo; + estate->es_result_relations = &resultRelInfo; estate->es_num_result_relations = 1; estate->es_result_relation_info = resultRelInfo; @@ -2852,7 +2852,7 @@ CopyFrom(CopyState cstate) mtstate->ps.plan = NULL; mtstate->ps.state = estate; mtstate->operation = CMD_INSERT; - mtstate->resultRelInfo = estate->es_result_relations; + mtstate->resultRelInfo = resultRelInfo; 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 1e565fd..e7f3632 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" @@ -795,13 +796,21 @@ ExplainPrintTriggers(ExplainState *es, QueryDesc *queryDesc) show_relname = (numrels > 1 || numrootrels > 0 || routerels != NIL || targrels != NIL); - rInfo = queryDesc->estate->es_result_relations; - for (nr = 0; nr < numrels; rInfo++, nr++) - report_triggers(rInfo, show_relname, es); + for (nr = 0; nr < numrels; nr++) + { + rInfo = queryDesc->estate->es_result_relations[nr]; - rInfo = queryDesc->estate->es_root_result_relations; - for (nr = 0; nr < numrootrels; rInfo++, nr++) - report_triggers(rInfo, show_relname, es); + if (rInfo) + report_triggers(rInfo, show_relname, es); + } + + for (nr = 0; nr < numrootrels; nr++) + { + rInfo = queryDesc->estate->es_root_result_relations[nr]; + + if (rInfo) + report_triggers(rInfo, show_relname, es); + } foreach(l, routerels) { @@ -3668,15 +3677,28 @@ 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)); + linitial_int(node->resultRelations) != node->nominalRelation)); if (labeltargets) ExplainOpenGroup("Target Tables", "Target Tables", false, es); for (j = 0; j < mtstate->mt_nplans; j++) { - ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j; - FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine; + /* + * Fetch ResultRelInfo to show target relation information. Some or + * all ResultRelInfos may not have been built either because + * ModifyTable is not executed at all (with ANALYZE off), or some were + * not processed during execution (with ANALYZE on). We ask to create + * any missing ResultRelInfos by passing true for 'create_it'. + */ + ResultRelInfo *resultRelInfo = + ExecGetResultRelInfo(mtstate, node->resultRelIndex + j, true); + FdwRoutine *fdwroutine; + + if (resultRelInfo == NULL) + continue; + + fdwroutine = resultRelInfo->ri_FdwRoutine; if (labeltargets) { diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index ac53f79..b5bbf3a 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -1789,7 +1789,7 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged, 0); resultRelInfo++; } - estate->es_result_relations = resultRelInfos; + estate->es_result_relations = &resultRelInfos; estate->es_num_result_relations = list_length(rels); /* diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 4fdffad..6d560c3 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -828,35 +828,17 @@ InitPlan(QueryDesc *queryDesc, int eflags) estate->es_plannedstmt = plannedstmt; /* - * Initialize ResultRelInfo data structures, and open the result rels. + * Allocate space for ResultRelInfo pointers that will be filled later. + * See ExecGetResultRelInfo() and ExecGetRootResultRelInfo(). */ if (plannedstmt->resultRelations) { List *resultRelations = plannedstmt->resultRelations; int numResultRelations = list_length(resultRelations); - ResultRelInfo *resultRelInfos; - ResultRelInfo *resultRelInfo; - resultRelInfos = (ResultRelInfo *) - palloc(numResultRelations * sizeof(ResultRelInfo)); - resultRelInfo = resultRelInfos; - foreach(l, resultRelations) - { - Index resultRelationIndex = lfirst_int(l); - Relation resultRelation; - - resultRelation = ExecGetRangeTableRelation(estate, - resultRelationIndex); - InitResultRelInfo(resultRelInfo, - resultRelation, - resultRelationIndex, - NULL, - estate->es_instrument); - resultRelInfo++; - } - estate->es_result_relations = resultRelInfos; + estate->es_result_relations = + palloc0(numResultRelations * sizeof(ResultRelInfo *)); estate->es_num_result_relations = numResultRelations; - /* es_result_relation_info is NULL except when within ModifyTable */ estate->es_result_relation_info = NULL; @@ -869,25 +851,8 @@ InitPlan(QueryDesc *queryDesc, int eflags) { int num_roots = list_length(plannedstmt->rootResultRelations); - resultRelInfos = (ResultRelInfo *) - palloc(num_roots * sizeof(ResultRelInfo)); - resultRelInfo = resultRelInfos; - foreach(l, plannedstmt->rootResultRelations) - { - Index resultRelIndex = lfirst_int(l); - Relation resultRelDesc; - - resultRelDesc = ExecGetRangeTableRelation(estate, - resultRelIndex); - InitResultRelInfo(resultRelInfo, - resultRelDesc, - resultRelIndex, - NULL, - estate->es_instrument); - resultRelInfo++; - } - - estate->es_root_result_relations = resultRelInfos; + estate->es_root_result_relations = + palloc0(num_roots * sizeof(ResultRelInfo *)); estate->es_num_root_result_relations = num_roots; } else @@ -1377,24 +1342,18 @@ ExecGetTriggerResultRel(EState *estate, Oid relid) MemoryContext oldcontext; /* First, search through the query result relations */ - rInfo = estate->es_result_relations; - nr = estate->es_num_result_relations; - while (nr > 0) + for (nr = 0; nr < estate->es_num_result_relations; nr++) { - if (RelationGetRelid(rInfo->ri_RelationDesc) == relid) + rInfo = estate->es_result_relations[nr]; + if (rInfo && RelationGetRelid(rInfo->ri_RelationDesc) == relid) return rInfo; - rInfo++; - nr--; } /* Second, search through the root result relations, if any */ - rInfo = estate->es_root_result_relations; - nr = estate->es_num_root_result_relations; - while (nr > 0) + for (nr = 0; nr < estate->es_num_root_result_relations; nr++) { - if (RelationGetRelid(rInfo->ri_RelationDesc) == relid) + rInfo = estate->es_root_result_relations[nr]; + if (rInfo && RelationGetRelid(rInfo->ri_RelationDesc) == relid) return rInfo; - rInfo++; - nr--; } /* @@ -1561,13 +1520,20 @@ ExecEndPlan(PlanState *planstate, EState *estate) /* * close indexes of result relation(s) if any. (Rels themselves get - * closed next.) + * closed next.) Also, allow the FDWs to shut down. */ - resultRelInfo = estate->es_result_relations; - for (i = estate->es_num_result_relations; i > 0; i--) + for (i = 0; i < estate->es_num_result_relations; i++) { - ExecCloseIndices(resultRelInfo); - resultRelInfo++; + resultRelInfo = estate->es_result_relations[i]; + if (resultRelInfo) + { + ExecCloseIndices(resultRelInfo); + if (!resultRelInfo->ri_usesFdwDirectModify && + resultRelInfo->ri_FdwRoutine != NULL && + resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL) + resultRelInfo->ri_FdwRoutine->EndForeignModify(estate, + resultRelInfo); + } } /* @@ -2796,23 +2762,42 @@ EvalPlanQualStart(EPQState *epqstate, Plan *planTree) { int numResultRelations = parentestate->es_num_result_relations; int numRootResultRels = parentestate->es_num_root_result_relations; - ResultRelInfo *resultRelInfos; + int i; + ResultRelInfo *resultRelInfo; - resultRelInfos = (ResultRelInfo *) - palloc(numResultRelations * sizeof(ResultRelInfo)); - memcpy(resultRelInfos, parentestate->es_result_relations, - numResultRelations * sizeof(ResultRelInfo)); - rcestate->es_result_relations = resultRelInfos; + rcestate->es_result_relations = + palloc0(numResultRelations * sizeof(ResultRelInfo *)); + for (i = 0; i < numResultRelations; i++) + { + if (parentestate->es_result_relations[i]) + { + resultRelInfo = makeNode(ResultRelInfo); + memcpy(resultRelInfo, parentestate->es_result_relations[i], + sizeof(ResultRelInfo)); + } + else + resultRelInfo = NULL; + rcestate->es_result_relations[i] = resultRelInfo; + } rcestate->es_num_result_relations = numResultRelations; /* Also transfer partitioned root result relations. */ if (numRootResultRels > 0) { - resultRelInfos = (ResultRelInfo *) - palloc(numRootResultRels * sizeof(ResultRelInfo)); - memcpy(resultRelInfos, parentestate->es_root_result_relations, - numRootResultRels * sizeof(ResultRelInfo)); - rcestate->es_root_result_relations = resultRelInfos; + rcestate->es_root_result_relations = + palloc0(numRootResultRels * sizeof(ResultRelInfo *)); + for (i = 0; i < numRootResultRels; i++) + { + if (parentestate->es_root_result_relations[i]) + { + resultRelInfo = makeNode(ResultRelInfo); + memcpy(resultRelInfo, parentestate->es_root_result_relations[i], + sizeof(ResultRelInfo)); + } + else + resultRelInfo = NULL; + rcestate->es_root_result_relations[i] = resultRelInfo; + } rcestate->es_num_root_result_relations = numRootResultRels; } } diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c index 7ee2dd9..cbc5e0e 100644 --- a/src/backend/executor/execPartition.c +++ b/src/backend/executor/execPartition.c @@ -151,7 +151,7 @@ typedef struct PartitionDispatchData typedef struct SubplanResultRelHashElem { Oid relid; /* hash key -- must be first */ - ResultRelInfo *rri; + int index; } SubplanResultRelHashElem; @@ -212,7 +212,6 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate, 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 @@ -234,18 +233,38 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate, ExecInitPartitionDispatchInfo(estate, proute, RelationGetRelid(rel), NULL, 0); - /* - * 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) + return proute; +} + +/* + * ExecLookupUpdateResultRelByOid + * If the table with given OID appears in the list of result relations + * to be updated by the given ModifyTable node, return its + * ResultRelInfo, NULL otherwise. + */ +static ResultRelInfo * +ExecLookupUpdateResultRelByOid(ModifyTableState *mtstate, Oid reloid) +{ + ModifyTable *node = (ModifyTable *) mtstate->ps.plan; + PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing; + SubplanResultRelHashElem *elem; + + Assert(proute != NULL); + if (proute->subplan_resultrel_htab == NULL) ExecHashSubPlanResultRelsByOid(mtstate, proute); - return proute; + elem = hash_search(proute->subplan_resultrel_htab, &reloid, + HASH_FIND, NULL); + + /* + * The UPDATE result relation may not have been processed and hence its + * ResultRelInfo not created yet, so pass true for 'create_it'. + */ + if (elem) + return ExecGetResultRelInfo(mtstate, + node->resultRelIndex + elem->index, + true); + return NULL; } /* @@ -352,7 +371,7 @@ ExecFindPartition(ModifyTableState *mtstate, if (partdesc->is_leaf[partidx]) { - ResultRelInfo *rri; + ResultRelInfo *rri = NULL; /* * Look to see if we've already got a ResultRelInfo for this @@ -366,36 +385,31 @@ ExecFindPartition(ModifyTableState *mtstate, } else { - bool found = false; - /* * 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 it's also an UPDATE result relation, we might as + * well use the one UPDATE might use. */ - 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) - { - found = true; - rri = elem->rri; + rri = ExecLookupUpdateResultRelByOid(mtstate, partoid); - /* Verify this ResultRelInfo allows INSERTs */ + /* Verify this ResultRelInfo allows INSERTs */ + if (rri) + { CheckValidResultRel(rri, CMD_INSERT); /* Set up the PartitionRoutingInfo for it */ + rri->ri_PartitionRoot = proute->partition_root; ExecInitRoutingInfo(mtstate, estate, proute, dispatch, rri, partidx); } } - /* We need to create a new one. */ - if (!found) + /* Nope, We need to create a new one. */ + if (rri == NULL) rri = ExecInitPartitionInfo(mtstate, estate, proute, dispatch, rootResultRelInfo, partidx); @@ -456,9 +470,13 @@ static void ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate, PartitionTupleRouting *proute) { + EState *estate = mtstate->ps.state; + ModifyTable *plan = (ModifyTable *) mtstate->ps.plan; + ListCell *l; HASHCTL ctl; HTAB *htab; int i; + MemoryContext oldcxt = MemoryContextSwitchTo(estate->es_query_cxt); memset(&ctl, 0, sizeof(ctl)); ctl.keysize = sizeof(Oid); @@ -469,26 +487,26 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate, &ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); proute->subplan_resultrel_htab = htab; - /* Hash all subplans by their Oid */ - for (i = 0; i < mtstate->mt_nplans; i++) + /* + * Map each result relation's OID to its ordinal position in + * plan->resultRelations. + */ + i = 0; + foreach(l, plan->resultRelations) { - ResultRelInfo *rri = &mtstate->resultRelInfo[i]; + Index rti = lfirst_int(l); + RangeTblEntry *rte = exec_rt_fetch(rti, estate); + Oid partoid = rte->relid; 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_PartitionRoot = proute->partition_root; + elem->index = i++; } + + MemoryContextSwitchTo(oldcxt); } /* @@ -509,7 +527,9 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, ModifyTable *node = (ModifyTable *) mtstate->ps.plan; Relation rootrel = rootResultRelInfo->ri_RelationDesc, partrel; - Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc; + Index firstVarno = node ? linitial_int(node->resultRelations) : 0; + Relation firstResultRel = firstVarno > 0 ? + ExecGetRangeTableRelation(estate, firstVarno) : NULL; ResultRelInfo *leaf_part_rri; MemoryContext oldcxt; AttrMap *part_attmap = NULL; @@ -550,14 +570,13 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, * 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) { List *wcoList; List *wcoExprs = NIL; ListCell *ll; - int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex; /* * In the case of INSERT on a partitioned table, there is only one @@ -621,7 +640,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, TupleTableSlot *slot; ExprContext *econtext; List *returningList; - int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex; /* See the comment above for WCO lists. */ Assert((node->operation == CMD_INSERT && @@ -680,7 +698,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, */ if (node && node->onConflictAction != ONCONFLICT_NONE) { - int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex; TupleDesc partrelDesc = RelationGetDescr(partrel); ExprContext *econtext = mtstate->ps.ps_ExprContext; ListCell *lc; @@ -931,8 +948,8 @@ ExecInitRoutingInfo(ModifyTableState *mtstate, /* * If the partition appears to be a reused UPDATE result relation, the * necessary map would already have been set in ri_ChildToRootMap by - * ExecInitModifyTable(), so use that one instead of building one from - * scratch. One can tell if it's actually a reused UPDATE result + * ExecBuildResultRelInfo(), so use that one instead of building one + * from scratch. One can tell if it's actually a reused UPDATE result * relation by looking at its ri_RangeTableIndex which must be * different from the root RT index. */ diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index d0e65b8..a67c023 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -711,13 +711,13 @@ ExecCreateScanSlotFromOuterPlan(EState *estate, bool ExecRelationIsTargetRelation(EState *estate, Index scanrelid) { - ResultRelInfo *resultRelInfos; + ResultRelInfo **resultRelInfos; int i; resultRelInfos = estate->es_result_relations; for (i = 0; i < estate->es_num_result_relations; i++) { - if (resultRelInfos[i].ri_RangeTableIndex == scanrelid) + if (resultRelInfos[i]->ri_RangeTableIndex == scanrelid) return true; } return false; diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index f8f4254..655b3d8 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -72,6 +72,9 @@ static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate, ResultRelInfo *targetRelInfo, TupleTableSlot *slot); static ResultRelInfo *getTargetResultRelInfo(ModifyTableState *node); +static ResultRelInfo *ExecGetRootResultRelInfo(ModifyTableState *mtstate, int rootRelIndex); +static ResultRelInfo *ExecBuildResultRelInfo(ModifyTableState *mtstate, Index rti, + int globalRelIndex); /* * Verify that the tuples to be produced by INSERT or UPDATE match the @@ -359,6 +362,405 @@ ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot, CmdType cmdtype MemoryContextSwitchTo(oldContext); } +/* + * ExecGetResultRelInfo + * Returns the result relation at a given offset in es_result_relations + * + * If not present and 'create_it' is true, it is created and put at the given + * offset for subsequent calls to find. + * + * This allows lazy initialization of ResultRelInfos. That can be helpful in + * the case where there are multiple result relations due to inheritance but + * only one or few actually end up actually having any tuples to process. + * + * Note: only call from the executor proper or anything that possesses a valid + * execution context, that is an EState with a PlannedStmt, because this + * depends on finding a valid PlannedStmt to get result relation RT indexes + * from. + */ +ResultRelInfo * +ExecGetResultRelInfo(ModifyTableState *mtstate, int resultRelIndex, + bool create_it) +{ + EState *estate = mtstate->ps.state; + ResultRelInfo *resultRelInfo = estate->es_result_relations[resultRelIndex]; + + if (resultRelInfo == NULL && create_it) + { + List *resultRelations = estate->es_plannedstmt->resultRelations; + Index rti = list_nth_int(resultRelations, resultRelIndex); + + Assert(mtstate != NULL && mtstate->ps.plan != NULL); + resultRelInfo = ExecBuildResultRelInfo(mtstate, rti, resultRelIndex); + estate->es_result_relations[resultRelIndex] = resultRelInfo; + } + + return resultRelInfo; +} + +/* + * ExecGetRootResultRelInfo + * Like ExecGetResultRelInfo, but for "root" result relations + * corresponding to partitioned tables, which are managed separately from + * leaf result relations + * + * Root ResultRelInfos are never created lazily, although it seems better to + * have the same interface to avoid exposing ExecBuildResultRelInfo(). + */ +static ResultRelInfo * +ExecGetRootResultRelInfo(ModifyTableState *mtstate, int rootRelIndex) +{ + EState *estate = mtstate->ps.state; + ResultRelInfo *rootRelInfo = estate->es_root_result_relations[rootRelIndex]; + + if (rootRelInfo == NULL) + { + List *rootRelations = estate->es_plannedstmt->rootResultRelations; + Index rti = list_nth_int(rootRelations, rootRelIndex); + + Assert(mtstate != NULL && mtstate->ps.plan != NULL); + rootRelInfo = ExecBuildResultRelInfo(mtstate, rti, rootRelIndex); + estate->es_root_result_relations[rootRelIndex] = rootRelInfo; + } + + return rootRelInfo; +} + +/* + * ExecBuildResultRelInfo + * Builds a ResultRelInfo for a result relation with given RT index + * + * Beside creating the ResultRelInfo and setting its various fields based on + * the provided ModifyTable plan, this may also set some fields in the + * ModifyTableState. + */ +static ResultRelInfo * +ExecBuildResultRelInfo(ModifyTableState *mtstate, Index rti, + int globalRelIndex) +{ + EState *estate = mtstate->ps.state; + ModifyTable *node = (ModifyTable *) mtstate->ps.plan; + CmdType operation = node->operation; + int firstRelIndex = node->resultRelIndex; + int thisRelIndex = globalRelIndex - firstRelIndex; + Plan *subplan = mtstate->mt_plans[thisRelIndex]->plan; + Relation relation = ExecGetRangeTableRelation(estate, rti); + ResultRelInfo *resultRelInfo; + bool update_tuple_routing_needed = false; + ListCell *l; + int eflags = estate->es_top_eflags; + bool junk_filter_needed = false; + MemoryContext oldcxt; + + /* Things built here have to last for the query duration. */ + oldcxt = MemoryContextSwitchTo(estate->es_query_cxt); + + resultRelInfo = makeNode(ResultRelInfo); + InitResultRelInfo(resultRelInfo, relation, rti, NULL, + estate->es_instrument); + + /* + * Verify result relation is a valid target for the current operation + */ + CheckValidResultRel(resultRelInfo, operation); + + /* + * If this is the root result relation of an UPDATE/DELETE, it only needs + * to look minimally valid. + */ + if (rti == node->rootRelation) + { + MemoryContextSwitchTo(oldcxt); + return resultRelInfo; + } + + /* + * 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); + + /* Initialize the usesFdwDirectModify flag */ + resultRelInfo->ri_usesFdwDirectModify = bms_is_member(thisRelIndex, + node->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(node->fdwPrivLists, + thisRelIndex); + + resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate, + resultRelInfo, + fdw_private, + thisRelIndex, + eflags); + } + + /* + * Initialize any WITH CHECK OPTION constraints if needed. + */ + if (node->withCheckOptionLists) + { + List *wcoList = (List *) list_nth(node->withCheckOptionLists, + thisRelIndex); + 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; + } + + /* RETURNING list */ + if (node->returningLists) + { + List *rlist = (List *) list_nth(node->returningLists, + thisRelIndex); + TupleTableSlot *slot; + ExprContext *econtext; + + 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); + } + + /* Set the list of arbiter indexes if needed for ON CONFLICT */ + 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(mtstate->mt_nplans == 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; + } + } + + /* + * Initialize the junk filter(s) if needed. INSERT queries need a filter + * if there are any junk attrs in the tlist. UPDATE and DELETE always + * need a filter, since there's always at least one junk attribute present + * --- no need to look first. Typically, this will be a 'ctid' or + * 'wholerow' attribute, but in the case of a foreign data wrapper it + * might be a set of junk attributes sufficient to identify the remote + * row. + * + * If there are multiple result relations, each one needs its own junk + * filter. Note multiple rels are only possible for UPDATE/DELETE, so we + * can't be fooled by some needing a filter 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). + */ + switch (operation) + { + case CMD_INSERT: + foreach(l, subplan->targetlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(l); + + if (tle->resjunk) + { + junk_filter_needed = true; + break; + } + } + break; + case CMD_UPDATE: + case CMD_DELETE: + junk_filter_needed = true; + break; + default: + elog(ERROR, "unknown operation"); + break; + } + + if (junk_filter_needed) + { + JunkFilter *j; + TupleTableSlot *junkresslot; + + if (operation == CMD_INSERT || operation == CMD_UPDATE) + ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, + subplan->targetlist); + + junkresslot = + ExecInitExtraTupleSlot(estate, NULL, + table_slot_callbacks(resultRelInfo->ri_RelationDesc)); + j = ExecInitJunkFilter(subplan->targetlist, junkresslot); + + if (operation == CMD_UPDATE || operation == CMD_DELETE) + { + /* For UPDATE/DELETE, find the appropriate junk attr now */ + char relkind; + + relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind; + if (relkind == RELKIND_RELATION || + relkind == RELKIND_MATVIEW || + relkind == RELKIND_PARTITIONED_TABLE) + { + j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid"); + if (!AttributeNumberIsValid(j->jf_junkAttNo)) + 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. + */ + j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow"); + } + else + { + j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow"); + if (!AttributeNumberIsValid(j->jf_junkAttNo)) + elog(ERROR, "could not find junk wholerow column"); + } + } + + resultRelInfo->ri_junkFilter = j; + } + else if (operation == CMD_INSERT) + ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, + subplan->targetlist); + + /* + * For UPDATE on a partitioned tables, tuple routing might be needed if + * if the plan says so or a BEFORE UPDATE trigger is present on the + * partition which might modify the partition-key values. + */ + if (mtstate->rootResultRelInfo && operation == CMD_UPDATE && + (node->partColsUpdated || + (resultRelInfo->ri_TrigDesc && + resultRelInfo->ri_TrigDesc->trig_update_before_row))) + update_tuple_routing_needed = true; + + /* + * 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. During INSERT, partition tuples to + * store into the transition tuple store are converted using + * PartitionToRoot map in the partition's PartitionRoutingInfo. + */ + if (update_tuple_routing_needed || + (mtstate->mt_transition_capture && operation != CMD_INSERT)) + { + Relation targetRel = getTargetResultRelInfo(mtstate)->ri_RelationDesc; + + resultRelInfo->ri_ChildToRootMap = + convert_tuples_by_name(RelationGetDescr(relation), + RelationGetDescr(targetRel)); + } + + /* ModifyTableState changes follow.*/ + + /* Result relation specific slot to store the plan's output tuple. */ + mtstate->mt_scans[thisRelIndex] = + ExecInitExtraTupleSlot(mtstate->ps.state, + ExecGetResultType(mtstate->mt_plans[thisRelIndex]), + table_slot_callbacks(resultRelInfo->ri_RelationDesc)); + + /* Tuple routing state may already have been initialized. */ + if (update_tuple_routing_needed && + mtstate->mt_partition_tuple_routing == NULL) + { + Relation rootRel = mtstate->rootResultRelInfo->ri_RelationDesc; + + mtstate->mt_partition_tuple_routing = + ExecSetupPartitionTupleRouting(estate, mtstate, rootRel); + + /* + * 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. + */ + mtstate->mt_root_tuple_slot = table_slot_create(rootRel, NULL); + } + + MemoryContextSwitchTo(oldcxt); + + return resultRelInfo; +} + /* ---------------------------------------------------------------- * ExecInsert * @@ -1896,11 +2298,13 @@ static TupleTableSlot * ExecModifyTable(PlanState *pstate) { ModifyTableState *node = castNode(ModifyTableState, pstate); + ModifyTable *plan = (ModifyTable *) node->ps.plan; + int firstRelIndex = plan->resultRelIndex; PartitionTupleRouting *proute = node->mt_partition_tuple_routing; EState *estate = node->ps.state; CmdType operation = node->operation; ResultRelInfo *saved_resultRelInfo; - ResultRelInfo *resultRelInfo; + ResultRelInfo *resultRelInfo = NULL; PlanState *subplanstate; JunkFilter *junkfilter; TupleTableSlot *slot; @@ -1943,9 +2347,7 @@ ExecModifyTable(PlanState *pstate) } /* Preload local variables */ - resultRelInfo = node->resultRelInfo + node->mt_whichplan; subplanstate = node->mt_plans[node->mt_whichplan]; - junkfilter = resultRelInfo->ri_junkFilter; /* * es_result_relation_info must point to the currently active result @@ -1956,8 +2358,6 @@ ExecModifyTable(PlanState *pstate) */ saved_resultRelInfo = estate->es_result_relation_info; - estate->es_result_relation_info = resultRelInfo; - /* * Fetch rows from subplan(s), and execute the required table modification * for each row. @@ -1980,18 +2380,50 @@ ExecModifyTable(PlanState *pstate) if (pstate->ps_ExprContext) ResetExprContext(pstate->ps_ExprContext); + /* + * FDWs that can push down a modify operation would need to see the + * ResultRelInfo, so fetch one if not already done before executing + * the subplan, potentially creating it for the first time. + */ + if (bms_is_member(node->mt_whichplan, plan->fdwDirectModifyPlans) && + resultRelInfo == NULL) + { + resultRelInfo = ExecGetResultRelInfo(node, + firstRelIndex + node->mt_whichplan, + true); + junkfilter = resultRelInfo->ri_junkFilter; + estate->es_result_relation_info = resultRelInfo; + } + planSlot = ExecProcNode(subplanstate); - if (TupIsNull(planSlot)) + /* + * If we got a tuple to process and haven't initialized the + * ResultRelInfo to use for the subplan's target relation, fetch one + * if not not already done, potentially creating it for the first + * time. + */ + if (!TupIsNull(planSlot) && resultRelInfo == NULL) + { + resultRelInfo = ExecGetResultRelInfo(node, + firstRelIndex + node->mt_whichplan, + true); + junkfilter = resultRelInfo->ri_junkFilter; + estate->es_result_relation_info = resultRelInfo; + } + else if (TupIsNull(planSlot)) { - /* advance to next subplan if any */ + /* Mark the current result rel as having been fully processed. */ + node->mt_done_rels = lappend_int(node->mt_done_rels, + list_nth_int(plan->resultRelations, + node->mt_whichplan)); + resultRelInfo = NULL; + junkfilter = NULL; + node->mt_whichplan++; if (node->mt_whichplan < node->mt_nplans) { - resultRelInfo++; subplanstate = node->mt_plans[node->mt_whichplan]; - junkfilter = resultRelInfo->ri_junkFilter; - estate->es_result_relation_info = resultRelInfo; EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan, node->mt_arowmarks[node->mt_whichplan]); continue; @@ -2167,14 +2599,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) ModifyTableState *mtstate; CmdType operation = node->operation; int nplans = list_length(node->plans); - ResultRelInfo *saved_resultRelInfo; - ResultRelInfo *resultRelInfo; Plan *subplan; ListCell *l; int i; Relation rel; - bool update_tuple_routing_needed = node->partColsUpdated; - ResultRelInfo *rootResultRel; /* check for unsupported flags */ Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK))); @@ -2192,19 +2620,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) mtstate->mt_done = false; mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans); - mtstate->resultRelInfo = estate->es_result_relations + node->resultRelIndex; mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans); - /* If modifying a partitioned table, initialize the root table info */ - if (node->rootResultRelIndex >= 0) - { - mtstate->rootResultRelInfo = estate->es_root_result_relations + - node->rootResultRelIndex; - rootResultRel = mtstate->rootResultRelInfo; - } - else - rootResultRel = mtstate->resultRelInfo; - mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans); mtstate->mt_nplans = nplans; @@ -2213,197 +2630,33 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) mtstate->fireBSTriggers = true; /* - * Build state for collecting transition tuples. This requires having a - * valid trigger query context, so skip it in explain-only mode. - */ - if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY)) - ExecSetupTransitionCaptureState(mtstate, estate); - - /* * call ExecInitNode on each of the plans to be executed and save the - * results into the array "mt_plans". This is also a convenient place to - * verify that the proposed target relations are valid and open their - * indexes for insertion of new index entries. Note we *must* set - * estate->es_result_relation_info correctly while we initialize each - * sub-plan; external modules such as FDWs may depend on that (see - * contrib/postgres_fdw/postgres_fdw.c: postgresBeginDirectModify() as one - * example). + * results into the array "mt_plans". */ - saved_resultRelInfo = estate->es_result_relation_info; - - resultRelInfo = mtstate->resultRelInfo; i = 0; foreach(l, node->plans) { subplan = (Plan *) lfirst(l); - /* 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); - - /* - * 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; - /* 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]), - table_slot_callbacks(resultRelInfo->ri_RelationDesc)); - - /* 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. During INSERT, partition tuples to - * store into the transition tuple store are converted using - * PartitionToRoot map in the partition's PartitionRoutingInfo. - */ - 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(rootResultRel->ri_RelationDesc)); - resultRelInfo++; - i++; - } - - estate->es_result_relation_info = saved_resultRelInfo; - - /* Get the target relation */ - rel = (getTargetResultRelInfo(mtstate))->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. - */ - if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && - (operation == CMD_INSERT || update_tuple_routing_needed)) - 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; - i = 0; - 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++; - i++; + mtstate->mt_plans[i++] = ExecInitNode(subplan, estate, eflags); } - /* - * Initialize RETURNING projections if needed. - */ + /* Initialize some global state for RETURNING projections. */ 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); + mtstate->ps.plan->targetlist = 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 { @@ -2417,67 +2670,51 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) 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. + * Initialize the target relation for getTargetResultRelInfo() to return + * for this ModifyTableState. Other result relations, especially those + * for UPDATE and DELETE that have an RT index present in + * node->resultRelations, are initialized on as-needed basis during + * execution. INSERT or UPDATE tuple routing target partitions, which are + * not present in node->resultRelations, are also initialized on as-needed + * basis but managed by execPartition.c, not by nodeModifyTable.c. + * + * For UPDATE, DELETE on a partitioned table, the target relation is the + * "root" table given by node->rootResultRelIndex and + * node->resultRelations contains only leaf partitions. In all other cases, + * we initialize the first relation appearing in node->resultRelations as + * the target relation. For INSERTs, there's only one relation in + * node->resultRelations. */ - if (node->onConflictAction == ONCONFLICT_UPDATE) + if (node->rootResultRelIndex >= 0) { - ExprContext *econtext; - TupleDesc relationDesc; - TupleDesc tupDesc; - - /* insert may only have one plan, inheritance is not expanded */ - Assert(nplans == 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)); + Assert(operation != CMD_INSERT); + mtstate->rootResultRelInfo = + ExecGetRootResultRelInfo(mtstate, node->rootResultRelIndex); + } + else + mtstate->resultRelInfo = + ExecGetResultRelInfo(mtstate, node->resultRelIndex, true); - /* build UPDATE SET projection state */ - resultRelInfo->ri_onConflict->oc_ProjInfo = - ExecBuildProjectionInfo(node->onConflictSet, econtext, - resultRelInfo->ri_onConflict->oc_ProjSlot, - &mtstate->ps, - relationDesc); + /* Get the target relation */ + rel = (getTargetResultRelInfo(mtstate))->ri_RelationDesc; - /* initialize state to evaluate the WHERE clause, if any */ - if (node->onConflictWhere) - { - ExprState *qualexpr; + /* + * Build state for tuple routing if it's an INSERT. If an UPDATE might + * need it, ExecBuildResultRelInfo will build it when initializing + * a partition's ResultRelInfo. + */ + if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + operation == CMD_INSERT) + mtstate->mt_partition_tuple_routing = + ExecSetupPartitionTupleRouting(estate, mtstate, rel); - qualexpr = ExecInitQual((List *) node->onConflictWhere, - &mtstate->ps); - resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr; - } - } + /* + * Build state for collecting transition tuples. This requires having a + * valid trigger query context, so skip it in explain-only mode. + */ + if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY)) + ExecSetupTransitionCaptureState(mtstate, estate); /* * If we have any secondary relations in an UPDATE or DELETE, they need to @@ -2515,109 +2752,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) mtstate->mt_arowmarks[0]); /* - * Initialize the junk filter(s) if needed. INSERT queries need a filter - * if there are any junk attrs in the tlist. UPDATE and DELETE always - * need a filter, since there's always at least one junk attribute present - * --- no need to look first. Typically, this will be a 'ctid' or - * 'wholerow' attribute, but in the case of a foreign data wrapper it - * might be a set of junk attributes sufficient to identify the remote - * row. - * - * If there are multiple result relations, each one needs its own junk - * filter. Note multiple rels are only possible for UPDATE/DELETE, so we - * can't be fooled by some needing a filter 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). - */ - { - bool junk_filter_needed = false; - - switch (operation) - { - case CMD_INSERT: - foreach(l, subplan->targetlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(l); - - if (tle->resjunk) - { - junk_filter_needed = true; - break; - } - } - break; - case CMD_UPDATE: - case CMD_DELETE: - junk_filter_needed = true; - break; - default: - elog(ERROR, "unknown operation"); - break; - } - - if (junk_filter_needed) - { - resultRelInfo = mtstate->resultRelInfo; - for (i = 0; i < nplans; i++) - { - JunkFilter *j; - TupleTableSlot *junkresslot; - - subplan = mtstate->mt_plans[i]->plan; - if (operation == CMD_INSERT || operation == CMD_UPDATE) - ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, - subplan->targetlist); - - junkresslot = - ExecInitExtraTupleSlot(estate, NULL, - table_slot_callbacks(resultRelInfo->ri_RelationDesc)); - j = ExecInitJunkFilter(subplan->targetlist, - junkresslot); - - if (operation == CMD_UPDATE || operation == CMD_DELETE) - { - /* For UPDATE/DELETE, find the appropriate junk attr now */ - char relkind; - - relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind; - if (relkind == RELKIND_RELATION || - relkind == RELKIND_MATVIEW || - relkind == RELKIND_PARTITIONED_TABLE) - { - j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid"); - if (!AttributeNumberIsValid(j->jf_junkAttNo)) - 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. - */ - j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow"); - } - else - { - j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow"); - if (!AttributeNumberIsValid(j->jf_junkAttNo)) - elog(ERROR, "could not find junk wholerow column"); - } - } - - resultRelInfo->ri_junkFilter = j; - resultRelInfo++; - } - } - else - { - if (operation == CMD_INSERT) - ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc, - subplan->targetlist); - } - } - - /* * Lastly, if this is not the primary (canSetTag) ModifyTable node, add it * to estate->es_auxmodifytables so that it will be run to completion by * ExecPostprocessPlan. (It'd actually work fine to add the primary @@ -2647,20 +2781,6 @@ ExecEndModifyTable(ModifyTableState *node) int i; /* - * Allow any FDWs to shut down - */ - for (i = 0; i < node->mt_nplans; 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. */ diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c index 2fcf2e6..689030b 100644 --- a/src/backend/replication/logical/worker.c +++ b/src/backend/replication/logical/worker.c @@ -213,7 +213,7 @@ create_estate_for_relation(LogicalRepRelMapEntry *rel) resultRelInfo = makeNode(ResultRelInfo); InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0); - estate->es_result_relations = resultRelInfo; + estate->es_result_relations = &resultRelInfo; estate->es_num_result_relations = 1; estate->es_result_relation_info = resultRelInfo; diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 415e117..c65f0a8 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -603,4 +603,8 @@ extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd); extern void CheckSubscriptionRelkind(char relkind, const char *nspname, const char *relname); +/* prototypes from nodeModifyTable.c */ +extern ResultRelInfo *ExecGetResultRelInfo(ModifyTableState *mtstate, int resultRelIndex, + bool create_it); + #endif /* EXECUTOR_H */ diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 647bb79..393aa49 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -530,7 +530,7 @@ typedef struct EState CommandId es_output_cid; /* Info about target table(s) for insert/update/delete queries: */ - ResultRelInfo *es_result_relations; /* array of ResultRelInfos */ + ResultRelInfo **es_result_relations; /* array of ResultRelInfo pointers */ int es_num_result_relations; /* length of array */ ResultRelInfo *es_result_relation_info; /* currently active array elt */ @@ -540,7 +540,8 @@ typedef struct EState * es_result_relations, but we need access to the roots for firing * triggers and for runtime tuple routing. */ - ResultRelInfo *es_root_result_relations; /* array of ResultRelInfos */ + ResultRelInfo **es_root_result_relations; /* array of ResultRelInfo + * pointers */ int es_num_root_result_relations; /* length of the array */ PartitionDirectory es_partition_directory; /* for PartitionDesc lookup */ @@ -1177,6 +1178,8 @@ typedef struct ModifyTableState PlanState **mt_plans; /* subplans (one per target rel) */ int mt_nplans; /* number of plans in the array */ int mt_whichplan; /* which one is being executed (0..n-1) */ + List *mt_done_rels; /* RT indexes of result relations that have + * been fully processed. */ TupleTableSlot **mt_scans; /* input tuple corresponding to underlying * plans */ ResultRelInfo *resultRelInfo; /* per-subplan target relations */ -- 1.8.3.1