summaryrefslogtreecommitdiff
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.c720
1 files changed, 435 insertions, 285 deletions
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index adfe97cefd..c0eab4bf0d 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -3,12 +3,12 @@
* nodeModifyTable.c
* routines to handle ModifyTable nodes.
*
- * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/executor/nodeModifyTable.c,v 1.7 2010/02/26 02:00:42 momjian Exp $
+ * src/backend/executor/nodeModifyTable.c
*
*-------------------------------------------------------------------------
*/
@@ -160,7 +160,8 @@ ExecProcessReturning(ProjectionInfo *projectReturning,
static TupleTableSlot *
ExecInsert(TupleTableSlot *slot,
TupleTableSlot *planSlot,
- EState *estate)
+ EState *estate,
+ bool canSetTag)
{
HeapTuple tuple;
ResultRelInfo *resultRelInfo;
@@ -197,59 +198,62 @@ ExecInsert(TupleTableSlot *slot,
/* BEFORE ROW INSERT Triggers */
if (resultRelInfo->ri_TrigDesc &&
- resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_INSERT] > 0)
+ resultRelInfo->ri_TrigDesc->trig_insert_before_row)
{
- HeapTuple newtuple;
+ slot = ExecBRInsertTriggers(estate, resultRelInfo, slot);
- newtuple = ExecBRInsertTriggers(estate, resultRelInfo, tuple);
-
- if (newtuple == NULL) /* "do nothing" */
+ if (slot == NULL) /* "do nothing" */
return NULL;
- if (newtuple != tuple) /* modified by Trigger(s) */
- {
- /*
- * Put the modified tuple into a slot for convenience of routines
- * below. We assume the tuple was allocated in per-tuple memory
- * context, and therefore will go away by itself. The tuple table
- * slot should not try to clear it.
- */
- TupleTableSlot *newslot = estate->es_trig_tuple_slot;
- TupleDesc tupdesc = RelationGetDescr(resultRelationDesc);
-
- if (newslot->tts_tupleDescriptor != tupdesc)
- ExecSetSlotDescriptor(newslot, tupdesc);
- ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
- slot = newslot;
- tuple = newtuple;
- }
+ /* trigger might have changed tuple */
+ tuple = ExecMaterializeSlot(slot);
}
- /*
- * Check the constraints of the tuple
- */
- if (resultRelationDesc->rd_att->constr)
- ExecConstraints(resultRelInfo, slot, estate);
+ /* INSTEAD OF ROW INSERT Triggers */
+ if (resultRelInfo->ri_TrigDesc &&
+ resultRelInfo->ri_TrigDesc->trig_insert_instead_row)
+ {
+ slot = ExecIRInsertTriggers(estate, resultRelInfo, slot);
- /*
- * insert the tuple
- *
- * Note: heap_insert returns the tid (location) of the new tuple in the
- * t_self field.
- */
- newId = heap_insert(resultRelationDesc, tuple,
- estate->es_output_cid, 0, NULL);
+ if (slot == NULL) /* "do nothing" */
+ return NULL;
- (estate->es_processed)++;
- estate->es_lastoid = newId;
- setLastTid(&(tuple->t_self));
+ /* trigger might have changed tuple */
+ tuple = ExecMaterializeSlot(slot);
- /*
- * insert index entries for tuple
- */
- if (resultRelInfo->ri_NumIndices > 0)
- recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
- estate);
+ newId = InvalidOid;
+ }
+ else
+ {
+ /*
+ * Check the constraints of the tuple
+ */
+ if (resultRelationDesc->rd_att->constr)
+ ExecConstraints(resultRelInfo, slot, estate);
+
+ /*
+ * insert the tuple
+ *
+ * Note: heap_insert returns the tid (location) of the new tuple in
+ * the t_self field.
+ */
+ newId = heap_insert(resultRelationDesc, tuple,
+ estate->es_output_cid, 0, NULL);
+
+ /*
+ * insert index entries for tuple
+ */
+ if (resultRelInfo->ri_NumIndices > 0)
+ recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+ estate);
+ }
+
+ if (canSetTag)
+ {
+ (estate->es_processed)++;
+ estate->es_lastoid = newId;
+ setLastTid(&(tuple->t_self));
+ }
/* AFTER ROW INSERT Triggers */
ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes);
@@ -268,16 +272,23 @@ ExecInsert(TupleTableSlot *slot,
* ExecDelete
*
* DELETE is like UPDATE, except that we delete the tuple and no
- * index modifications are needed
+ * 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.
*
* Returns RETURNING result if any, otherwise NULL.
* ----------------------------------------------------------------
*/
static TupleTableSlot *
ExecDelete(ItemPointer tupleid,
+ HeapTupleHeader oldtuple,
TupleTableSlot *planSlot,
EPQState *epqstate,
- EState *estate)
+ EState *estate,
+ bool canSetTag)
{
ResultRelInfo *resultRelInfo;
Relation resultRelationDesc;
@@ -293,7 +304,7 @@ ExecDelete(ItemPointer tupleid,
/* BEFORE ROW DELETE Triggers */
if (resultRelInfo->ri_TrigDesc &&
- resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_DELETE] > 0)
+ resultRelInfo->ri_TrigDesc->trig_delete_before_row)
{
bool dodelete;
@@ -304,68 +315,91 @@ ExecDelete(ItemPointer tupleid,
return NULL;
}
- /*
- * delete the tuple
- *
- * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
- * the row to be deleted is visible to that snapshot, and throw a can't-
- * serialize error if not. This is a special-case behavior needed for
- * referential integrity updates in serializable transactions.
- */
-ldelete:;
- result = heap_delete(resultRelationDesc, tupleid,
- &update_ctid, &update_xmax,
- estate->es_output_cid,
- estate->es_crosscheck_snapshot,
- true /* wait for commit */ );
- switch (result)
+ /* INSTEAD OF ROW DELETE Triggers */
+ if (resultRelInfo->ri_TrigDesc &&
+ resultRelInfo->ri_TrigDesc->trig_delete_instead_row)
{
- case HeapTupleSelfUpdated:
- /* already deleted by self; nothing to do */
+ HeapTupleData tuple;
+ bool dodelete;
+
+ Assert(oldtuple != NULL);
+ tuple.t_data = oldtuple;
+ tuple.t_len = HeapTupleHeaderGetDatumLength(oldtuple);
+ ItemPointerSetInvalid(&(tuple.t_self));
+ tuple.t_tableOid = InvalidOid;
+
+ dodelete = ExecIRDeleteTriggers(estate, resultRelInfo, &tuple);
+
+ if (!dodelete) /* "do nothing" */
return NULL;
+ }
+ else
+ {
+ /*
+ * delete the tuple
+ *
+ * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check
+ * that the row to be deleted is visible to that snapshot, and throw a
+ * can't-serialize error if not. This is a special-case behavior
+ * needed for referential integrity updates in transaction-snapshot
+ * mode transactions.
+ */
+ldelete:;
+ result = heap_delete(resultRelationDesc, tupleid,
+ &update_ctid, &update_xmax,
+ estate->es_output_cid,
+ estate->es_crosscheck_snapshot,
+ true /* wait for commit */ );
+ switch (result)
+ {
+ case HeapTupleSelfUpdated:
+ /* already deleted by self; nothing to do */
+ return NULL;
- case HeapTupleMayBeUpdated:
- break;
+ case HeapTupleMayBeUpdated:
+ break;
- case HeapTupleUpdated:
- if (IsXactIsoLevelSerializable)
- ereport(ERROR,
- (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
- errmsg("could not serialize access due to concurrent update")));
- if (!ItemPointerEquals(tupleid, &update_ctid))
- {
- TupleTableSlot *epqslot;
-
- epqslot = EvalPlanQual(estate,
- epqstate,
- resultRelationDesc,
- resultRelInfo->ri_RangeTableIndex,
- &update_ctid,
- update_xmax);
- if (!TupIsNull(epqslot))
+ case HeapTupleUpdated:
+ if (IsolationUsesXactSnapshot())
+ ereport(ERROR,
+ (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+ errmsg("could not serialize access due to concurrent update")));
+ if (!ItemPointerEquals(tupleid, &update_ctid))
{
- *tupleid = update_ctid;
- goto ldelete;
+ TupleTableSlot *epqslot;
+
+ epqslot = EvalPlanQual(estate,
+ epqstate,
+ resultRelationDesc,
+ resultRelInfo->ri_RangeTableIndex,
+ &update_ctid,
+ update_xmax);
+ if (!TupIsNull(epqslot))
+ {
+ *tupleid = update_ctid;
+ goto ldelete;
+ }
}
- }
- /* tuple already deleted; nothing to do */
- return NULL;
+ /* tuple already deleted; nothing to do */
+ return NULL;
- default:
- elog(ERROR, "unrecognized heap_delete status: %u", result);
- return NULL;
- }
+ default:
+ elog(ERROR, "unrecognized heap_delete status: %u", result);
+ return NULL;
+ }
- (estate->es_processed)++;
+ /*
+ * Note: Normally one would think that we have to delete index tuples
+ * associated with the heap tuple now...
+ *
+ * ... but in POSTGRES, we have no need to do this because VACUUM will
+ * take care of it later. We can't delete index tuples immediately
+ * anyway, since the tuple is still visible to other transactions.
+ */
+ }
- /*
- * Note: Normally one would think that we have to delete index tuples
- * associated with the heap tuple now...
- *
- * ... but in POSTGRES, we have no need to do this because VACUUM will
- * take care of it later. We can't delete index tuples immediately
- * anyway, since the tuple is still visible to other transactions.
- */
+ if (canSetTag)
+ (estate->es_processed)++;
/* AFTER ROW DELETE Triggers */
ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
@@ -382,10 +416,21 @@ ldelete:;
HeapTupleData deltuple;
Buffer delbuffer;
- deltuple.t_self = *tupleid;
- if (!heap_fetch(resultRelationDesc, SnapshotAny,
- &deltuple, &delbuffer, false, NULL))
- elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
+ if (oldtuple != NULL)
+ {
+ deltuple.t_data = oldtuple;
+ deltuple.t_len = HeapTupleHeaderGetDatumLength(oldtuple);
+ ItemPointerSetInvalid(&(deltuple.t_self));
+ deltuple.t_tableOid = InvalidOid;
+ delbuffer = InvalidBuffer;
+ }
+ else
+ {
+ deltuple.t_self = *tupleid;
+ if (!heap_fetch(resultRelationDesc, SnapshotAny,
+ &deltuple, &delbuffer, false, NULL))
+ elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
+ }
if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
@@ -395,7 +440,8 @@ ldelete:;
slot, planSlot);
ExecClearTuple(slot);
- ReleaseBuffer(delbuffer);
+ if (BufferIsValid(delbuffer))
+ ReleaseBuffer(delbuffer);
return rslot;
}
@@ -413,15 +459,22 @@ ldelete:;
* 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.
+ *
* Returns RETURNING result if any, otherwise NULL.
* ----------------------------------------------------------------
*/
static TupleTableSlot *
ExecUpdate(ItemPointer tupleid,
+ HeapTupleHeader oldtuple,
TupleTableSlot *slot,
TupleTableSlot *planSlot,
EPQState *epqstate,
- EState *estate)
+ EState *estate,
+ bool canSetTag)
{
HeapTuple tuple;
ResultRelInfo *resultRelInfo;
@@ -451,122 +504,131 @@ ExecUpdate(ItemPointer tupleid,
/* BEFORE ROW UPDATE Triggers */
if (resultRelInfo->ri_TrigDesc &&
- resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_UPDATE] > 0)
+ resultRelInfo->ri_TrigDesc->trig_update_before_row)
{
- HeapTuple newtuple;
-
- newtuple = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
- tupleid, tuple);
+ slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
+ tupleid, slot);
- if (newtuple == NULL) /* "do nothing" */
+ if (slot == NULL) /* "do nothing" */
return NULL;
- if (newtuple != tuple) /* modified by Trigger(s) */
- {
- /*
- * Put the modified tuple into a slot for convenience of routines
- * below. We assume the tuple was allocated in per-tuple memory
- * context, and therefore will go away by itself. The tuple table
- * slot should not try to clear it.
- */
- TupleTableSlot *newslot = estate->es_trig_tuple_slot;
- TupleDesc tupdesc = RelationGetDescr(resultRelationDesc);
-
- if (newslot->tts_tupleDescriptor != tupdesc)
- ExecSetSlotDescriptor(newslot, tupdesc);
- ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
- slot = newslot;
- tuple = newtuple;
- }
+ /* trigger might have changed tuple */
+ tuple = ExecMaterializeSlot(slot);
}
- /*
- * Check the constraints of the tuple
- *
- * If we generate a new candidate tuple after EvalPlanQual testing, we
- * must loop back here and recheck constraints. (We don't need to redo
- * triggers, however. If there are any BEFORE triggers then trigger.c
- * will have done heap_lock_tuple to lock the correct tuple, so there's no
- * need to do them again.)
- */
-lreplace:;
- if (resultRelationDesc->rd_att->constr)
- ExecConstraints(resultRelInfo, slot, estate);
-
- /*
- * replace the heap tuple
- *
- * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
- * the row to be updated is visible to that snapshot, and throw a can't-
- * serialize error if not. This is a special-case behavior needed for
- * referential integrity updates in serializable transactions.
- */
- result = heap_update(resultRelationDesc, tupleid, tuple,
- &update_ctid, &update_xmax,
- estate->es_output_cid,
- estate->es_crosscheck_snapshot,
- true /* wait for commit */ );
- switch (result)
+ /* INSTEAD OF ROW UPDATE Triggers */
+ if (resultRelInfo->ri_TrigDesc &&
+ resultRelInfo->ri_TrigDesc->trig_update_instead_row)
{
- case HeapTupleSelfUpdated:
- /* already deleted by self; nothing to do */
+ HeapTupleData oldtup;
+
+ Assert(oldtuple != NULL);
+ oldtup.t_data = oldtuple;
+ oldtup.t_len = HeapTupleHeaderGetDatumLength(oldtuple);
+ ItemPointerSetInvalid(&(oldtup.t_self));
+ oldtup.t_tableOid = InvalidOid;
+
+ slot = ExecIRUpdateTriggers(estate, resultRelInfo,
+ &oldtup, slot);
+
+ if (slot == NULL) /* "do nothing" */
return NULL;
- case HeapTupleMayBeUpdated:
- break;
+ /* trigger might have changed tuple */
+ tuple = ExecMaterializeSlot(slot);
+ }
+ else
+ {
+ /*
+ * Check the constraints of the tuple
+ *
+ * If we generate a new candidate tuple after EvalPlanQual testing, we
+ * must loop back here and recheck constraints. (We don't need to
+ * redo triggers, however. If there are any BEFORE triggers then
+ * trigger.c will have done heap_lock_tuple to lock the correct tuple,
+ * so there's no need to do them again.)
+ */
+lreplace:;
+ if (resultRelationDesc->rd_att->constr)
+ ExecConstraints(resultRelInfo, slot, estate);
- case HeapTupleUpdated:
- if (IsXactIsoLevelSerializable)
- ereport(ERROR,
- (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
- errmsg("could not serialize access due to concurrent update")));
- if (!ItemPointerEquals(tupleid, &update_ctid))
- {
- TupleTableSlot *epqslot;
-
- epqslot = EvalPlanQual(estate,
- epqstate,
- resultRelationDesc,
- resultRelInfo->ri_RangeTableIndex,
- &update_ctid,
- update_xmax);
- if (!TupIsNull(epqslot))
+ /*
+ * replace the heap tuple
+ *
+ * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check
+ * that the row to be updated is visible to that snapshot, and throw a
+ * can't-serialize error if not. This is a special-case behavior
+ * needed for referential integrity updates in transaction-snapshot
+ * mode transactions.
+ */
+ result = heap_update(resultRelationDesc, tupleid, tuple,
+ &update_ctid, &update_xmax,
+ estate->es_output_cid,
+ estate->es_crosscheck_snapshot,
+ true /* wait for commit */ );
+ switch (result)
+ {
+ case HeapTupleSelfUpdated:
+ /* already deleted by self; nothing to do */
+ return NULL;
+
+ case HeapTupleMayBeUpdated:
+ break;
+
+ case HeapTupleUpdated:
+ if (IsolationUsesXactSnapshot())
+ ereport(ERROR,
+ (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+ errmsg("could not serialize access due to concurrent update")));
+ if (!ItemPointerEquals(tupleid, &update_ctid))
{
- *tupleid = update_ctid;
- slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
- tuple = ExecMaterializeSlot(slot);
- goto lreplace;
+ TupleTableSlot *epqslot;
+
+ epqslot = EvalPlanQual(estate,
+ epqstate,
+ resultRelationDesc,
+ resultRelInfo->ri_RangeTableIndex,
+ &update_ctid,
+ update_xmax);
+ if (!TupIsNull(epqslot))
+ {
+ *tupleid = update_ctid;
+ slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
+ tuple = ExecMaterializeSlot(slot);
+ goto lreplace;
+ }
}
- }
- /* tuple already deleted; nothing to do */
- return NULL;
+ /* tuple already deleted; nothing to do */
+ return NULL;
- default:
- elog(ERROR, "unrecognized heap_update status: %u", result);
- return NULL;
- }
+ default:
+ elog(ERROR, "unrecognized heap_update status: %u", result);
+ return NULL;
+ }
- (estate->es_processed)++;
+ /*
+ * Note: instead of having to update the old index tuples associated
+ * with the heap tuple, all we do is form and insert new index tuples.
+ * This is because UPDATEs are actually DELETEs and INSERTs, and index
+ * tuple deletion is done later by VACUUM (see notes in ExecDelete).
+ * All we do here is insert new index tuples. -cim 9/27/89
+ */
- /*
- * Note: instead of having to update the old index tuples associated with
- * the heap tuple, all we do is form and insert new index tuples. This is
- * because UPDATEs are actually DELETEs and INSERTs, and index tuple
- * deletion is done later by VACUUM (see notes in ExecDelete). All we do
- * here is insert new index tuples. -cim 9/27/89
- */
+ /*
+ * insert index entries for tuple
+ *
+ * Note: heap_update returns the tid (location) of the new tuple in
+ * the t_self field.
+ *
+ * If it's a HOT update, we mustn't insert new index entries.
+ */
+ if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple))
+ recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+ estate);
+ }
- /*
- * insert index entries for tuple
- *
- * Note: heap_update returns the tid (location) of the new tuple in the
- * t_self field.
- *
- * If it's a HOT update, we mustn't insert new index entries.
- */
- if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple))
- recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
- estate);
+ if (canSetTag)
+ (estate->es_processed)++;
/* AFTER ROW UPDATE Triggers */
ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple,
@@ -592,16 +654,13 @@ fireBSTriggers(ModifyTableState *node)
switch (node->operation)
{
case CMD_INSERT:
- ExecBSInsertTriggers(node->ps.state,
- node->ps.state->es_result_relations);
+ ExecBSInsertTriggers(node->ps.state, node->resultRelInfo);
break;
case CMD_UPDATE:
- ExecBSUpdateTriggers(node->ps.state,
- node->ps.state->es_result_relations);
+ ExecBSUpdateTriggers(node->ps.state, node->resultRelInfo);
break;
case CMD_DELETE:
- ExecBSDeleteTriggers(node->ps.state,
- node->ps.state->es_result_relations);
+ ExecBSDeleteTriggers(node->ps.state, node->resultRelInfo);
break;
default:
elog(ERROR, "unknown operation");
@@ -618,16 +677,13 @@ fireASTriggers(ModifyTableState *node)
switch (node->operation)
{
case CMD_INSERT:
- ExecASInsertTriggers(node->ps.state,
- node->ps.state->es_result_relations);
+ ExecASInsertTriggers(node->ps.state, node->resultRelInfo);
break;
case CMD_UPDATE:
- ExecASUpdateTriggers(node->ps.state,
- node->ps.state->es_result_relations);
+ ExecASUpdateTriggers(node->ps.state, node->resultRelInfo);
break;
case CMD_DELETE:
- ExecASDeleteTriggers(node->ps.state,
- node->ps.state->es_result_relations);
+ ExecASDeleteTriggers(node->ps.state, node->resultRelInfo);
break;
default:
elog(ERROR, "unknown operation");
@@ -648,12 +704,24 @@ ExecModifyTable(ModifyTableState *node)
{
EState *estate = node->ps.state;
CmdType operation = node->operation;
+ ResultRelInfo *saved_resultRelInfo;
+ ResultRelInfo *resultRelInfo;
PlanState *subplanstate;
JunkFilter *junkfilter;
TupleTableSlot *slot;
TupleTableSlot *planSlot;
ItemPointer tupleid = NULL;
ItemPointerData tuple_ctid;
+ HeapTupleHeader oldtuple = NULL;
+
+ /*
+ * If we've already completed processing, don't try to do more. We need
+ * this test because ExecPostprocessPlan might call us an extra time, and
+ * our subplan's nodes aren't necessarily robust against being called
+ * extra times.
+ */
+ if (node->mt_done)
+ return NULL;
/*
* On first call, fire BEFORE STATEMENT triggers before proceeding.
@@ -664,17 +732,21 @@ ExecModifyTable(ModifyTableState *node)
node->fireBSTriggers = false;
}
+ /* Preload local variables */
+ resultRelInfo = node->resultRelInfo + node->mt_whichplan;
+ subplanstate = node->mt_plans[node->mt_whichplan];
+ junkfilter = resultRelInfo->ri_junkFilter;
+
/*
* es_result_relation_info must point to the currently active result
- * relation. (Note we assume that ModifyTable nodes can't be nested.) We
- * want it to be NULL whenever we're not within ModifyTable, though.
+ * relation while we are within this ModifyTable node. Even though
+ * ModifyTable nodes can't be nested statically, they can be nested
+ * dynamically (since our subplan could include a reference to a modifying
+ * CTE). So we have to save and restore the caller's value.
*/
- estate->es_result_relation_info =
- estate->es_result_relations + node->mt_whichplan;
+ saved_resultRelInfo = estate->es_result_relation_info;
- /* Preload local variables */
- subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = estate->es_result_relation_info->ri_junkFilter;
+ estate->es_result_relation_info = resultRelInfo;
/*
* Fetch rows from subplan(s), and execute the required table modification
@@ -682,6 +754,14 @@ ExecModifyTable(ModifyTableState *node)
*/
for (;;)
{
+ /*
+ * Reset the per-output-tuple exprcontext. This is needed because
+ * triggers expect to use that context as workspace. It's a bit ugly
+ * to do this below the top level of the plan, however. We might need
+ * to rethink this later.
+ */
+ ResetPerTupleExprContext(estate);
+
planSlot = ExecProcNode(subplanstate);
if (TupIsNull(planSlot))
@@ -690,10 +770,12 @@ ExecModifyTable(ModifyTableState *node)
node->mt_whichplan++;
if (node->mt_whichplan < node->mt_nplans)
{
- estate->es_result_relation_info++;
+ resultRelInfo++;
subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = estate->es_result_relation_info->ri_junkFilter;
- EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan);
+ junkfilter = resultRelInfo->ri_junkFilter;
+ estate->es_result_relation_info = resultRelInfo;
+ EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan,
+ node->mt_arowmarks[node->mt_whichplan]);
continue;
}
else
@@ -706,22 +788,38 @@ ExecModifyTable(ModifyTableState *node)
if (junkfilter != NULL)
{
/*
- * extract the 'ctid' junk attribute.
+ * extract the 'ctid' or 'wholerow' junk attribute.
*/
if (operation == CMD_UPDATE || operation == CMD_DELETE)
{
Datum datum;
bool isNull;
- datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo,
- &isNull);
- /* shouldn't ever get a null result... */
- if (isNull)
- elog(ERROR, "ctid is NULL");
-
- tupleid = (ItemPointer) DatumGetPointer(datum);
- tuple_ctid = *tupleid; /* be sure we don't free the ctid!! */
- tupleid = &tuple_ctid;
+ if (resultRelInfo->ri_RelationDesc->rd_rel->relkind == RELKIND_RELATION)
+ {
+ datum = ExecGetJunkAttribute(slot,
+ junkfilter->jf_junkAttNo,
+ &isNull);
+ /* shouldn't ever get a null result... */
+ if (isNull)
+ elog(ERROR, "ctid is NULL");
+
+ tupleid = (ItemPointer) DatumGetPointer(datum);
+ tuple_ctid = *tupleid; /* be sure we don't free
+ * ctid!! */
+ tupleid = &tuple_ctid;
+ }
+ else
+ {
+ datum = ExecGetJunkAttribute(slot,
+ junkfilter->jf_junkAttNo,
+ &isNull);
+ /* shouldn't ever get a null result... */
+ if (isNull)
+ elog(ERROR, "wholerow is NULL");
+
+ oldtuple = DatumGetHeapTupleHeader(datum);
+ }
}
/*
@@ -734,15 +832,15 @@ ExecModifyTable(ModifyTableState *node)
switch (operation)
{
case CMD_INSERT:
- slot = ExecInsert(slot, planSlot, estate);
+ slot = ExecInsert(slot, planSlot, estate, node->canSetTag);
break;
case CMD_UPDATE:
- slot = ExecUpdate(tupleid, slot, planSlot,
- &node->mt_epqstate, estate);
+ slot = ExecUpdate(tupleid, oldtuple, slot, planSlot,
+ &node->mt_epqstate, estate, node->canSetTag);
break;
case CMD_DELETE:
- slot = ExecDelete(tupleid, planSlot,
- &node->mt_epqstate, estate);
+ slot = ExecDelete(tupleid, oldtuple, planSlot,
+ &node->mt_epqstate, estate, node->canSetTag);
break;
default:
elog(ERROR, "unknown operation");
@@ -755,19 +853,21 @@ ExecModifyTable(ModifyTableState *node)
*/
if (slot)
{
- estate->es_result_relation_info = NULL;
+ estate->es_result_relation_info = saved_resultRelInfo;
return slot;
}
}
- /* Reset es_result_relation_info before exiting */
- estate->es_result_relation_info = NULL;
+ /* Restore es_result_relation_info before exiting */
+ estate->es_result_relation_info = saved_resultRelInfo;
/*
* We're done, but fire AFTER STATEMENT triggers before exiting.
*/
fireASTriggers(node);
+ node->mt_done = true;
+
return NULL;
}
@@ -781,6 +881,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ModifyTableState *mtstate;
CmdType operation = node->operation;
int nplans = list_length(node->plans);
+ ResultRelInfo *saved_resultRelInfo;
ResultRelInfo *resultRelInfo;
TupleDesc tupDesc;
Plan *subplan;
@@ -806,36 +907,59 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->ps.state = estate;
mtstate->ps.targetlist = NIL; /* not actually used */
+ mtstate->operation = operation;
+ mtstate->canSetTag = node->canSetTag;
+ mtstate->mt_done = false;
+
mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
+ mtstate->resultRelInfo = estate->es_result_relations + node->resultRelIndex;
+ mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
mtstate->mt_nplans = nplans;
- mtstate->operation = operation;
- /* set up epqstate with dummy subplan pointer for the moment */
- EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, node->epqParam);
- mtstate->fireBSTriggers = true;
- /* For the moment, assume our targets are exactly the global result rels */
+ /* set up epqstate with dummy subplan data for the moment */
+ EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, NIL, node->epqParam);
+ mtstate->fireBSTriggers = true;
/*
* call ExecInitNode on each of the plans to be executed and save the
- * results into the array "mt_plans". Note we *must* set
+ * results into the array "mt_plans". This is also a convenient place to
+ * verify that the proposed target relations are valid and open their
+ * indexes for insertion of new index entries. Note we *must* set
* estate->es_result_relation_info correctly while we initialize each
* sub-plan; ExecContextForcesOids depends on that!
*/
- estate->es_result_relation_info = estate->es_result_relations;
+ saved_resultRelInfo = estate->es_result_relation_info;
+
+ resultRelInfo = mtstate->resultRelInfo;
i = 0;
foreach(l, node->plans)
{
subplan = (Plan *) lfirst(l);
+
+ /*
+ * Verify result relation is a valid target for the current operation
+ */
+ CheckValidResultRel(resultRelInfo->ri_RelationDesc, operation);
+
+ /*
+ * If there are indices on the result relation, open them and save
+ * descriptors in the result relation info, so that we can add new
+ * index entries for the tuples we add/update. We need not do this
+ * for a DELETE, however, since deletion doesn't affect indexes.
+ */
+ if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+ operation != CMD_DELETE)
+ ExecOpenIndices(resultRelInfo);
+
+ /* Now init the plan for this result rel */
+ estate->es_result_relation_info = resultRelInfo;
mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
- estate->es_result_relation_info++;
+
+ resultRelInfo++;
i++;
}
- estate->es_result_relation_info = NULL;
- /* select first subplan */
- mtstate->mt_whichplan = 0;
- subplan = (Plan *) linitial(node->plans);
- EvalPlanQualSetPlan(&mtstate->mt_epqstate, subplan);
+ estate->es_result_relation_info = saved_resultRelInfo;
/*
* Initialize RETURNING projections if needed.
@@ -864,8 +988,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
/*
* Build a projection for each result rel.
*/
- Assert(list_length(node->returningLists) == estate->es_num_result_relations);
- resultRelInfo = estate->es_result_relations;
+ resultRelInfo = mtstate->resultRelInfo;
foreach(l, node->returningLists)
{
List *rlist = (List *) lfirst(l);
@@ -900,8 +1023,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
foreach(l, node->rowMarks)
{
PlanRowMark *rc = (PlanRowMark *) lfirst(l);
- ExecRowMark *erm = NULL;
- ListCell *lce;
+ ExecRowMark *erm;
Assert(IsA(rc, PlanRowMark));
@@ -909,25 +1031,31 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
if (rc->isParent)
continue;
- foreach(lce, estate->es_rowMarks)
+ /* find ExecRowMark (same for all subplans) */
+ erm = ExecFindRowMark(estate, rc->rti);
+
+ /* build ExecAuxRowMark for each subplan */
+ for (i = 0; i < nplans; i++)
{
- erm = (ExecRowMark *) lfirst(lce);
- if (erm->rti == rc->rti)
- break;
- erm = NULL;
- }
- if (erm == NULL)
- elog(ERROR, "failed to find ExecRowMark for PlanRowMark %u",
- rc->rti);
+ ExecAuxRowMark *aerm;
- EvalPlanQualAddRowMark(&mtstate->mt_epqstate, erm);
+ subplan = mtstate->mt_plans[i]->plan;
+ aerm = ExecBuildAuxRowMark(erm, subplan->targetlist);
+ mtstate->mt_arowmarks[i] = lappend(mtstate->mt_arowmarks[i], aerm);
+ }
}
+ /* select first subplan */
+ mtstate->mt_whichplan = 0;
+ subplan = (Plan *) linitial(node->plans);
+ EvalPlanQualSetPlan(&mtstate->mt_epqstate, subplan,
+ mtstate->mt_arowmarks[0]);
+
/*
* Initialize the junk filter(s) if needed. INSERT queries need a filter
* if there are any junk attrs in the tlist. UPDATE and DELETE always
- * need a filter, since there's always a junk 'ctid' attribute present ---
- * no need to look first.
+ * need a filter, since there's always a junk 'ctid' or 'wholerow'
+ * attribute present --- no need to look first.
*
* If there are multiple result relations, each one needs its own junk
* filter. Note multiple rels are only possible for UPDATE/DELETE, so we
@@ -964,7 +1092,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
if (junk_filter_needed)
{
- resultRelInfo = estate->es_result_relations;
+ resultRelInfo = mtstate->resultRelInfo;
for (i = 0; i < nplans; i++)
{
JunkFilter *j;
@@ -980,10 +1108,19 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
if (operation == CMD_UPDATE || operation == CMD_DELETE)
{
- /* For UPDATE/DELETE, find the ctid junk attr now */
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
- elog(ERROR, "could not find junk ctid column");
+ /* For UPDATE/DELETE, find the appropriate junk attr now */
+ if (resultRelInfo->ri_RelationDesc->rd_rel->relkind == RELKIND_RELATION)
+ {
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
+ if (!AttributeNumberIsValid(j->jf_junkAttNo))
+ elog(ERROR, "could not find junk ctid column");
+ }
+ else
+ {
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
+ if (!AttributeNumberIsValid(j->jf_junkAttNo))
+ elog(ERROR, "could not find junk wholerow column");
+ }
}
resultRelInfo->ri_junkFilter = j;
@@ -993,7 +1130,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
else
{
if (operation == CMD_INSERT)
- ExecCheckPlanOutput(estate->es_result_relations->ri_RelationDesc,
+ ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
subplan->targetlist);
}
}
@@ -1006,6 +1143,19 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
if (estate->es_trig_tuple_slot == NULL)
estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate);
+ /*
+ * Lastly, if this is not the primary (canSetTag) ModifyTable node, add it
+ * to estate->es_auxmodifytables so that it will be run to completion by
+ * ExecPostprocessPlan. (It'd actually work fine to add the primary
+ * ModifyTable node too, but there's no need.) Note the use of lcons not
+ * lappend: we need later-initialized ModifyTable nodes to be shut down
+ * before earlier ones. This ensures that we don't throw away RETURNING
+ * rows that need to be seen by a later CTE subplan.
+ */
+ if (!mtstate->canSetTag)
+ estate->es_auxmodifytables = lcons(mtstate,
+ estate->es_auxmodifytables);
+
return mtstate;
}
@@ -1045,7 +1195,7 @@ ExecEndModifyTable(ModifyTableState *node)
}
void
-ExecReScanModifyTable(ModifyTableState *node, ExprContext *exprCtxt)
+ExecReScanModifyTable(ModifyTableState *node)
{
/*
* Currently, we don't need to support rescan on ModifyTable nodes. The