aboutsummaryrefslogtreecommitdiff
path: root/src/backend/executor/nodeModifyTable.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/executor/nodeModifyTable.c')
-rw-r--r--src/backend/executor/nodeModifyTable.c223
1 files changed, 197 insertions, 26 deletions
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 1af8c9caf6c..bc82e035ba2 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -102,6 +102,13 @@ typedef struct ModifyTableContext
TM_FailureData tmfd;
/*
+ * The tuple deleted when doing a cross-partition UPDATE with a RETURNING
+ * clause that refers to OLD columns (converted to the root's tuple
+ * descriptor).
+ */
+ TupleTableSlot *cpDeletedSlot;
+
+ /*
* The tuple projected by the INSERT's RETURNING clause, when doing a
* cross-partition UPDATE
*/
@@ -243,34 +250,81 @@ ExecCheckPlanOutput(Relation resultRel, List *targetList)
/*
* ExecProcessReturning --- evaluate a RETURNING list
*
+ * context: context for the ModifyTable operation
* resultRelInfo: current result rel
- * tupleSlot: slot holding tuple actually inserted/updated/deleted
+ * cmdType: operation/merge action performed (INSERT, UPDATE, or DELETE)
+ * oldSlot: slot holding old tuple deleted or updated
+ * newSlot: slot holding new tuple inserted or updated
* planSlot: slot holding tuple returned by top subplan node
*
- * Note: If tupleSlot is NULL, the FDW should have already provided econtext's
- * scan tuple.
+ * Note: If oldSlot and newSlot are NULL, the FDW should have already provided
+ * econtext's scan tuple and its old & new tuples are not needed (FDW direct-
+ * modify is disabled if the RETURNING list refers to any OLD/NEW values).
*
* Returns a slot holding the result tuple
*/
static TupleTableSlot *
-ExecProcessReturning(ResultRelInfo *resultRelInfo,
- TupleTableSlot *tupleSlot,
+ExecProcessReturning(ModifyTableContext *context,
+ ResultRelInfo *resultRelInfo,
+ CmdType cmdType,
+ TupleTableSlot *oldSlot,
+ TupleTableSlot *newSlot,
TupleTableSlot *planSlot)
{
+ EState *estate = context->estate;
ProjectionInfo *projectReturning = resultRelInfo->ri_projectReturning;
ExprContext *econtext = projectReturning->pi_exprContext;
/* Make tuple and any needed join variables available to ExecProject */
- if (tupleSlot)
- econtext->ecxt_scantuple = tupleSlot;
+ switch (cmdType)
+ {
+ case CMD_INSERT:
+ case CMD_UPDATE:
+ /* return new tuple by default */
+ if (newSlot)
+ econtext->ecxt_scantuple = newSlot;
+ break;
+
+ case CMD_DELETE:
+ /* return old tuple by default */
+ if (oldSlot)
+ econtext->ecxt_scantuple = oldSlot;
+ break;
+
+ default:
+ elog(ERROR, "unrecognized commandType: %d", (int) cmdType);
+ }
econtext->ecxt_outertuple = planSlot;
+ /* Make old/new tuples available to ExecProject, if required */
+ if (oldSlot)
+ econtext->ecxt_oldtuple = oldSlot;
+ else if (projectReturning->pi_state.flags & EEO_FLAG_HAS_OLD)
+ econtext->ecxt_oldtuple = ExecGetAllNullSlot(estate, resultRelInfo);
+ else
+ econtext->ecxt_oldtuple = NULL; /* No references to OLD columns */
+
+ if (newSlot)
+ econtext->ecxt_newtuple = newSlot;
+ else if (projectReturning->pi_state.flags & EEO_FLAG_HAS_NEW)
+ econtext->ecxt_newtuple = ExecGetAllNullSlot(estate, resultRelInfo);
+ else
+ econtext->ecxt_newtuple = NULL; /* No references to NEW columns */
+
/*
- * RETURNING expressions might reference the tableoid column, so
- * reinitialize tts_tableOid before evaluating them.
+ * Tell ExecProject whether or not the OLD/NEW rows actually exist. This
+ * information is required to evaluate ReturningExpr nodes and also in
+ * ExecEvalSysVar() and ExecEvalWholeRowVar().
*/
- econtext->ecxt_scantuple->tts_tableOid =
- RelationGetRelid(resultRelInfo->ri_RelationDesc);
+ if (oldSlot == NULL)
+ projectReturning->pi_state.flags |= EEO_FLAG_OLD_IS_NULL;
+ else
+ projectReturning->pi_state.flags &= ~EEO_FLAG_OLD_IS_NULL;
+
+ if (newSlot == NULL)
+ projectReturning->pi_state.flags |= EEO_FLAG_NEW_IS_NULL;
+ else
+ projectReturning->pi_state.flags &= ~EEO_FLAG_NEW_IS_NULL;
/* Compute the RETURNING expressions */
return ExecProject(projectReturning);
@@ -1204,7 +1258,56 @@ ExecInsert(ModifyTableContext *context,
/* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning)
- result = ExecProcessReturning(resultRelInfo, slot, planSlot);
+ {
+ TupleTableSlot *oldSlot = NULL;
+
+ /*
+ * If this is part of a cross-partition UPDATE, and the RETURNING list
+ * refers to any OLD columns, ExecDelete() will have saved the tuple
+ * deleted from the original partition, which we must use here to
+ * compute the OLD column values. Otherwise, all OLD column values
+ * will be NULL.
+ */
+ if (context->cpDeletedSlot)
+ {
+ TupleConversionMap *tupconv_map;
+
+ /*
+ * Convert the OLD tuple to the new partition's format/slot, if
+ * needed. Note that ExceDelete() already converted it to the
+ * root's partition's format/slot.
+ */
+ oldSlot = context->cpDeletedSlot;
+ tupconv_map = ExecGetRootToChildMap(resultRelInfo, estate);
+ if (tupconv_map != NULL)
+ {
+ oldSlot = execute_attr_map_slot(tupconv_map->attrMap,
+ oldSlot,
+ ExecGetReturningSlot(estate,
+ resultRelInfo));
+
+ oldSlot->tts_tableOid = context->cpDeletedSlot->tts_tableOid;
+ ItemPointerCopy(&context->cpDeletedSlot->tts_tid, &oldSlot->tts_tid);
+ }
+ }
+
+ result = ExecProcessReturning(context, resultRelInfo, CMD_INSERT,
+ oldSlot, slot, planSlot);
+
+ /*
+ * For a cross-partition UPDATE, release the old tuple, first making
+ * sure that the result slot has a local copy of any pass-by-reference
+ * values.
+ */
+ if (context->cpDeletedSlot)
+ {
+ ExecMaterializeSlot(result);
+ ExecClearTuple(oldSlot);
+ if (context->cpDeletedSlot != oldSlot)
+ ExecClearTuple(context->cpDeletedSlot);
+ context->cpDeletedSlot = NULL;
+ }
+ }
if (inserted_tuple)
*inserted_tuple = slot;
@@ -1442,6 +1545,7 @@ ExecDelete(ModifyTableContext *context,
Relation resultRelationDesc = resultRelInfo->ri_RelationDesc;
TupleTableSlot *slot = NULL;
TM_Result result;
+ bool saveOld;
if (tupleDeleted)
*tupleDeleted = false;
@@ -1676,8 +1780,17 @@ ldelete:
ExecDeleteEpilogue(context, resultRelInfo, tupleid, oldtuple, changingPart);
- /* Process RETURNING if present and if requested */
- if (processReturning && resultRelInfo->ri_projectReturning)
+ /*
+ * Process RETURNING if present and if requested.
+ *
+ * If this is part of a cross-partition UPDATE, and the RETURNING list
+ * refers to any OLD column values, save the old tuple here for later
+ * processing of the RETURNING list by ExecInsert().
+ */
+ saveOld = changingPart && resultRelInfo->ri_projectReturning &&
+ resultRelInfo->ri_projectReturning->pi_state.flags & EEO_FLAG_HAS_OLD;
+
+ if (resultRelInfo->ri_projectReturning && (processReturning || saveOld))
{
/*
* We have to put the target tuple into a slot, which means first we
@@ -1705,7 +1818,41 @@ ldelete:
}
}
- rslot = ExecProcessReturning(resultRelInfo, slot, context->planSlot);
+ /*
+ * If required, save the old tuple for later processing of the
+ * RETURNING list by ExecInsert().
+ */
+ if (saveOld)
+ {
+ TupleConversionMap *tupconv_map;
+
+ /*
+ * Convert the tuple into the root partition's format/slot, if
+ * needed. ExecInsert() will then convert it to the new
+ * partition's format/slot, if necessary.
+ */
+ tupconv_map = ExecGetChildToRootMap(resultRelInfo);
+ if (tupconv_map != NULL)
+ {
+ ResultRelInfo *rootRelInfo = context->mtstate->rootResultRelInfo;
+ TupleTableSlot *oldSlot = slot;
+
+ slot = execute_attr_map_slot(tupconv_map->attrMap,
+ slot,
+ ExecGetReturningSlot(estate,
+ rootRelInfo));
+
+ slot->tts_tableOid = oldSlot->tts_tableOid;
+ ItemPointerCopy(&oldSlot->tts_tid, &slot->tts_tid);
+ }
+
+ context->cpDeletedSlot = slot;
+
+ return NULL;
+ }
+
+ rslot = ExecProcessReturning(context, resultRelInfo, CMD_DELETE,
+ slot, NULL, context->planSlot);
/*
* Before releasing the target tuple again, make sure rslot has a
@@ -1758,6 +1905,7 @@ ExecCrossPartitionUpdate(ModifyTableContext *context,
bool tuple_deleted;
TupleTableSlot *epqslot = NULL;
+ context->cpDeletedSlot = NULL;
context->cpUpdateReturningSlot = NULL;
*retry_slot = NULL;
@@ -2258,6 +2406,7 @@ ExecCrossPartitionUpdateForeignKey(ModifyTableContext *context,
* the planSlot. oldtuple is passed to foreign table triggers; it is
* NULL when the foreign table has no relevant triggers.
*
+ * oldSlot contains the old tuple value.
* 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),
@@ -2270,8 +2419,8 @@ ExecCrossPartitionUpdateForeignKey(ModifyTableContext *context,
*/
static TupleTableSlot *
ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
- ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot,
- bool canSetTag)
+ ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *oldSlot,
+ TupleTableSlot *slot, bool canSetTag)
{
EState *estate = context->estate;
Relation resultRelationDesc = resultRelInfo->ri_RelationDesc;
@@ -2389,7 +2538,6 @@ redo_act:
{
TupleTableSlot *inputslot;
TupleTableSlot *epqslot;
- TupleTableSlot *oldSlot;
if (IsolationUsesXactSnapshot())
ereport(ERROR,
@@ -2504,7 +2652,8 @@ redo_act:
/* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning)
- return ExecProcessReturning(resultRelInfo, slot, context->planSlot);
+ return ExecProcessReturning(context, resultRelInfo, CMD_UPDATE,
+ oldSlot, slot, context->planSlot);
return NULL;
}
@@ -2724,16 +2873,23 @@ ExecOnConflictUpdate(ModifyTableContext *context,
/* Execute UPDATE with projection */
*returning = ExecUpdate(context, resultRelInfo,
- conflictTid, NULL,
+ conflictTid, NULL, existing,
resultRelInfo->ri_onConflict->oc_ProjSlot,
canSetTag);
/*
* Clear out existing tuple, as there might not be another conflict among
* the next input rows. Don't want to hold resources till the end of the
- * query.
+ * query. First though, make sure that the returning slot, if any, has a
+ * local copy of any OLD pass-by-reference values, if it refers to any OLD
+ * columns.
*/
+ if (*returning != NULL &&
+ resultRelInfo->ri_projectReturning->pi_state.flags & EEO_FLAG_HAS_OLD)
+ ExecMaterializeSlot(*returning);
+
ExecClearTuple(existing);
+
return true;
}
@@ -3338,13 +3494,20 @@ lmerge_matched:
switch (commandType)
{
case CMD_UPDATE:
- rslot = ExecProcessReturning(resultRelInfo, newslot,
+ rslot = ExecProcessReturning(context,
+ resultRelInfo,
+ CMD_UPDATE,
+ resultRelInfo->ri_oldTupleSlot,
+ newslot,
context->planSlot);
break;
case CMD_DELETE:
- rslot = ExecProcessReturning(resultRelInfo,
+ rslot = ExecProcessReturning(context,
+ resultRelInfo,
+ CMD_DELETE,
resultRelInfo->ri_oldTupleSlot,
+ NULL,
context->planSlot);
break;
@@ -3894,6 +4057,7 @@ ExecModifyTable(PlanState *pstate)
if (node->mt_merge_pending_not_matched != NULL)
{
context.planSlot = node->mt_merge_pending_not_matched;
+ context.cpDeletedSlot = NULL;
slot = ExecMergeNotMatched(&context, node->resultRelInfo,
node->canSetTag);
@@ -3913,6 +4077,7 @@ ExecModifyTable(PlanState *pstate)
/* Fetch the next row from subplan */
context.planSlot = ExecProcNode(subplanstate);
+ context.cpDeletedSlot = NULL;
/* No more tuples to process? */
if (TupIsNull(context.planSlot))
@@ -3980,9 +4145,15 @@ ExecModifyTable(PlanState *pstate)
* A scan slot containing the data that was actually inserted,
* updated or deleted has already been made available to
* ExecProcessReturning by IterateDirectModify, so no need to
- * provide it here.
+ * provide it here. The individual old and new slots are not
+ * needed, since direct-modify is disabled if the RETURNING list
+ * refers to OLD/NEW values.
*/
- slot = ExecProcessReturning(resultRelInfo, NULL, context.planSlot);
+ Assert((resultRelInfo->ri_projectReturning->pi_state.flags & EEO_FLAG_HAS_OLD) == 0 &&
+ (resultRelInfo->ri_projectReturning->pi_state.flags & EEO_FLAG_HAS_NEW) == 0);
+
+ slot = ExecProcessReturning(&context, resultRelInfo, operation,
+ NULL, NULL, context.planSlot);
return slot;
}
@@ -4172,7 +4343,7 @@ ExecModifyTable(PlanState *pstate)
/* Now apply the update. */
slot = ExecUpdate(&context, resultRelInfo, tupleid, oldtuple,
- slot, node->canSetTag);
+ oldSlot, slot, node->canSetTag);
if (tuplock)
UnlockTuple(resultRelInfo->ri_RelationDesc, tupleid,
InplaceUpdateTupleLock);