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