Author: Noah Misch Commit: Noah Misch Expand comments and add an assertion in nodeModifyTable.c. Most comments concern RELKIND_VIEW. One addresses the ExecUpdate() "tupleid" parameter. A later commit will rely on these facts, but they hold already. Back-patch to v12 (all supported versions), the plan for that commit. Reviewed by FIXME. Discussion: https://postgr.es/m/FIXME diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index cee60d3..7bcc03c 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1398,18 +1398,18 @@ ExecDeleteEpilogue(ModifyTableContext *context, ResultRelInfo *resultRelInfo, * DELETE is like UPDATE, except that we delete the tuple and no * index modifications are needed. * - * When deleting from a table, tupleid identifies the tuple to - * delete and oldtuple is NULL. When deleting from a view, - * oldtuple is passed to the INSTEAD OF triggers and identifies - * what to delete, and tupleid is invalid. When deleting from a - * foreign table, tupleid is invalid; the FDW has to figure out - * which row to delete using data from the planSlot. oldtuple is - * passed to foreign table triggers; it is NULL when the foreign - * table has no relevant triggers. We use tupleDeleted to indicate - * whether the tuple is actually deleted, callers can use it to - * decide whether to continue the operation. When this DELETE is a - * part of an UPDATE of partition-key, then the slot returned by - * EvalPlanQual() is passed back using output parameter epqreturnslot. + * When deleting from a table, tupleid identifies the tuple to delete and + * oldtuple is NULL. When deleting through a view INSTEAD OF trigger, + * oldtuple is passed to the triggers and identifies what to delete, and + * tupleid is invalid. When deleting from a foreign table, tupleid is + * invalid; the FDW has to figure out which row to delete using data from + * the planSlot. oldtuple is passed to foreign table triggers; it is + * NULL when the foreign table has no relevant triggers. We use + * tupleDeleted to indicate whether the tuple is actually deleted, + * callers can use it to decide whether to continue the operation. When + * this DELETE is a part of an UPDATE of partition-key, then the slot + * returned by EvalPlanQual() is passed back using output parameter + * epqreturnslot. * * Returns RETURNING result if any, otherwise NULL. * ---------------------------------------------------------------- @@ -2238,21 +2238,22 @@ ExecCrossPartitionUpdateForeignKey(ModifyTableContext *context, * is, we don't want to get stuck in an infinite loop * which corrupts your database.. * - * When updating a table, tupleid identifies the tuple to - * update and oldtuple is NULL. When updating a view, oldtuple - * is passed to the INSTEAD OF triggers and identifies what to - * update, and tupleid is invalid. When updating a foreign table, - * tupleid is invalid; the FDW has to figure out which row to - * update using data from the planSlot. oldtuple is passed to - * foreign table triggers; it is NULL when the foreign table has - * no relevant triggers. + * When updating a table, tupleid identifies the tuple to update and + * oldtuple is NULL. When updating through a view INSTEAD OF trigger, + * oldtuple is passed to the triggers and identifies what to update, and + * tupleid is invalid. When updating a foreign table, tupleid is + * invalid; the FDW has to figure out which row to update using data from + * the planSlot. oldtuple is passed to foreign table triggers; it is + * NULL when the foreign table has no relevant triggers. * * slot contains the new tuple value to be stored. * planSlot is the output of the ModifyTable's subplan; we use it * to access values from other input tables (for RETURNING), * row-ID junk columns, etc. * - * Returns RETURNING result if any, otherwise NULL. + * Returns RETURNING result if any, otherwise NULL. On exit, if tupleid + * had identified the tuple to update, it will identify the tuple + * actually updated after EvalPlanQual. * ---------------------------------------------------------------- */ static TupleTableSlot * @@ -2717,10 +2718,10 @@ ExecMerge(ModifyTableContext *context, ResultRelInfo *resultRelInfo, /*----- * If we are dealing with a WHEN MATCHED case, tupleid or oldtuple is - * valid, depending on whether the result relation is a table or a view. - * We execute the first action for which the additional WHEN MATCHED AND - * quals pass. If an action without quals is found, that action is - * executed. + * valid, depending on whether the result relation is a table or a view + * having an INSTEAD OF trigger. We execute the first action for which + * the additional WHEN MATCHED AND quals pass. If an action without quals + * is found, that action is executed. * * Similarly, in the WHEN NOT MATCHED BY SOURCE case, tupleid or oldtuple * is valid, and we look at the given WHEN NOT MATCHED BY SOURCE actions @@ -2811,8 +2812,8 @@ ExecMerge(ModifyTableContext *context, ResultRelInfo *resultRelInfo, * Check and execute the first qualifying MATCHED or NOT MATCHED BY SOURCE * action, depending on whether the join quals are satisfied. If the target * relation is a table, the current target tuple is identified by tupleid. - * Otherwise, if the target relation is a view, oldtuple is the current target - * tuple from the view. + * Otherwise, if the target relation is a view having an INSTEAD OF trigger, + * oldtuple is the current target tuple from the view. * * We start from the first WHEN MATCHED or WHEN NOT MATCHED BY SOURCE action * and check if the WHEN quals pass, if any. If the WHEN quals for the first @@ -2878,8 +2879,11 @@ ExecMergeMatched(ModifyTableContext *context, ResultRelInfo *resultRelInfo, */ Assert(tupleid != NULL || oldtuple != NULL); if (oldtuple != NULL) + { + Assert(resultRelInfo->ri_TrigDesc); ExecForceStoreHeapTuple(oldtuple, resultRelInfo->ri_oldTupleSlot, false); + } else if (!table_tuple_fetch_row_version(resultRelInfo->ri_RelationDesc, tupleid, SnapshotAny, @@ -3992,8 +3996,8 @@ ExecModifyTable(PlanState *pstate) * know enough here to set t_tableOid. Quite separately from * this, the FDW may fetch its own junk attrs to identify the row. * - * Other relevant relkinds, currently limited to views, always - * have a wholerow attribute. + * Other relevant relkinds, currently limited to views having + * INSTEAD OF triggers, always have a wholerow attribute. */ else if (AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo)) {