aboutsummaryrefslogtreecommitdiff
path: root/src/backend/executor/nodeModifyTable.c
diff options
context:
space:
mode:
authorNoah Misch <noah@leadboat.com>2014-03-23 02:16:34 -0400
committerNoah Misch <noah@leadboat.com>2014-03-23 02:16:34 -0400
commit7cbe57c34dec4860243e6d0f81738cfbb6e5d069 (patch)
tree1b2e725b85caef56f986db8ae7c43732819c1f5c /src/backend/executor/nodeModifyTable.c
parent6115480c543c0141011a99db78987ad13540be59 (diff)
downloadpostgresql-7cbe57c34dec4860243e6d0f81738cfbb6e5d069.tar.gz
postgresql-7cbe57c34dec4860243e6d0f81738cfbb6e5d069.zip
Offer triggers on foreign tables.
This covers all the SQL-standard trigger types supported for regular tables; it does not cover constraint triggers. The approach for acquiring the old row mirrors that for view INSTEAD OF triggers. For AFTER ROW triggers, we spool the foreign tuples to a tuplestore. This changes the FDW API contract; when deciding which columns to populate in the slot returned from data modification callbacks, writable FDWs will need to check for AFTER ROW triggers in addition to checking for a RETURNING clause. In support of the feature addition, refactor the TriggerFlags bits and the assembly of old tuples in ModifyTable. Ronan Dunklau, reviewed by KaiGai Kohei; some additional hacking by me.
Diffstat (limited to 'src/backend/executor/nodeModifyTable.c')
-rw-r--r--src/backend/executor/nodeModifyTable.c90
1 files changed, 52 insertions, 38 deletions
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 6f0f47e7ce3..fca7a2581f3 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -309,15 +309,17 @@ ExecInsert(TupleTableSlot *slot,
* 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, both tupleid and oldtuple are NULL; the FDW has
- * to figure out which row to delete using data from the planSlot.
+ * 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.
*
* Returns RETURNING result if any, otherwise NULL.
* ----------------------------------------------------------------
*/
static TupleTableSlot *
ExecDelete(ItemPointer tupleid,
- HeapTupleHeader oldtuple,
+ HeapTuple oldtuple,
TupleTableSlot *planSlot,
EPQState *epqstate,
EState *estate,
@@ -342,7 +344,7 @@ ExecDelete(ItemPointer tupleid,
bool dodelete;
dodelete = ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
- tupleid);
+ tupleid, oldtuple);
if (!dodelete) /* "do nothing" */
return NULL;
@@ -352,16 +354,10 @@ ExecDelete(ItemPointer tupleid,
if (resultRelInfo->ri_TrigDesc &&
resultRelInfo->ri_TrigDesc->trig_delete_instead_row)
{
- 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);
+ dodelete = ExecIRDeleteTriggers(estate, resultRelInfo, oldtuple);
if (!dodelete) /* "do nothing" */
return NULL;
@@ -488,7 +484,7 @@ ldelete:;
(estate->es_processed)++;
/* AFTER ROW DELETE Triggers */
- ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
+ ExecARDeleteTriggers(estate, resultRelInfo, tupleid, oldtuple);
/* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning)
@@ -512,10 +508,7 @@ ldelete:;
slot = estate->es_trig_tuple_slot;
if (oldtuple != NULL)
{
- deltuple.t_data = oldtuple;
- deltuple.t_len = HeapTupleHeaderGetDatumLength(oldtuple);
- ItemPointerSetInvalid(&(deltuple.t_self));
- deltuple.t_tableOid = InvalidOid;
+ deltuple = *oldtuple;
delbuffer = InvalidBuffer;
}
else
@@ -564,15 +557,17 @@ ldelete:;
* 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,
- * both tupleid and oldtuple are NULL; the FDW has to figure out
- * which row to update using data from the planSlot.
+ * 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.
*
* Returns RETURNING result if any, otherwise NULL.
* ----------------------------------------------------------------
*/
static TupleTableSlot *
ExecUpdate(ItemPointer tupleid,
- HeapTupleHeader oldtuple,
+ HeapTuple oldtuple,
TupleTableSlot *slot,
TupleTableSlot *planSlot,
EPQState *epqstate,
@@ -609,7 +604,7 @@ ExecUpdate(ItemPointer tupleid,
resultRelInfo->ri_TrigDesc->trig_update_before_row)
{
slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
- tupleid, slot);
+ tupleid, oldtuple, slot);
if (slot == NULL) /* "do nothing" */
return NULL;
@@ -622,16 +617,8 @@ ExecUpdate(ItemPointer tupleid,
if (resultRelInfo->ri_TrigDesc &&
resultRelInfo->ri_TrigDesc->trig_update_instead_row)
{
- 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);
+ oldtuple, slot);
if (slot == NULL) /* "do nothing" */
return NULL;
@@ -788,7 +775,7 @@ lreplace:;
(estate->es_processed)++;
/* AFTER ROW UPDATE Triggers */
- ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple,
+ ExecARUpdateTriggers(estate, resultRelInfo, tupleid, oldtuple, tuple,
recheckIndexes);
list_free(recheckIndexes);
@@ -873,7 +860,8 @@ ExecModifyTable(ModifyTableState *node)
TupleTableSlot *planSlot;
ItemPointer tupleid = NULL;
ItemPointerData tuple_ctid;
- HeapTupleHeader oldtuple = NULL;
+ HeapTupleData oldtupdata;
+ HeapTuple oldtuple;
/*
* This should NOT get called during EvalPlanQual; we should have passed a
@@ -958,6 +946,7 @@ ExecModifyTable(ModifyTableState *node)
EvalPlanQualSetSlot(&node->mt_epqstate, planSlot);
slot = planSlot;
+ oldtuple = NULL;
if (junkfilter != NULL)
{
/*
@@ -984,11 +973,21 @@ ExecModifyTable(ModifyTableState *node)
* ctid!! */
tupleid = &tuple_ctid;
}
- else if (relkind == RELKIND_FOREIGN_TABLE)
- {
- /* do nothing; FDW must fetch any junk attrs it wants */
- }
- else
+ /*
+ * Use the wholerow attribute, when available, to reconstruct
+ * the old relation tuple.
+ *
+ * Foreign table updates have a wholerow attribute when the
+ * relation has an AFTER ROW trigger. Note that the wholerow
+ * attribute does not carry system columns. Foreign table
+ * triggers miss seeing those, except that we 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.
+ */
+ else if (AttributeNumberIsValid(junkfilter->jf_junkAttNo))
{
datum = ExecGetJunkAttribute(slot,
junkfilter->jf_junkAttNo,
@@ -997,8 +996,19 @@ ExecModifyTable(ModifyTableState *node)
if (isNull)
elog(ERROR, "wholerow is NULL");
- oldtuple = DatumGetHeapTupleHeader(datum);
+ oldtupdata.t_data = DatumGetHeapTupleHeader(datum);
+ oldtupdata.t_len =
+ HeapTupleHeaderGetDatumLength(oldtupdata.t_data);
+ ItemPointerSetInvalid(&(oldtupdata.t_self));
+ /* Historically, view triggers see invalid t_tableOid. */
+ oldtupdata.t_tableOid =
+ (relkind == RELKIND_VIEW) ? InvalidOid :
+ RelationGetRelid(resultRelInfo->ri_RelationDesc);
+
+ oldtuple = &oldtupdata;
}
+ else
+ Assert(relkind == RELKIND_FOREIGN_TABLE);
}
/*
@@ -1334,7 +1344,11 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
}
else if (relkind == RELKIND_FOREIGN_TABLE)
{
- /* FDW must fetch any junk attrs it wants */
+ /*
+ * When there is an AFTER trigger, there should be a
+ * wholerow attribute.
+ */
+ j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
}
else
{