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);