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;