after-triggers-1.patch
application/octet-stream
Filename: after-triggers-1.patch
Type: application/octet-stream
Part: 0
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
new file mode 100644
index 4c31f19..836e626
*** a/src/backend/commands/trigger.c
--- b/src/backend/commands/trigger.c
*************** typedef SetConstraintStateData *SetConst
*** 2852,2864 ****
* Per-trigger-event data
*
* The actual per-event data, AfterTriggerEventData, includes DONE/IN_PROGRESS
! * status bits and one or two tuple CTIDs. Each event record also has an
! * associated AfterTriggerSharedData that is shared across all instances
! * of similar events within a "chunk".
! *
! * We arrange not to waste storage on ate_ctid2 for non-update events.
! * We could go further and not store either ctid for statement-level triggers,
! * but that seems unlikely to be worth the trouble.
*
* Note: ats_firing_id is initially zero and is set to something else when
* AFTER_TRIGGER_IN_PROGRESS is set. It indicates which trigger firing
--- 2852,2865 ----
* Per-trigger-event data
*
* The actual per-event data, AfterTriggerEventData, includes DONE/IN_PROGRESS
! * status bits and a CTID representing the old tuple for UPDATE and DELETE
! * events and the new tuple for INSERT events. In the UPDATE case, the new
! * tuple is obtained from the old tuple when the trigger is fired by following
! * its t_ctid link. This allows us to save space in the queue by only saving
! * one CTID per event. We could save further space by not storing a CTID at
! * all for statement-level triggers, but that seems unlikely to be worth the
! * trouble. Each event record also has an associated AfterTriggerSharedData
! * that is shared across all instances of similar events within a "chunk".
*
* Note: ats_firing_id is initially zero and is set to something else when
* AFTER_TRIGGER_IN_PROGRESS is set. It indicates which trigger firing
*************** typedef uint32 TriggerFlags;
*** 2873,2879 ****
#define AFTER_TRIGGER_OFFSET 0x0FFFFFFF /* must be low-order
* bits */
- #define AFTER_TRIGGER_2CTIDS 0x10000000
#define AFTER_TRIGGER_DONE 0x20000000
#define AFTER_TRIGGER_IN_PROGRESS 0x40000000
--- 2874,2879 ----
*************** typedef struct AfterTriggerEventData *Af
*** 2892,2911 ****
typedef struct AfterTriggerEventData
{
TriggerFlags ate_flags; /* status bits and offset to shared data */
! ItemPointerData ate_ctid1; /* inserted, deleted, or old updated tuple */
! ItemPointerData ate_ctid2; /* new updated tuple */
} AfterTriggerEventData;
! /* This struct must exactly match the one above except for not having ctid2 */
! typedef struct AfterTriggerEventDataOneCtid
! {
! TriggerFlags ate_flags; /* status bits and offset to shared data */
! ItemPointerData ate_ctid1; /* inserted, deleted, or old updated tuple */
! } AfterTriggerEventDataOneCtid;
!
! #define SizeofTriggerEvent(evt) \
! (((evt)->ate_flags & AFTER_TRIGGER_2CTIDS) ? \
! sizeof(AfterTriggerEventData) : sizeof(AfterTriggerEventDataOneCtid))
#define GetTriggerSharedData(evt) \
((AfterTriggerShared) ((char *) (evt) + ((evt)->ate_flags & AFTER_TRIGGER_OFFSET)))
--- 2892,2901 ----
typedef struct AfterTriggerEventData
{
TriggerFlags ate_flags; /* status bits and offset to shared data */
! ItemPointerData ate_ctid; /* inserted, deleted, or old updated tuple */
} AfterTriggerEventData;
! #define SizeofTriggerEvent(evt) sizeof(AfterTriggerEventData)
#define GetTriggerSharedData(evt) \
((AfterTriggerShared) ((char *) (evt) + ((evt)->ate_flags & AFTER_TRIGGER_OFFSET)))
*************** AfterTriggerExecute(AfterTriggerEvent ev
*** 3285,3290 ****
--- 3275,3281 ----
{
AfterTriggerShared evtshared = GetTriggerSharedData(event);
Oid tgoid = evtshared->ats_tgoid;
+ ItemPointer new_ctid = NULL;
TriggerData LocTriggerData;
HeapTupleData tuple1;
HeapTupleData tuple2;
*************** AfterTriggerExecute(AfterTriggerEvent ev
*** 3318,3330 ****
/*
* Fetch the required tuple(s).
*/
! if (ItemPointerIsValid(&(event->ate_ctid1)))
{
! ItemPointerCopy(&(event->ate_ctid1), &(tuple1.t_self));
if (!heap_fetch(rel, SnapshotAny, &tuple1, &buffer1, false, NULL))
elog(ERROR, "failed to fetch tuple1 for AFTER trigger");
LocTriggerData.tg_trigtuple = &tuple1;
LocTriggerData.tg_trigtuplebuf = buffer1;
}
else
{
--- 3309,3331 ----
/*
* Fetch the required tuple(s).
*/
! if (ItemPointerIsValid(&(event->ate_ctid)))
{
! ItemPointerCopy(&(event->ate_ctid), &(tuple1.t_self));
if (!heap_fetch(rel, SnapshotAny, &tuple1, &buffer1, false, NULL))
elog(ERROR, "failed to fetch tuple1 for AFTER trigger");
LocTriggerData.tg_trigtuple = &tuple1;
LocTriggerData.tg_trigtuplebuf = buffer1;
+
+ /*
+ * For an UPDATE the old tuple points to the new tuple. This link should
+ * have been set on the old tuple just before the event was queued.
+ */
+ if (TRIGGER_FIRED_BY_UPDATE(evtshared->ats_event))
+ {
+ new_ctid = &(tuple1.t_data->t_ctid);
+ Assert(ItemPointerIsValid(new_ctid));
+ }
}
else
{
*************** AfterTriggerExecute(AfterTriggerEvent ev
*** 3332,3346 ****
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
}
! /* don't touch ctid2 if not there */
! if ((event->ate_flags & AFTER_TRIGGER_2CTIDS) &&
! ItemPointerIsValid(&(event->ate_ctid2)))
{
! ItemPointerCopy(&(event->ate_ctid2), &(tuple2.t_self));
if (!heap_fetch(rel, SnapshotAny, &tuple2, &buffer2, false, NULL))
elog(ERROR, "failed to fetch tuple2 for AFTER trigger");
LocTriggerData.tg_newtuple = &tuple2;
LocTriggerData.tg_newtuplebuf = buffer2;
}
else
{
--- 3333,3360 ----
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
}
! if (ItemPointerIsValid(new_ctid))
{
! ItemPointerCopy(new_ctid, &(tuple2.t_self));
if (!heap_fetch(rel, SnapshotAny, &tuple2, &buffer2, false, NULL))
elog(ERROR, "failed to fetch tuple2 for AFTER trigger");
LocTriggerData.tg_newtuple = &tuple2;
LocTriggerData.tg_newtuplebuf = buffer2;
+
+ /*
+ * Sanity check: make sure that the (sub)transaction and command ID of
+ * the new tuple match those on the old tuple. This should always be
+ * the case if we have the correct new tuple (updated when the event
+ * was first queued). This could go wrong if the t_ctid link has been
+ * overwritten or the chain collapsed, which should be disallowed at
+ * least until this transaction is committed, but we check just to be
+ * sure.
+ */
+ if (HeapTupleHeaderGetXmin(tuple2.t_data) !=
+ HeapTupleHeaderGetXmax(tuple1.t_data) ||
+ HeapTupleHeaderGetCmin(tuple2.t_data) !=
+ HeapTupleHeaderGetCmax(tuple1.t_data))
+ elog(ERROR, "incorrect xmin/cmin found on tuple2 in AFTER trigger");
}
else
{
*************** AfterTriggerSaveEvent(EState *estate, Re
*** 4485,4499 ****
{
Assert(oldtup == NULL);
Assert(newtup != NULL);
! ItemPointerCopy(&(newtup->t_self), &(new_event.ate_ctid1));
! ItemPointerSetInvalid(&(new_event.ate_ctid2));
}
else
{
Assert(oldtup == NULL);
Assert(newtup == NULL);
! ItemPointerSetInvalid(&(new_event.ate_ctid1));
! ItemPointerSetInvalid(&(new_event.ate_ctid2));
}
break;
case TRIGGER_EVENT_DELETE:
--- 4499,4511 ----
{
Assert(oldtup == NULL);
Assert(newtup != NULL);
! ItemPointerCopy(&(newtup->t_self), &(new_event.ate_ctid));
}
else
{
Assert(oldtup == NULL);
Assert(newtup == NULL);
! ItemPointerSetInvalid(&(new_event.ate_ctid));
}
break;
case TRIGGER_EVENT_DELETE:
*************** AfterTriggerSaveEvent(EState *estate, Re
*** 4502,4516 ****
{
Assert(oldtup != NULL);
Assert(newtup == NULL);
! ItemPointerCopy(&(oldtup->t_self), &(new_event.ate_ctid1));
! ItemPointerSetInvalid(&(new_event.ate_ctid2));
}
else
{
Assert(oldtup == NULL);
Assert(newtup == NULL);
! ItemPointerSetInvalid(&(new_event.ate_ctid1));
! ItemPointerSetInvalid(&(new_event.ate_ctid2));
}
break;
case TRIGGER_EVENT_UPDATE:
--- 4514,4526 ----
{
Assert(oldtup != NULL);
Assert(newtup == NULL);
! ItemPointerCopy(&(oldtup->t_self), &(new_event.ate_ctid));
}
else
{
Assert(oldtup == NULL);
Assert(newtup == NULL);
! ItemPointerSetInvalid(&(new_event.ate_ctid));
}
break;
case TRIGGER_EVENT_UPDATE:
*************** AfterTriggerSaveEvent(EState *estate, Re
*** 4519,4542 ****
{
Assert(oldtup != NULL);
Assert(newtup != NULL);
! ItemPointerCopy(&(oldtup->t_self), &(new_event.ate_ctid1));
! ItemPointerCopy(&(newtup->t_self), &(new_event.ate_ctid2));
! new_event.ate_flags |= AFTER_TRIGGER_2CTIDS;
}
else
{
Assert(oldtup == NULL);
Assert(newtup == NULL);
! ItemPointerSetInvalid(&(new_event.ate_ctid1));
! ItemPointerSetInvalid(&(new_event.ate_ctid2));
}
break;
case TRIGGER_EVENT_TRUNCATE:
tgtype_event = TRIGGER_TYPE_TRUNCATE;
Assert(oldtup == NULL);
Assert(newtup == NULL);
! ItemPointerSetInvalid(&(new_event.ate_ctid1));
! ItemPointerSetInvalid(&(new_event.ate_ctid2));
break;
default:
elog(ERROR, "invalid after-trigger event code: %d", event);
--- 4529,4548 ----
{
Assert(oldtup != NULL);
Assert(newtup != NULL);
! ItemPointerCopy(&(oldtup->t_self), &(new_event.ate_ctid));
}
else
{
Assert(oldtup == NULL);
Assert(newtup == NULL);
! ItemPointerSetInvalid(&(new_event.ate_ctid));
}
break;
case TRIGGER_EVENT_TRUNCATE:
tgtype_event = TRIGGER_TYPE_TRUNCATE;
Assert(oldtup == NULL);
Assert(newtup == NULL);
! ItemPointerSetInvalid(&(new_event.ate_ctid));
break;
default:
elog(ERROR, "invalid after-trigger event code: %d", event);