v16-0001-Initialize-result-relation-information-lazily.patch
application/octet-stream
Filename: v16-0001-Initialize-result-relation-information-lazily.patch
Type: application/octet-stream
Part: 0
Patch
Format: unified
Series: patch v16-0001
| File | + | − |
|---|---|---|
| contrib/postgres_fdw/postgres_fdw.c | 2 | 2 |
| src/backend/commands/copyfrom.c | 1 | 1 |
| src/backend/commands/trigger.c | 1 | 1 |
| src/backend/executor/execMain.c | 25 | 1 |
| src/backend/executor/execPartition.c | 32 | 130 |
| src/backend/executor/execUtils.c | 23 | 0 |
| src/backend/executor/nodeModifyTable.c | 415 | 375 |
| src/backend/replication/logical/worker.c | 1 | 1 |
| src/include/executor/execPartition.h | 0 | 1 |
| src/include/executor/executor.h | 6 | 2 |
| src/include/nodes/execnodes.h | 4 | 0 |
| src/test/regress/expected/inherit.out | 1 | 1 |
| src/test/regress/expected/privileges.out | 1 | 1 |
| src/test/regress/expected/updatable_views.out | 6 | 12 |
| src/test/regress/expected/update.out | 6 | 6 |
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 16c2979f2d..5f6d0fa1bf 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -1879,7 +1879,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
rte,
resultRelInfo,
mtstate->operation,
- outerPlanState(mtstate)->plan,
+ outerPlan(mtstate->ps.plan),
query,
target_attrs,
values_end_len,
@@ -1983,7 +1983,7 @@ postgresGetForeignModifyBatchSize(ResultRelInfo *resultRelInfo)
batch_size = get_batch_size_option(resultRelInfo->ri_RelationDesc);
/* Disable batching when we have to use RETURNING. */
- if (resultRelInfo->ri_projectReturning != NULL ||
+ if (resultRelInfo->ri_returningList != NULL ||
(resultRelInfo->ri_TrigDesc &&
resultRelInfo->ri_TrigDesc->trig_insert_after_row))
return 1;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index be2e3d7354..20e7d57d41 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -697,7 +697,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/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..bd457254d0 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1221,6 +1221,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_projectNew = NULL;
resultRelInfo->ri_newTupleSlot = NULL;
resultRelInfo->ri_oldTupleSlot = NULL;
+ resultRelInfo->ri_projectNewInfoValid = false;
resultRelInfo->ri_FdwState = NULL;
resultRelInfo->ri_usesFdwDirectModify = false;
resultRelInfo->ri_ConstraintExprs = NULL;
@@ -1231,11 +1232,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
+ * ExecInitModifyTable().
+ */
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;
}
@@ -1923,7 +1930,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
*/
void
ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
- TupleTableSlot *slot, EState *estate)
+ TupleTableSlot *slot, EState *estate,
+ PlanState *parent)
{
Relation rel = resultRelInfo->ri_RelationDesc;
TupleDesc tupdesc = RelationGetDescr(rel);
@@ -1931,6 +1939,22 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
ListCell *l1,
*l2;
+ /* Initialize the quals if not already done. */
+ if (resultRelInfo->ri_WithCheckOptionExprs == NIL)
+ {
+ List *wcoExprs = NIL;
+
+ foreach(l1, resultRelInfo->ri_WithCheckOptions)
+ {
+ WithCheckOption *wco = (WithCheckOption *) lfirst(l1);
+ ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
+ parent);
+
+ wcoExprs = lappend(wcoExprs, wcoExpr);
+ }
+ resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
+ }
+
/*
* We will use the EState's per-tuple context for evaluating constraint
* expressions (creating it if it's not already there).
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 558060e080..ead630a367 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
@@ -613,7 +544,7 @@ 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)
{
@@ -680,8 +611,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
*/
if (node && node->returningLists != NIL)
{
- TupleTableSlot *slot;
- ExprContext *econtext;
List *returningList;
/* See the comment above for WCO lists. */
@@ -716,25 +645,11 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
/* We ignore the value of found_whole_row. */
leaf_part_rri->ri_returningList = returningList;
-
- /*
- * Initialize the projection itself.
- *
- * Use the slot and the expression context that would have been set up
- * in ExecInitModifyTable() for projection's output.
- */
- Assert(mtstate->ps.ps_ResultTupleSlot != NULL);
- slot = mtstate->ps.ps_ResultTupleSlot;
- Assert(mtstate->ps.ps_ExprContext != NULL);
- econtext = mtstate->ps.ps_ExprContext;
- leaf_part_rri->ri_projectReturning =
- ExecBuildProjectionInfo(returningList, econtext, slot,
- &mtstate->ps, RelationGetDescr(partrel));
}
/* 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 +825,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 +855,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 +935,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 +944,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 +1111,6 @@ void
ExecCleanupTupleRouting(ModifyTableState *mtstate,
PartitionTupleRouting *proute)
{
- HTAB *htab = proute->subplan_resultrel_htab;
int i;
/*
@@ -1230,20 +1141,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..6d47344f3d 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -154,12 +154,38 @@ ExecCheckPlanOutput(Relation resultRel, List *targetList)
errdetail("Query has too few columns.")));
}
+/* Initializes the RETURNING projection for given result relation. */
+static void
+ExecInitReturningProjection(ResultRelInfo *resultRelInfo,
+ ModifyTableState *mtstate)
+{
+ TupleTableSlot *slot;
+ ExprContext *econtext;
+
+ /* should not get called twice */
+ Assert(resultRelInfo->ri_projectReturning == NULL);
+
+
+ /* ExecInitModifyTable() should've initialized these. */
+ Assert(mtstate->ps.ps_ExprContext != NULL);
+ econtext = mtstate->ps.ps_ExprContext;
+ Assert(mtstate->ps.ps_ResultTupleSlot != NULL);
+ slot = mtstate->ps.ps_ResultTupleSlot;
+
+ Assert(resultRelInfo->ri_returningList != NIL);
+ resultRelInfo->ri_projectReturning =
+ ExecBuildProjectionInfo(resultRelInfo->ri_returningList,
+ econtext, slot, &mtstate->ps,
+ resultRelInfo->ri_RelationDesc->rd_att);
+}
+
/*
* ExecProcessReturning --- evaluate a RETURNING list
*
* resultRelInfo: current result rel
* tupleSlot: slot holding tuple actually inserted/updated/deleted
* planSlot: slot holding tuple returned by top subplan node
+ * mtstate: query's plan state
*
* Note: If tupleSlot is NULL, the FDW should have already provided econtext's
* scan tuple.
@@ -169,10 +195,18 @@ ExecCheckPlanOutput(Relation resultRel, List *targetList)
static TupleTableSlot *
ExecProcessReturning(ResultRelInfo *resultRelInfo,
TupleTableSlot *tupleSlot,
- TupleTableSlot *planSlot)
+ TupleTableSlot *planSlot,
+ ModifyTableState *mtstate)
{
- ProjectionInfo *projectReturning = resultRelInfo->ri_projectReturning;
- ExprContext *econtext = projectReturning->pi_exprContext;
+ ProjectionInfo *projectReturning;
+ ExprContext *econtext;
+
+ /* Initialize the projection if not already done. */
+ if (resultRelInfo->ri_projectReturning == NULL)
+ ExecInitReturningProjection(resultRelInfo, mtstate);
+
+ projectReturning = resultRelInfo->ri_projectReturning;
+ econtext = projectReturning->pi_exprContext;
/* Make tuple and any needed join variables available to ExecProject */
if (tupleSlot)
@@ -371,6 +405,110 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
MemoryContextSwitchTo(oldContext);
}
+/*
+ * Initialize projection to create tuples suitable for the given result rel.
+ * 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.
+ *
+ * This is also a convenient place to verify that the output of an INSERT or
+ * UPDATE matches the target table.
+ */
+static inline void
+ExecInitNewTupleProjection(ResultRelInfo *resultRelInfo,
+ ModifyTableState *mtstate,
+ int whichrel)
+{
+ EState *estate = mtstate->ps.state;
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+ Plan *subplan = outerPlan(node);
+ CmdType operation = mtstate->operation;
+ ListCell *l;
+
+ Assert(!resultRelInfo->ri_projectNewInfoValid);
+
+ 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);
+
+ Assert(whichrel >= 0 && whichrel < mtstate->mt_nrels);
+ updateColnos = (List *) list_nth(node->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);
+ }
+
+ resultRelInfo->ri_projectNewInfoValid = true;
+}
+
/*
* ExecGetInsertNewTuple
* This prepares a "new" tuple ready to be inserted into given result
@@ -488,6 +626,9 @@ ExecInsert(ModifyTableState *mtstate,
resultRelInfo = partRelInfo;
}
+ if (resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
+
ExecMaterializeSlot(slot);
resultRelationDesc = resultRelInfo->ri_RelationDesc;
@@ -627,7 +768,8 @@ ExecInsert(ModifyTableState *mtstate,
* we are looking for at this point.
*/
if (resultRelInfo->ri_WithCheckOptions != NIL)
- ExecWithCheckOptions(wco_kind, resultRelInfo, slot, estate);
+ ExecWithCheckOptions(wco_kind, resultRelInfo, slot, estate,
+ &mtstate->ps);
/*
* Check the constraints of the tuple.
@@ -641,7 +783,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);
@@ -822,11 +964,12 @@ ExecInsert(ModifyTableState *mtstate,
* are looking for at this point.
*/
if (resultRelInfo->ri_WithCheckOptions != NIL)
- ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate);
+ ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate,
+ &mtstate->ps);
/* Process RETURNING if present */
- if (resultRelInfo->ri_projectReturning)
- result = ExecProcessReturning(resultRelInfo, slot, planSlot);
+ if (resultRelInfo->ri_returningList)
+ result = ExecProcessReturning(resultRelInfo, slot, planSlot, mtstate);
return result;
}
@@ -882,7 +1025,8 @@ ExecBatchInsert(ModifyTableState *mtstate,
* comment in ExecInsert.
*/
if (resultRelInfo->ri_WithCheckOptions != NIL)
- ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate);
+ ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate,
+ &mtstate->ps);
}
if (canSetTag && numInserted > 0)
@@ -1205,7 +1349,7 @@ ldelete:;
ar_delete_trig_tcs);
/* Process RETURNING if present and if requested */
- if (processReturning && resultRelInfo->ri_projectReturning)
+ if (processReturning && resultRelInfo->ri_returningList)
{
/*
* We have to put the target tuple into a slot, which means first we
@@ -1233,7 +1377,7 @@ ldelete:;
}
}
- rslot = ExecProcessReturning(resultRelInfo, slot, planSlot);
+ rslot = ExecProcessReturning(resultRelInfo, slot, planSlot, mtstate);
/*
* Before releasing the target tuple again, make sure rslot has a
@@ -1276,7 +1420,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 +1438,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 +1521,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 +1591,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 */
@@ -1534,7 +1694,8 @@ lreplace:;
* kind we are looking for at this point.
*/
ExecWithCheckOptions(WCO_RLS_UPDATE_CHECK,
- resultRelInfo, slot, estate);
+ resultRelInfo, slot, estate,
+ &mtstate->ps);
}
/*
@@ -1547,6 +1708,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
@@ -1757,11 +1925,12 @@ lreplace:;
* are looking for at this point.
*/
if (resultRelInfo->ri_WithCheckOptions != NIL)
- ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate);
+ ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate,
+ &mtstate->ps);
/* Process RETURNING if present */
- if (resultRelInfo->ri_projectReturning)
- return ExecProcessReturning(resultRelInfo, slot, planSlot);
+ if (resultRelInfo->ri_returningList)
+ return ExecProcessReturning(resultRelInfo, slot, planSlot, mtstate);
return NULL;
}
@@ -1955,7 +2124,8 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
*/
ExecWithCheckOptions(WCO_RLS_CONFLICT_CHECK, resultRelInfo,
existing,
- mtstate->ps.state);
+ mtstate->ps.state,
+ &mtstate->ps);
}
/* Project the new tuple version */
@@ -2244,38 +2414,8 @@ 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;
-
- 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;
- }
- }
+ resultRelInfo = ExecLookupResultRelByOid(node, resultoid,
+ false);
}
/*
@@ -2292,7 +2432,7 @@ ExecModifyTable(PlanState *pstate)
* ExecProcessReturning by IterateDirectModify, so no need to
* provide it here.
*/
- slot = ExecProcessReturning(resultRelInfo, NULL, planSlot);
+ slot = ExecProcessReturning(resultRelInfo, NULL, planSlot, node);
return slot;
}
@@ -2381,11 +2521,22 @@ ExecModifyTable(PlanState *pstate)
switch (operation)
{
case CMD_INSERT:
+ if (!resultRelInfo->ri_projectNewInfoValid)
+ ExecInitNewTupleProjection(resultRelInfo, node,
+ node->mt_lastResultIndex);
+ Assert(resultRelInfo->ri_newTupleSlot != NULL);
+
slot = ExecGetInsertNewTuple(resultRelInfo, planSlot);
slot = ExecInsert(node, resultRelInfo, slot, planSlot,
estate, node->canSetTag);
break;
case CMD_UPDATE:
+ if (!resultRelInfo->ri_projectNewInfoValid)
+ ExecInitNewTupleProjection(resultRelInfo, node,
+ node->mt_lastResultIndex);
+ Assert(resultRelInfo->ri_projectNew != NULL &&
+ resultRelInfo->ri_newTupleSlot != NULL &&
+ resultRelInfo->ri_oldTupleSlot != NULL);
/*
* Make the new tuple by combining plan's output tuple with
@@ -2408,6 +2559,7 @@ ExecModifyTable(PlanState *pstate)
oldSlot))
elog(ERROR, "failed to fetch tuple being updated");
}
+
slot = ExecGetUpdateNewTuple(resultRelInfo, planSlot,
oldSlot);
@@ -2482,7 +2634,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
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)));
@@ -2542,8 +2693,37 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
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.)
+ * Initialize result tuple slot and assign its rowtype using the first
+ * RETURNING list. We assume the rest will look the same.
+ */
+ if (node->returningLists)
+ {
+ 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;
+ }
+
+ /*
+ * Open all the result relations and initialize the ResultRelInfo structs,
+ * initializing its fields as needed. (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.
*/
@@ -2554,8 +2734,18 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
Index resultRelation = lfirst_int(l);
if (resultRelInfo != mtstate->rootResultRelInfo)
+ {
ExecInitResultRelation(estate, resultRelInfo, resultRelation);
+ /*
+ * For child result relations, store the root result relation
+ * pointer. We do so for the convenience of places that want to
+ * look at the query's original target relation but don't have the
+ * mtstate handy.
+ */
+ resultRelInfo->ri_RootResultRelInfo = mtstate->rootResultRelInfo;
+ }
+
/* Initialize the usesFdwDirectModify flag */
resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
node->fdwDirectModifyPlans);
@@ -2565,47 +2755,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
*/
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 &&
@@ -2621,129 +2770,132 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int 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().
+ * Initialize any WITH CHECK OPTION constraints if needed.
*/
- 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.
- */
- 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;
- foreach(l, node->withCheckOptionLists)
- {
- List *wcoList = (List *) lfirst(l);
- List *wcoExprs = NIL;
- ListCell *ll;
-
- foreach(ll, wcoList)
+ if (node->withCheckOptionLists)
{
- WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
- ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
- &mtstate->ps);
+ List *wcoList = (List *) list_nth(node->withCheckOptionLists, i);
- wcoExprs = lappend(wcoExprs, wcoExpr);
+ resultRelInfo->ri_WithCheckOptions = wcoList;
+ /*
+ * ri_WithCheckOptionExprs is built the first time it needs to be
+ * used (see ExecWithCheckOptions()).
+ */
}
- 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.
+ * Initialize RETURNING projections if needed.
*/
- 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;
+ if (node->returningLists)
+ {
+ List *rlist = (List *) list_nth(node->returningLists, i);
- /* Need an econtext too */
- if (mtstate->ps.ps_ExprContext == NULL)
- ExecAssignExprContext(estate, &mtstate->ps);
- econtext = mtstate->ps.ps_ExprContext;
+ resultRelInfo->ri_returningList = rlist;
+ /*
+ * ri_projectReturning is built the first time it needs to be used
+ * (see ExecProcessReturning), unless the relation is going to be
+ * "direct modified" by its FDW.
+ */
+ if (resultRelInfo->ri_usesFdwDirectModify)
+ ExecInitReturningProjection(resultRelInfo, mtstate);
+ }
/*
- * Build a projection for each result rel.
+ * 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.
*/
- resultRelInfo = mtstate->resultRelInfo;
- foreach(l, node->returningLists)
+ if (operation == CMD_UPDATE || operation == CMD_DELETE)
{
- List *rlist = (List *) lfirst(l);
+ char relkind;
- resultRelInfo->ri_returningList = rlist;
- resultRelInfo->ri_projectReturning =
- ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
- resultRelInfo->ri_RelationDesc->rd_att);
- resultRelInfo++;
+ 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");
+ }
}
- }
- else
- {
+
/*
- * We still must construct a dummy result tuple type, because InitPlan
- * expects one (maybe should change that?).
+ * 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.
*/
- mtstate->ps.plan->targetlist = NIL;
- ExecInitResultTypeTL(&mtstate->ps);
+ 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);
+ }
- mtstate->ps.ps_ExprContext = NULL;
+ resultRelInfo++;
+ i++;
}
+ /*
+ * Initialize the subplan.
+ */
+ outerPlanState(mtstate) = ExecInitNode(subplan, estate, eflags);
+
+ /* Get the root target relation */
+ rel = mtstate->rootResultRelInfo->ri_RelationDesc;
+
+ /*
+ * 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)
+ mtstate->mt_partition_tuple_routing =
+ ExecSetupPartitionTupleRouting(estate, rel);
+
/* Set the list of arbiter indexes if needed for ON CONFLICT */
resultRelInfo = mtstate->resultRelInfo;
if (node->onConflictAction != ONCONFLICT_NONE)
+ {
+ /* insert may only have one relation, inheritance is not expanded */
+ Assert(nrels == 1);
resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes;
+ }
/*
* If needed, Initialize target list, projection and qual for ON CONFLICT
@@ -2827,151 +2979,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
@@ -3026,34 +3033,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
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++)
- {
- 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);
-
- resultRelInfo++;
- }
- }
-
/*
* 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
@@ -3140,3 +3119,64 @@ 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++)
+ {
+ ResultRelInfo *rInfo = node->resultRelInfo + ndx;
+
+ if (RelationGetRelid(rInfo->ri_RelationDesc) == 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 node->resultRelInfo + 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 26dcc4485e..7c6ee53d4f 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -211,7 +211,8 @@ extern bool ExecPartitionCheck(ResultRelInfo *resultRelInfo,
extern void ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
TupleTableSlot *slot, EState *estate);
extern void ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
- TupleTableSlot *slot, EState *estate);
+ TupleTableSlot *slot, EState *estate,
+ PlanState *parent);
extern LockTupleMode ExecUpdateLockMode(EState *estate, ResultRelInfo *relinfo);
extern ExecRowMark *ExecFindRowMark(EState *estate, Index rti, bool missing_ok);
extern ExecAuxRowMark *ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist);
@@ -596,6 +597,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);
@@ -645,9 +647,11 @@ 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 *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 52d1fa018b..a0b29745bb 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -431,6 +431,8 @@ typedef struct ResultRelInfo
TupleTableSlot *ri_newTupleSlot;
/* Slot to hold the old tuple being updated */
TupleTableSlot *ri_oldTupleSlot;
+ /* Have the projection and the slots above been initialized? */
+ bool ri_projectNewInfoValid;
/* triggers to be fired, if any */
TriggerDesc *ri_TrigDesc;
@@ -516,6 +518,8 @@ typedef struct ResultRelInfo
* transition tuple capture or update partition row movement is active.
*/
TupleConversionMap *ri_ChildToRootMap;
+ /* has the map been initialized? */
+ bool ri_ChildToRootMapValid;
/* for use by copyfrom.c when performing multi-inserts */
struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
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/updatable_views.out b/src/test/regress/expected/updatable_views.out
index cdff914b93..dd309283be 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -1892,28 +1892,22 @@ UPDATE rw_view1 SET a = a + 5; -- should fail
ERROR: new row violates check option for view "rw_view1"
DETAIL: Failing row contains (15).
EXPLAIN (costs off) INSERT INTO rw_view1 VALUES (5);
- QUERY PLAN
----------------------------------------------------------
+ QUERY PLAN
+----------------------
Insert on base_tbl b
-> Result
- SubPlan 1
- -> Index Only Scan using ref_tbl_pkey on ref_tbl r
- Index Cond: (a = b.a)
-(5 rows)
+(2 rows)
EXPLAIN (costs off) UPDATE rw_view1 SET a = a + 5;
- QUERY PLAN
------------------------------------------------------------
+ QUERY PLAN
+-----------------------------------------
Update on base_tbl b
-> Hash Join
Hash Cond: (b.a = r.a)
-> Seq Scan on base_tbl b
-> Hash
-> Seq Scan on ref_tbl r
- SubPlan 1
- -> Index Only Scan using ref_tbl_pkey on ref_tbl r_1
- Index Cond: (a = b.a)
-(9 rows)
+(6 rows)
DROP TABLE base_tbl, ref_tbl CASCADE;
NOTICE: drop cascades to view rw_view1
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;