*** a/doc/src/sgml/ref/create_trigger.sgml
--- b/doc/src/sgml/ref/create_trigger.sgml
***************
*** 25,30 **** CREATE [ CONSTRAINT ] TRIGGER <replaceable class="PARAMETER">name</replaceable>
--- 25,31 ----
      ON <replaceable class="PARAMETER">table_name</replaceable>
      [ FROM <replaceable class="parameter">referenced_table_name</replaceable> ]
      [ NOT DEFERRABLE | [ DEFERRABLE ] { INITIALLY IMMEDIATE | INITIALLY DEFERRED } ]
+     [ REFERENCING { { OLD | NEW } TABLE [ AS ] <replaceable class="PARAMETER">transition_relation_name</replaceable> } [ ... ] ]
      [ FOR [ EACH ] { ROW | STATEMENT } ]
      [ WHEN ( <replaceable class="parameter">condition</replaceable> ) ]
      EXECUTE PROCEDURE <replaceable class="PARAMETER">function_name</replaceable> ( <replaceable class="PARAMETER">arguments</replaceable> )
***************
*** 175,180 **** CREATE [ CONSTRAINT ] TRIGGER <replaceable class="PARAMETER">name</replaceable>
--- 176,190 ----
    </para>
  
    <para>
+    The <literal>REFERENCING</> option is only allowed for an <literal>AFTER</>
+    trigger which is not a constraint trigger.  <literal>OLD TABLE</> may only
+    be specified once, and only on a trigger which can fire on
+    <literal>UPDATE</> or <literal>DELETE</>.  <literal>NEW TABLE</> may only
+    be specified once, and only on a trigger which can fire on
+    <literal>UPDATE</> or <literal>INSERT</>.
+   </para>
+ 
+   <para>
     <command>SELECT</command> does not modify any rows so you cannot
     create <command>SELECT</command> triggers. Rules and views are more
     appropriate in such cases.
***************
*** 279,284 **** UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</
--- 289,328 ----
     </varlistentry>
  
     <varlistentry>
+     <term><literal>REFERENCING</literal></term>
+     <listitem>
+      <para>
+       This immediately preceeds the declaration of one or two relations which
+       can be used to read the before and/or after images of all rows directly
+       affected by the triggering statement.  An <literal>AFTER EACH ROW</>
+       trigger is allowed to use both these transition relation names and the
+       row names (<literal>OLD</> and <literal>NEW</>) which reference each
+       individual row for which the trigger fires.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><literal>OLD TABLE</literal></term>
+     <term><literal>NEW TABLE</literal></term>
+     <listitem>
+      <para>
+       This specifies whether the named relation contains the before or after
+       images for rows affected by the statement which fired the triggered.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><replaceable class="PARAMETER">transition_relation_name</replaceable></term>
+     <listitem>
+      <para>
+       The (unqualified) name to be used within the trigger for this relation.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><literal>FOR EACH ROW</literal></term>
      <term><literal>FOR EACH STATEMENT</literal></term>
  
***************
*** 471,476 **** CREATE TRIGGER view_insert
--- 515,544 ----
      FOR EACH ROW
      EXECUTE PROCEDURE view_insert_row();
  </programlisting>
+ 
+    Execute the function <function>check_transfer_balances_to_zero</> for each
+    statement to confirm that the <literal>transfer</> rows offset to a net of
+    zero:
+ 
+ <programlisting>
+ CREATE TRIGGER transfer_insert
+     AFTER INSERT ON transfer
+     FOR EACH STATEMENT
+     REFERENCING NEW TABLE AS inserted
+     EXECUTE PROCEDURE check_transfer_balances_to_zero();
+ </programlisting>
+ 
+    Execute the function <function>check_matching_pairs</> for each row to
+    confirm that changes are made to matching pairs at the same time (by the
+    same statement):
+ 
+ <programlisting>
+ CREATE TRIGGER paired_items_update
+     AFTER UPDATE ON paired_items
+     FOR EACH ROW
+     REFERENCING NEW TABLE AS newtab OLD TABLE AS oldtab
+     EXECUTE PROCEDURE check_matching_pairs();
+ </programlisting>
    </para>
  
    <para>
***************
*** 499,522 **** CREATE TRIGGER view_insert
     <itemizedlist>
      <listitem>
       <para>
!       SQL allows you to define aliases for the <quote>old</quote>
!       and <quote>new</quote> rows or tables for use in the definition
!       of the triggered action (e.g., <literal>CREATE TRIGGER ... ON
!       tablename REFERENCING OLD ROW AS somename NEW ROW AS othername
!       ...</literal>).  Since <productname>PostgreSQL</productname>
!       allows trigger procedures to be written in any number of
!       user-defined languages, access to the data is handled in a
!       language-specific way.
!      </para>
!     </listitem>
! 
!     <listitem>
!      <para>
!       <productname>PostgreSQL</productname> does not allow the old and new
!       tables to be referenced in statement-level triggers, i.e., the tables
!       that contain all the old and/or new rows, which are referred to by the
!       <literal>OLD TABLE</literal> and <literal>NEW TABLE</literal> clauses in
!       the <acronym>SQL</> standard.
       </para>
      </listitem>
  
--- 567,580 ----
     <itemizedlist>
      <listitem>
       <para>
!       While transition tables for <literal>AFTER</> triggers are specified
!       using the <literal>REFERENCING</> clause in the standard way, the row
!       variables used in <literal>FOR EACH ROW</> triggers may not be
!       specified in <literal>REFERENCING</> clause.  They are available in a
!       manner which is dependent on the language in which the trigger function
!       is written.  Some languages effectively behave as though there is a
!       <literal>REFERENCING</> clause containing <literal>OLD ROW AS OLD NEW
!       ROW AS NEW</>.
       </para>
      </listitem>
  
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 7020,7026 **** validateForeignKeyConstraint(char *conname,
  	trig.tgconstraint = constraintOid;
  	trig.tgdeferrable = FALSE;
  	trig.tginitdeferred = FALSE;
! 	/* we needn't fill in tgargs or tgqual */
  
  	/*
  	 * See if we can do it with a single LEFT JOIN query.  A FALSE result
--- 7020,7026 ----
  	trig.tgconstraint = constraintOid;
  	trig.tgdeferrable = FALSE;
  	trig.tginitdeferred = FALSE;
! 	/* we needn't fill in remaining fields */
  
  	/*
  	 * See if we can do it with a single LEFT JOIN query.  A FALSE result
***************
*** 7104,7109 **** CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
--- 7104,7110 ----
  	}
  
  	fk_trigger->columns = NIL;
+ 	fk_trigger->transitionRels = NIL;
  	fk_trigger->whenClause = NULL;
  	fk_trigger->isconstraint = true;
  	fk_trigger->deferrable = fkconstraint->deferrable;
***************
*** 7144,7149 **** createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint,
--- 7145,7151 ----
  	fk_trigger->timing = TRIGGER_TYPE_AFTER;
  	fk_trigger->events = TRIGGER_TYPE_DELETE;
  	fk_trigger->columns = NIL;
+ 	fk_trigger->transitionRels = NIL;
  	fk_trigger->whenClause = NULL;
  	fk_trigger->isconstraint = true;
  	fk_trigger->constrrel = NULL;
***************
*** 7198,7203 **** createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint,
--- 7200,7206 ----
  	fk_trigger->timing = TRIGGER_TYPE_AFTER;
  	fk_trigger->events = TRIGGER_TYPE_UPDATE;
  	fk_trigger->columns = NIL;
+ 	fk_trigger->transitionRels = NIL;
  	fk_trigger->whenClause = NULL;
  	fk_trigger->isconstraint = true;
  	fk_trigger->constrrel = NULL;
*** a/src/backend/commands/trigger.c
--- b/src/backend/commands/trigger.c
***************
*** 155,160 **** CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
--- 155,162 ----
  	Oid			constrrelid = InvalidOid;
  	ObjectAddress myself,
  				referenced;
+ 	char	   *oldtablename = NULL;
+ 	char	   *newtablename = NULL;
  
  	if (OidIsValid(relOid))
  		rel = heap_open(relOid, AccessExclusiveLock);
***************
*** 301,306 **** CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
--- 303,383 ----
  	}
  
  	/*
+ 	 * We don't yet support naming ROW transition variables, but the parser
+ 	 * recognizes the syntax so we can give a nicer message here.
+ 	 *
+ 	 * Per standard, REFERENCING TABLE names are only allowed on AFTER
+ 	 * triggers.  Per standard, REFERENCING ROW names are not allowed with FOR
+ 	 * EACH STATEMENT.  Per standard, each OLD/NEW, ROW/TABLE permutation is
+ 	 * only allowed once.  Per standard, OLD may not be specified when
+ 	 * creating a trigger only for INSERT, and NEW may not be specified when
+ 	 * creating a trigger only for DELETE.
+ 	 *
+ 	 * Notice that the standard allows an AFTER ... FOR EACH ROW trigger to
+ 	 * reference both ROW and TABLE transition data.
+ 	 */
+ 	if (stmt->transitionRels != NIL)
+ 	{
+ 		List	   *varList = stmt->transitionRels;
+ 		ListCell   *lc;
+ 
+ 		foreach(lc, varList)
+ 		{
+ 			TriggerTransition   *tt = (TriggerTransition *) lfirst(lc);
+ 
+ 			Assert(IsA(tt, TriggerTransition));
+ 
+ 			if (!(tt->isTable))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 						 errmsg("ROW variable naming in the REFERENCING clause is not supported"),
+ 						 errhint("Use OLD TABLE or NEW TABLE for naming transition tables.")));
+ 
+ 			/*
+ 			 * Because of the above test, we omit further ROW-related testing
+ 			 * below.  If we later allow naming OLD and NEW ROW variables,
+ 			 * adjustments will be needed below.
+ 			 */
+ 
+ 			if (stmt->timing != TRIGGER_TYPE_AFTER)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ 						 errmsg("transition table name can only be specified for an AFTER trigger")));
+ 
+ 			if (tt->isNew)
+ 			{
+ 				if (!(TRIGGER_FOR_INSERT(tgtype) ||
+ 					  TRIGGER_FOR_UPDATE(tgtype)))
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ 							 errmsg("NEW TABLE can only be specified for an INSERT or UPDATE trigger")));
+ 
+ 				if (newtablename != NULL)
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ 							 errmsg("NEW TABLE cannot be specified multiple times")));
+ 
+ 				newtablename = tt->name;
+ 			}
+ 			else
+ 			{
+ 				if (!(TRIGGER_FOR_DELETE(tgtype) ||
+ 					  TRIGGER_FOR_UPDATE(tgtype)))
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ 							 errmsg("OLD TABLE can only be specified for a DELETE or UPDATE trigger")));
+ 
+ 				if (oldtablename != NULL)
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ 							 errmsg("OLD TABLE cannot be specified multiple times")));
+ 
+ 				oldtablename = tt->name;
+ 			}
+ 		}
+ 	}
+ 
+ 	/*
  	 * Parse the WHEN clause, if any
  	 */
  	if (stmt->whenClause)
***************
*** 566,571 **** CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
--- 643,660 ----
  	values[Anum_pg_trigger_tgconstraint - 1] = ObjectIdGetDatum(constraintOid);
  	values[Anum_pg_trigger_tgdeferrable - 1] = BoolGetDatum(stmt->deferrable);
  	values[Anum_pg_trigger_tginitdeferred - 1] = BoolGetDatum(stmt->initdeferred);
+ 	if (oldtablename)
+ 		values[Anum_pg_trigger_tgoldtable - 1] = DirectFunctionCall1(namein,
+ 												  CStringGetDatum(oldtablename));
+ 	else
+ 		values[Anum_pg_trigger_tgoldtable - 1] = DirectFunctionCall1(namein,
+ 												  CStringGetDatum(""));
+ 	if (newtablename)
+ 		values[Anum_pg_trigger_tgnewtable - 1] = DirectFunctionCall1(namein,
+ 												  CStringGetDatum(newtablename));
+ 	else
+ 		values[Anum_pg_trigger_tgnewtable - 1] = DirectFunctionCall1(namein,
+ 												  CStringGetDatum(""));
  
  	if (stmt->args)
  	{
***************
*** 673,678 **** CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
--- 762,769 ----
  	heap_close(tgrel, RowExclusiveLock);
  
  	pfree(DatumGetPointer(values[Anum_pg_trigger_tgname - 1]));
+ 	pfree(DatumGetPointer(values[Anum_pg_trigger_tgoldtable - 1]));
+ 	pfree(DatumGetPointer(values[Anum_pg_trigger_tgnewtable - 1]));
  	pfree(DatumGetPointer(values[Anum_pg_trigger_tgargs - 1]));
  	pfree(DatumGetPointer(values[Anum_pg_trigger_tgattr - 1]));
  
***************
*** 1542,1547 **** RelationBuildTriggers(Relation relation)
--- 1633,1642 ----
  		build->tgconstraint = pg_trigger->tgconstraint;
  		build->tgdeferrable = pg_trigger->tgdeferrable;
  		build->tginitdeferred = pg_trigger->tginitdeferred;
+ 		build->tgoldtable = DatumGetCString(DirectFunctionCall1(nameout,
+ 											NameGetDatum(&pg_trigger->tgoldtable)));
+ 		build->tgnewtable = DatumGetCString(DirectFunctionCall1(nameout,
+ 											NameGetDatum(&pg_trigger->tgnewtable)));
  		build->tgnargs = pg_trigger->tgnargs;
  		/* tgattr is first var-width field, so OK to access directly */
  		build->tgnattr = pg_trigger->tgattr.dim1;
***************
*** 1670,1675 **** SetTriggerFlags(TriggerDesc *trigdesc, Trigger *trigger)
--- 1765,1783 ----
  	trigdesc->trig_truncate_after_statement |=
  		TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_STATEMENT,
  							 TRIGGER_TYPE_AFTER, TRIGGER_TYPE_TRUNCATE);
+ 
+ 	trigdesc->trig_insert_new_table |=
+ 		(TRIGGER_FOR_INSERT(tgtype) &&
+ 		 TRIGGER_USES_TRANSITION_TABLE(trigger->tgnewtable)) ? true : false;
+ 	trigdesc->trig_update_old_table |=
+ 		(TRIGGER_FOR_UPDATE(tgtype) &&
+ 		 TRIGGER_USES_TRANSITION_TABLE(trigger->tgoldtable)) ? true : false;
+ 	trigdesc->trig_update_new_table |=
+ 		(TRIGGER_FOR_UPDATE(tgtype) &&
+ 		 TRIGGER_USES_TRANSITION_TABLE(trigger->tgnewtable)) ? true : false;
+ 	trigdesc->trig_delete_old_table |=
+ 		(TRIGGER_FOR_DELETE(tgtype) &&
+ 		 TRIGGER_USES_TRANSITION_TABLE(trigger->tgoldtable)) ? true : false;
  }
  
  /*
***************
*** 1698,1703 **** CopyTriggerDesc(TriggerDesc *trigdesc)
--- 1806,1813 ----
  	for (i = 0; i < trigdesc->numtriggers; i++)
  	{
  		trigger->tgname = pstrdup(trigger->tgname);
+ 		trigger->tgoldtable = pstrdup(trigger->tgoldtable);
+ 		trigger->tgnewtable = pstrdup(trigger->tgnewtable);
  		if (trigger->tgnattr > 0)
  		{
  			int16	   *newattr;
***************
*** 1741,1746 **** FreeTriggerDesc(TriggerDesc *trigdesc)
--- 1851,1858 ----
  	for (i = 0; i < trigdesc->numtriggers; i++)
  	{
  		pfree(trigger->tgname);
+ 		pfree(trigger->tgoldtable);
+ 		pfree(trigger->tgnewtable);
  		if (trigger->tgnattr > 0)
  			pfree(trigger->tgattr);
  		if (trigger->tgnargs > 0)
***************
*** 1812,1817 **** equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2)
--- 1924,1933 ----
  				return false;
  			if (trig1->tginitdeferred != trig2->tginitdeferred)
  				return false;
+ 			if (strcmp(trig1->tgoldtable, trig2->tgoldtable) != 0)
+ 				return false;
+ 			if (strcmp(trig1->tgnewtable, trig2->tgnewtable) != 0)
+ 				return false;
  			if (trig1->tgnargs != trig2->tgnargs)
  				return false;
  			if (trig1->tgnattr != trig2->tgnattr)
***************
*** 2060,2066 **** ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
  {
  	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
  
! 	if (trigdesc && trigdesc->trig_insert_after_row)
  		AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT,
  							  true, NULL, trigtuple, recheckIndexes, NULL);
  }
--- 2176,2183 ----
  {
  	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
  
! 	if (trigdesc &&
! 		(trigdesc->trig_insert_after_row || trigdesc->trig_insert_new_table))
  		AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT,
  							  true, NULL, trigtuple, recheckIndexes, NULL);
  }
***************
*** 2263,2269 **** ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
  {
  	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
  
! 	if (trigdesc && trigdesc->trig_delete_after_row)
  	{
  		HeapTuple	trigtuple;
  
--- 2380,2387 ----
  {
  	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
  
! 	if (trigdesc &&
! 		(trigdesc->trig_delete_after_row || trigdesc->trig_delete_old_table))
  	{
  		HeapTuple	trigtuple;
  
***************
*** 2528,2534 **** ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
  {
  	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
  
! 	if (trigdesc && trigdesc->trig_update_after_row)
  	{
  		HeapTuple	trigtuple;
  
--- 2646,2653 ----
  {
  	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
  
! 	if (trigdesc && (trigdesc->trig_update_after_row ||
! 		 trigdesc->trig_update_old_table || trigdesc->trig_update_new_table))
  	{
  		HeapTuple	trigtuple;
  
***************
*** 3160,3167 **** typedef struct AfterTriggerEventList
   * fdw_tuplestores[query_depth] is a tuplestore containing the foreign tuples
   * needed for the current query.
   *
!  * maxquerydepth is just the allocated length of query_stack and
!  * fdw_tuplestores.
   *
   * state_stack is a stack of pointers to saved copies of the SET CONSTRAINTS
   * state data; each subtransaction level that modifies that state first
--- 3279,3289 ----
   * fdw_tuplestores[query_depth] is a tuplestore containing the foreign tuples
   * needed for the current query.
   *
!  * old_tuplestores[query_depth] and new_tuplestores[query_depth] hold the
!  * delta relations for the current query.
!  *
!  * maxquerydepth is just the allocated length of query_stack and the
!  * tuplestores.
   *
   * state_stack is a stack of pointers to saved copies of the SET CONSTRAINTS
   * state data; each subtransaction level that modifies that state first
***************
*** 3190,3196 **** typedef struct AfterTriggersData
  	AfterTriggerEventList events;		/* deferred-event list */
  	int			query_depth;	/* current query list index */
  	AfterTriggerEventList *query_stack; /* events pending from each query */
! 	Tuplestorestate **fdw_tuplestores;	/* foreign tuples from each query */
  	int			maxquerydepth;	/* allocated len of above array */
  	MemoryContext event_cxt;	/* memory context for events, if any */
  
--- 3312,3320 ----
  	AfterTriggerEventList events;		/* deferred-event list */
  	int			query_depth;	/* current query list index */
  	AfterTriggerEventList *query_stack; /* events pending from each query */
! 	Tuplestorestate **fdw_tuplestores;	/* foreign tuples for one row from each query */
! 	Tuplestorestate **old_tuplestores;	/* all old tuples from each query */
! 	Tuplestorestate **new_tuplestores;	/* all new tuples from each query */
  	int			maxquerydepth;	/* allocated len of above array */
  	MemoryContext event_cxt;	/* memory context for events, if any */
  
***************
*** 3221,3234 **** static SetConstraintState SetConstraintStateAddItem(SetConstraintState state,
  
  
  /*
!  * Gets the current query fdw tuplestore and initializes it if necessary
   */
  static Tuplestorestate *
! GetCurrentFDWTuplestore()
  {
  	Tuplestorestate *ret;
  
! 	ret = afterTriggers->fdw_tuplestores[afterTriggers->query_depth];
  	if (ret == NULL)
  	{
  		MemoryContext oldcxt;
--- 3345,3358 ----
  
  
  /*
!  * Gets a current query delta tuplestore and initializes it if necessary
   */
  static Tuplestorestate *
! GetCurrentTriggerDeltaTuplestore(Tuplestorestate **tss)
  {
  	Tuplestorestate *ret;
  
! 	ret = tss[afterTriggers->query_depth];
  	if (ret == NULL)
  	{
  		MemoryContext oldcxt;
***************
*** 3255,3261 **** GetCurrentFDWTuplestore()
  		CurrentResourceOwner = saveResourceOwner;
  		MemoryContextSwitchTo(oldcxt);
  
! 		afterTriggers->fdw_tuplestores[afterTriggers->query_depth] = ret;
  	}
  
  	return ret;
--- 3379,3385 ----
  		CurrentResourceOwner = saveResourceOwner;
  		MemoryContextSwitchTo(oldcxt);
  
! 		tss[afterTriggers->query_depth] = ret;
  	}
  
  	return ret;
***************
*** 3552,3558 **** AfterTriggerExecute(AfterTriggerEvent event,
  	{
  		case AFTER_TRIGGER_FDW_FETCH:
  			{
! 				Tuplestorestate *fdw_tuplestore = GetCurrentFDWTuplestore();
  
  				if (!tuplestore_gettupleslot(fdw_tuplestore, true, false,
  											 trig_tuple_slot1))
--- 3676,3684 ----
  	{
  		case AFTER_TRIGGER_FDW_FETCH:
  			{
! 				Tuplestorestate *fdw_tuplestore =
! 					GetCurrentTriggerDeltaTuplestore
! 						(afterTriggers->fdw_tuplestores);
  
  				if (!tuplestore_gettupleslot(fdw_tuplestore, true, false,
  											 trig_tuple_slot1))
***************
*** 3622,3627 **** AfterTriggerExecute(AfterTriggerEvent event,
--- 3748,3767 ----
  	}
  
  	/*
+ 	 * Set up the tuplestore information.
+ 	 */
+ 	if (trigdesc->trig_delete_old_table || trigdesc->trig_update_old_table)
+ 		LocTriggerData.tg_olddelta =
+ 			GetCurrentTriggerDeltaTuplestore(afterTriggers->old_tuplestores);
+ 	else
+ 		LocTriggerData.tg_olddelta = NULL;
+ 	if (trigdesc->trig_insert_new_table || trigdesc->trig_update_new_table)
+ 		LocTriggerData.tg_newdelta =
+ 			GetCurrentTriggerDeltaTuplestore(afterTriggers->new_tuplestores);
+ 	else
+ 		LocTriggerData.tg_newdelta = NULL;
+ 
+ 	/*
  	 * Setup the remaining trigger information
  	 */
  	LocTriggerData.type = T_TriggerData;
***************
*** 3921,3926 **** AfterTriggerBeginXact(void)
--- 4061,4072 ----
  	afterTriggers->fdw_tuplestores = (Tuplestorestate **)
  		MemoryContextAllocZero(TopTransactionContext,
  							   8 * sizeof(Tuplestorestate *));
+ 	afterTriggers->old_tuplestores = (Tuplestorestate **)
+ 		MemoryContextAllocZero(TopTransactionContext,
+ 							   8 * sizeof(Tuplestorestate *));
+ 	afterTriggers->new_tuplestores = (Tuplestorestate **)
+ 		MemoryContextAllocZero(TopTransactionContext,
+ 							   8 * sizeof(Tuplestorestate *));
  	afterTriggers->maxquerydepth = 8;
  
  	/* Context for events is created only when needed */
***************
*** 3970,3978 **** AfterTriggerBeginQuery(void)
--- 4116,4134 ----
  		afterTriggers->fdw_tuplestores = (Tuplestorestate **)
  			repalloc(afterTriggers->fdw_tuplestores,
  					 new_alloc * sizeof(Tuplestorestate *));
+ 		afterTriggers->old_tuplestores = (Tuplestorestate **)
+ 			repalloc(afterTriggers->old_tuplestores,
+ 					 new_alloc * sizeof(Tuplestorestate *));
+ 		afterTriggers->new_tuplestores = (Tuplestorestate **)
+ 			repalloc(afterTriggers->new_tuplestores,
+ 					 new_alloc * sizeof(Tuplestorestate *));
  		/* Clear newly-allocated slots for subsequent lazy initialization. */
  		memset(afterTriggers->fdw_tuplestores + old_alloc,
  			   0, (new_alloc - old_alloc) * sizeof(Tuplestorestate *));
+ 		memset(afterTriggers->old_tuplestores + old_alloc,
+ 			   0, (new_alloc - old_alloc) * sizeof(Tuplestorestate *));
+ 		memset(afterTriggers->new_tuplestores + old_alloc,
+ 			   0, (new_alloc - old_alloc) * sizeof(Tuplestorestate *));
  		afterTriggers->maxquerydepth = new_alloc;
  	}
  
***************
*** 4001,4006 **** AfterTriggerEndQuery(EState *estate)
--- 4157,4164 ----
  {
  	AfterTriggerEventList *events;
  	Tuplestorestate *fdw_tuplestore;
+ 	Tuplestorestate *old_tuplestore;
+ 	Tuplestorestate *new_tuplestore;
  
  	/* Must be inside a transaction */
  	Assert(afterTriggers != NULL);
***************
*** 4045,4057 **** AfterTriggerEndQuery(EState *estate)
  			break;
  	}
  
! 	/* Release query-local storage for events, including tuplestore if any */
  	fdw_tuplestore = afterTriggers->fdw_tuplestores[afterTriggers->query_depth];
  	if (fdw_tuplestore)
  	{
  		tuplestore_end(fdw_tuplestore);
  		afterTriggers->fdw_tuplestores[afterTriggers->query_depth] = NULL;
  	}
  	afterTriggerFreeEventList(&afterTriggers->query_stack[afterTriggers->query_depth]);
  
  	afterTriggers->query_depth--;
--- 4203,4227 ----
  			break;
  	}
  
! 	/* Release query-local storage for events, including tuplestores, if any */
  	fdw_tuplestore = afterTriggers->fdw_tuplestores[afterTriggers->query_depth];
  	if (fdw_tuplestore)
  	{
  		tuplestore_end(fdw_tuplestore);
  		afterTriggers->fdw_tuplestores[afterTriggers->query_depth] = NULL;
  	}
+ 	old_tuplestore = afterTriggers->old_tuplestores[afterTriggers->query_depth];
+ 	if (old_tuplestore)
+ 	{
+ 		tuplestore_end(old_tuplestore);
+ 		afterTriggers->old_tuplestores[afterTriggers->query_depth] = NULL;
+ 	}
+ 	new_tuplestore = afterTriggers->new_tuplestores[afterTriggers->query_depth];
+ 	if (new_tuplestore)
+ 	{
+ 		tuplestore_end(new_tuplestore);
+ 		afterTriggers->new_tuplestores[afterTriggers->query_depth] = NULL;
+ 	}
  	afterTriggerFreeEventList(&afterTriggers->query_stack[afterTriggers->query_depth]);
  
  	afterTriggers->query_depth--;
***************
*** 4283,4288 **** AfterTriggerEndSubXact(bool isCommit)
--- 4453,4470 ----
  				tuplestore_end(ts);
  				afterTriggers->fdw_tuplestores[afterTriggers->query_depth] = NULL;
  			}
+ 			ts = afterTriggers->old_tuplestores[afterTriggers->query_depth];
+ 			if (ts)
+ 			{
+ 				tuplestore_end(ts);
+ 				afterTriggers->old_tuplestores[afterTriggers->query_depth] = NULL;
+ 			}
+ 			ts = afterTriggers->new_tuplestores[afterTriggers->query_depth];
+ 			if (ts)
+ 			{
+ 				tuplestore_end(ts);
+ 				afterTriggers->new_tuplestores[afterTriggers->query_depth] = NULL;
+ 			}
  
  			afterTriggerFreeEventList(&afterTriggers->query_stack[afterTriggers->query_depth]);
  			afterTriggers->query_depth--;
***************
*** 4767,4773 **** AfterTriggerPendingOnRel(Oid relid)
   *
   *	NOTE: this is called whenever there are any triggers associated with
   *	the event (even if they are disabled).  This function decides which
!  *	triggers actually need to be queued.
   * ----------
   */
  static void
--- 4949,4962 ----
   *
   *	NOTE: this is called whenever there are any triggers associated with
   *	the event (even if they are disabled).  This function decides which
!  *	triggers actually need to be queued.  It is also called after each row,
!  *	even if there are no triggers for that event, if there are any AFTER
!  *	STATEMENT triggers for the statement which use transition tables, so that
!  *	the delta tuplestores can be built.
!  *
!  *	Delta tuplestores are built now, rather than when events are pulled off
!  *	of the queue because AFTER ROW triggers are allowed to select from the
!  *	transition tables for the statement.
   * ----------
   */
  static void
***************
*** 4797,4802 **** AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
--- 4986,5031 ----
  		elog(ERROR, "AfterTriggerSaveEvent() called outside of query");
  
  	/*
+ 	 * If the relation has AFTER ... FOR EACH ROW triggers, capture rows into
+ 	 * delta tuplestores for this depth.
+ 	 */
+ 	if (row_trigger)
+ 	{
+ 		if ((event == TRIGGER_EVENT_DELETE &&
+ 			 trigdesc->trig_delete_old_table) ||
+ 			(event == TRIGGER_EVENT_UPDATE &&
+ 			 trigdesc->trig_update_old_table))
+ 		{
+ 			Tuplestorestate *old_tuplestore;
+ 
+ 			Assert(oldtup != NULL);
+ 			old_tuplestore =
+ 				GetCurrentTriggerDeltaTuplestore
+ 					(afterTriggers->old_tuplestores);
+ 			tuplestore_puttuple(old_tuplestore, oldtup);
+ 		}
+ 		if ((event == TRIGGER_EVENT_INSERT &&
+ 			 trigdesc->trig_insert_new_table) ||
+ 			(event == TRIGGER_EVENT_UPDATE &&
+ 			 trigdesc->trig_update_new_table))
+ 		{
+ 			Tuplestorestate *new_tuplestore;
+ 
+ 			Assert(newtup != NULL);
+ 			new_tuplestore =
+ 				GetCurrentTriggerDeltaTuplestore
+ 					(afterTriggers->new_tuplestores);
+ 			tuplestore_puttuple(new_tuplestore, newtup);
+ 		}
+ 
+ 		/* If deltas are the only reason we're here, return. */
+ 		if ((event == TRIGGER_EVENT_DELETE && !trigdesc->trig_delete_after_row) ||
+ 			(event == TRIGGER_EVENT_INSERT && !trigdesc->trig_insert_after_row) ||
+ 			(event == TRIGGER_EVENT_UPDATE && !trigdesc->trig_update_after_row))
+ 			return;
+ 	}
+ 
+ 	/*
  	 * Validate the event code and collect the associated tuple CTIDs.
  	 *
  	 * The event code will be used both as a bitmask and an array offset, so
***************
*** 4893,4899 **** AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
  		{
  			if (fdw_tuplestore == NULL)
  			{
! 				fdw_tuplestore = GetCurrentFDWTuplestore();
  				new_event.ate_flags = AFTER_TRIGGER_FDW_FETCH;
  			}
  			else
--- 5122,5130 ----
  		{
  			if (fdw_tuplestore == NULL)
  			{
! 				fdw_tuplestore =
! 					GetCurrentTriggerDeltaTuplestore
! 						(afterTriggers->fdw_tuplestores);
  				new_event.ate_flags = AFTER_TRIGGER_FDW_FETCH;
  			}
  			else
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2470,2475 **** _copyXmlSerialize(const XmlSerialize *from)
--- 2470,2487 ----
  	return newnode;
  }
  
+ static TriggerTransition *
+ _copyTriggerTransition(const TriggerTransition *from)
+ {
+ 	TriggerTransition *newnode = makeNode(TriggerTransition);
+ 
+ 	COPY_STRING_FIELD(name);
+ 	COPY_SCALAR_FIELD(isNew);
+ 	COPY_SCALAR_FIELD(isTable);
+ 
+ 	return newnode;
+ }
+ 
  static Query *
  _copyQuery(const Query *from)
  {
***************
*** 3582,3587 **** _copyCreateTrigStmt(const CreateTrigStmt *from)
--- 3594,3600 ----
  	COPY_NODE_FIELD(columns);
  	COPY_NODE_FIELD(whenClause);
  	COPY_SCALAR_FIELD(isconstraint);
+ 	COPY_NODE_FIELD(transitionRels);
  	COPY_SCALAR_FIELD(deferrable);
  	COPY_SCALAR_FIELD(initdeferred);
  	COPY_NODE_FIELD(constrrel);
***************
*** 4649,4654 **** copyObject(const void *from)
--- 4662,4670 ----
  		case T_XmlSerialize:
  			retval = _copyXmlSerialize(from);
  			break;
+ 		case T_TriggerTransition:
+ 			retval = _copyTriggerTransition(from);
+ 			break;
  
  		default:
  			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 1781,1786 **** _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
--- 1781,1787 ----
  	COMPARE_NODE_FIELD(columns);
  	COMPARE_NODE_FIELD(whenClause);
  	COMPARE_SCALAR_FIELD(isconstraint);
+ 	COMPARE_NODE_FIELD(transitionRels);
  	COMPARE_SCALAR_FIELD(deferrable);
  	COMPARE_SCALAR_FIELD(initdeferred);
  	COMPARE_NODE_FIELD(constrrel);
***************
*** 2413,2418 **** _equalXmlSerialize(const XmlSerialize *a, const XmlSerialize *b)
--- 2414,2429 ----
  	return true;
  }
  
+ static bool
+ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b)
+ {
+ 	COMPARE_STRING_FIELD(name);
+ 	COMPARE_SCALAR_FIELD(isNew);
+ 	COMPARE_SCALAR_FIELD(isTable);
+ 
+ 	return true;
+ }
+ 
  /*
   * Stuff from pg_list.h
   */
***************
*** 3115,3120 **** equal(const void *a, const void *b)
--- 3126,3134 ----
  		case T_XmlSerialize:
  			retval = _equalXmlSerialize(a, b);
  			break;
+ 		case T_TriggerTransition:
+ 			retval = _equalTriggerTransition(a, b);
+ 			break;
  
  		default:
  			elog(ERROR, "unrecognized node type: %d",
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 2138,2143 **** _outXmlSerialize(StringInfo str, const XmlSerialize *node)
--- 2138,2153 ----
  }
  
  static void
+ _outTriggerTransition(StringInfo str, const TriggerTransition *node)
+ {
+ 	WRITE_NODE_TYPE("TRIGGERTRANSITION");
+ 
+ 	WRITE_STRING_FIELD(name);
+ 	WRITE_BOOL_FIELD(isNew);
+ 	WRITE_BOOL_FIELD(isTable);
+ }
+ 
+ static void
  _outColumnDef(StringInfo str, const ColumnDef *node)
  {
  	WRITE_NODE_TYPE("COLUMNDEF");
***************
*** 3236,3241 **** _outNode(StringInfo str, const void *obj)
--- 3246,3254 ----
  			case T_XmlSerialize:
  				_outXmlSerialize(str, obj);
  				break;
+ 			case T_TriggerTransition:
+ 				_outTriggerTransition(str, obj);
+ 				break;
  
  			default:
  
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 293,298 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
--- 293,301 ----
  %type <list>	TriggerEvents TriggerOneEvent
  %type <value>	TriggerFuncArg
  %type <node>	TriggerWhen
+ %type <str>		TransitionRelName
+ %type <boolean>	TransitionRowOrTable TransitionOldOrNew
+ %type <node>	TriggerTransition
  
  %type <list>	event_trigger_when_list event_trigger_value_list
  %type <defelt>	event_trigger_when_item
***************
*** 350,355 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
--- 353,359 ----
  				opt_enum_val_list enum_val_list table_func_column_list
  				create_generic_options alter_generic_options
  				relation_expr_list dostmt_opt_list
+ 				TriggerTransitions TriggerReferencing
  
  %type <list>	opt_fdw_options fdw_options
  %type <defelt>	fdw_option
***************
*** 570,580 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  
  	MAPPING MATCH MATERIALIZED MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
  
! 	NAME_P NAMES NATIONAL NATURAL NCHAR NEXT NO NONE
  	NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
  	NULLS_P NUMERIC
  
! 	OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
  	ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
  
  	PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POSITION
--- 574,584 ----
  
  	MAPPING MATCH MATERIALIZED MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
  
! 	NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NONE
  	NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
  	NULLS_P NUMERIC
  
! 	OBJECT_P OF OFF OFFSET OIDS OLD ON ONLY OPERATOR OPTION OPTIONS OR
  	ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
  
  	PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POSITION
***************
*** 583,590 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  
  	QUOTE
  
! 	RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFRESH REINDEX
! 	RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
  	RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK
  	ROW ROWS RULE
  
--- 587,594 ----
  
  	QUOTE
  
! 	RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFERENCING
! 	REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
  	RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK
  	ROW ROWS RULE
  
***************
*** 4347,4365 **** AlterUserMappingStmt: ALTER USER MAPPING FOR auth_ident SERVER name alter_generi
  
  CreateTrigStmt:
  			CREATE TRIGGER name TriggerActionTime TriggerEvents ON
! 			qualified_name TriggerForSpec TriggerWhen
  			EXECUTE PROCEDURE func_name '(' TriggerFuncArgs ')'
  				{
  					CreateTrigStmt *n = makeNode(CreateTrigStmt);
  					n->trigname = $3;
  					n->relation = $7;
! 					n->funcname = $12;
! 					n->args = $14;
! 					n->row = $8;
  					n->timing = $4;
  					n->events = intVal(linitial($5));
  					n->columns = (List *) lsecond($5);
! 					n->whenClause = $9;
  					n->isconstraint  = FALSE;
  					n->deferrable	 = FALSE;
  					n->initdeferred  = FALSE;
--- 4351,4370 ----
  
  CreateTrigStmt:
  			CREATE TRIGGER name TriggerActionTime TriggerEvents ON
! 			qualified_name TriggerReferencing TriggerForSpec TriggerWhen
  			EXECUTE PROCEDURE func_name '(' TriggerFuncArgs ')'
  				{
  					CreateTrigStmt *n = makeNode(CreateTrigStmt);
  					n->trigname = $3;
  					n->relation = $7;
! 					n->funcname = $13;
! 					n->args = $15;
! 					n->row = $9;
  					n->timing = $4;
  					n->events = intVal(linitial($5));
  					n->columns = (List *) lsecond($5);
! 					n->whenClause = $10;
! 					n->transitionRels = $8;
  					n->isconstraint  = FALSE;
  					n->deferrable	 = FALSE;
  					n->initdeferred  = FALSE;
***************
*** 4381,4386 **** CreateTrigStmt:
--- 4386,4392 ----
  					n->events = intVal(linitial($6));
  					n->columns = (List *) lsecond($6);
  					n->whenClause = $14;
+ 					n->transitionRels = NIL;
  					n->isconstraint  = TRUE;
  					processCASbits($10, @10, "TRIGGER",
  								   &n->deferrable, &n->initdeferred, NULL,
***************
*** 4433,4438 **** TriggerOneEvent:
--- 4439,4484 ----
  				{ $$ = list_make2(makeInteger(TRIGGER_TYPE_TRUNCATE), NIL); }
  		;
  
+ TriggerReferencing:
+ 			REFERENCING TriggerTransitions			{ $$ = $2; }
+ 			| /*EMPTY*/								{ $$ = NIL; }
+ 		;
+ 
+ TriggerTransitions:
+ 			TriggerTransition						{ $$ = list_make1($1); }
+ 			| TriggerTransitions TriggerTransition	{ $$ = lappend($1, $2); }
+ 		;
+ 
+ TriggerTransition:
+ 			TransitionOldOrNew TransitionRowOrTable opt_as TransitionRelName
+ 				{
+ 					TriggerTransition *n = makeNode(TriggerTransition);
+ 					n->name = $4;
+ 					n->isNew = $1;
+ 					n->isTable = $2;
+ 					$$ = (Node *)n;
+ 				}
+ 		;
+ 
+ TransitionOldOrNew:
+ 			NEW										{ $$ = TRUE; }
+ 			| OLD									{ $$ = FALSE; }
+ 		;
+ 
+ TransitionRowOrTable:
+ 			TABLE									{ $$ = TRUE; }
+ 			/*
+ 			 * Explicit ROW specification is not supported to avoid fully
+ 			 * reserving the keyword ROW.  According to the standard, lack of
+ 			 * a keyword here means ROW anyway.
+ 			 */
+ 			| /*EMPTY*/								{ $$ = FALSE; }
+ 		;
+ 
+ TransitionRelName:
+ 			ColId									{ $$ = $1; }
+ 		;
+ 
  TriggerForSpec:
  			FOR TriggerForOptEach TriggerForType
  				{
***************
*** 12978,12983 **** unreserved_keyword:
--- 13024,13030 ----
  			| MOVE
  			| NAME_P
  			| NAMES
+ 			| NEW
  			| NEXT
  			| NO
  			| NOTHING
***************
*** 12988,12993 **** unreserved_keyword:
--- 13035,13041 ----
  			| OF
  			| OFF
  			| OIDS
+ 			| OLD
  			| OPERATOR
  			| OPTION
  			| OPTIONS
***************
*** 13017,13022 **** unreserved_keyword:
--- 13065,13071 ----
  			| RECHECK
  			| RECURSIVE
  			| REF
+ 			| REFERENCING
  			| REFRESH
  			| REINDEX
  			| RELATIVE_P
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 809,814 **** pg_get_triggerdef_worker(Oid trigid, bool pretty)
--- 809,824 ----
  			appendStringInfoString(&buf, "IMMEDIATE ");
  	}
  
+ 	if (TRIGGER_USES_TRANSITION_TABLE(NameStr(trigrec->tgoldtable)) ||
+ 		TRIGGER_USES_TRANSITION_TABLE(NameStr(trigrec->tgnewtable)))
+ 	{
+ 		appendStringInfoString(&buf, "REFERENCING ");
+ 		if (TRIGGER_USES_TRANSITION_TABLE(NameStr(trigrec->tgoldtable)))
+ 			appendStringInfo(&buf, "OLD TABLE AS %s ", NameStr(trigrec->tgoldtable));
+ 		if (TRIGGER_USES_TRANSITION_TABLE(NameStr(trigrec->tgnewtable)))
+ 			appendStringInfo(&buf, "NEW TABLE AS %s ", NameStr(trigrec->tgnewtable));
+ 	}
+ 
  	if (TRIGGER_FOR_ROW(trigrec->tgtype))
  		appendStringInfoString(&buf, "FOR EACH ROW ");
  	else
*** a/src/include/catalog/pg_trigger.h
--- b/src/include/catalog/pg_trigger.h
***************
*** 48,53 **** CATALOG(pg_trigger,2620)
--- 48,55 ----
  	Oid			tgconstraint;	/* associated pg_constraint entry, if any */
  	bool		tgdeferrable;	/* constraint trigger is deferrable */
  	bool		tginitdeferred; /* constraint trigger is deferred initially */
+ 	NameData	tgoldtable;		/* Name to use for old delta table for stmt */
+ 	NameData	tgnewtable;		/* Name to use for new delta table for stmt */
  	int16		tgnargs;		/* # of extra arguments in tgargs */
  
  	/*
***************
*** 73,79 **** typedef FormData_pg_trigger *Form_pg_trigger;
   *		compiler constants for pg_trigger
   * ----------------
   */
! #define Natts_pg_trigger				15
  #define Anum_pg_trigger_tgrelid			1
  #define Anum_pg_trigger_tgname			2
  #define Anum_pg_trigger_tgfoid			3
--- 75,81 ----
   *		compiler constants for pg_trigger
   * ----------------
   */
! #define Natts_pg_trigger				17
  #define Anum_pg_trigger_tgrelid			1
  #define Anum_pg_trigger_tgname			2
  #define Anum_pg_trigger_tgfoid			3
***************
*** 85,94 **** typedef FormData_pg_trigger *Form_pg_trigger;
  #define Anum_pg_trigger_tgconstraint	9
  #define Anum_pg_trigger_tgdeferrable	10
  #define Anum_pg_trigger_tginitdeferred	11
! #define Anum_pg_trigger_tgnargs			12
! #define Anum_pg_trigger_tgattr			13
! #define Anum_pg_trigger_tgargs			14
! #define Anum_pg_trigger_tgqual			15
  
  /* Bits within tgtype */
  #define TRIGGER_TYPE_ROW				(1 << 0)
--- 87,98 ----
  #define Anum_pg_trigger_tgconstraint	9
  #define Anum_pg_trigger_tgdeferrable	10
  #define Anum_pg_trigger_tginitdeferred	11
! #define Anum_pg_trigger_tgoldtable		12
! #define Anum_pg_trigger_tgnewtable		13
! #define Anum_pg_trigger_tgnargs			14
! #define Anum_pg_trigger_tgattr			15
! #define Anum_pg_trigger_tgargs			16
! #define Anum_pg_trigger_tgqual			17
  
  /* Bits within tgtype */
  #define TRIGGER_TYPE_ROW				(1 << 0)
***************
*** 142,145 **** typedef FormData_pg_trigger *Form_pg_trigger;
--- 146,160 ----
  #define TRIGGER_TYPE_MATCHES(type, level, timing, event) \
  	(((type) & (TRIGGER_TYPE_LEVEL_MASK | TRIGGER_TYPE_TIMING_MASK | (event))) == ((level) | (timing) | (event)))
  
+ /*
+  * Macro to determine whether tgnewtable or tgoldtable has been specified for
+  * a trigger.
+  *
+  * TODO: Once the dust settles on development, this can probably be
+  * simplified to test for either a NULL pointer or a zero-length cstring, but
+  * for now we'll do both.
+  */
+ #define TRIGGER_USES_TRANSITION_TABLE(namepointer) \
+ 	((namepointer) != (char *) NULL && (*(namepointer)) != '\0')
+ 
  #endif   /* PG_TRIGGER_H */
*** a/src/include/commands/trigger.h
--- b/src/include/commands/trigger.h
***************
*** 36,41 **** typedef struct TriggerData
--- 36,43 ----
  	Trigger    *tg_trigger;
  	Buffer		tg_trigtuplebuf;
  	Buffer		tg_newtuplebuf;
+ 	Tuplestorestate *tg_olddelta;
+ 	Tuplestorestate *tg_newdelta;
  } TriggerData;
  
  /*
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
***************
*** 407,412 **** typedef enum NodeTag
--- 407,413 ----
  	T_XmlSerialize,
  	T_WithClause,
  	T_CommonTableExpr,
+ 	T_TriggerTransition,
  
  	/*
  	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 1018,1023 **** typedef struct CommonTableExpr
--- 1018,1039 ----
  	List	   *ctecolcollations;		/* OID list of column collation OIDs */
  } CommonTableExpr;
  
+ /*
+  * TriggerTransition -
+  *	   representation of transition row or table naming clause
+  *
+  * Only tables are initially supported, and only for AFTER EACH STATEMENT
+  * triggers, but other permutations are accepted by the parser so we can give
+  * a meaningful message from C code.
+  */
+ typedef struct TriggerTransition
+ {
+ 	NodeTag		type;
+ 	char	   *name;
+ 	bool		isNew;
+ 	bool		isTable;
+ } TriggerTransition;
+ 
  /* Convenience macro to get the output tlist of a CTE's query */
  #define GetCTETargetList(cte) \
  	(AssertMacro(IsA((cte)->ctequery, Query)), \
***************
*** 1850,1855 **** typedef struct CreateTrigStmt
--- 1866,1873 ----
  	List	   *columns;		/* column names, or NIL for all columns */
  	Node	   *whenClause;		/* qual expression, or NULL if none */
  	bool		isconstraint;	/* This is a constraint trigger */
+ 	/* explicitly named transition data */
+ 	List	   *transitionRels; /* TriggerTransition nodes, or NIL if none */
  	/* The remaining fields are only used for constraint triggers */
  	bool		deferrable;		/* [NOT] DEFERRABLE */
  	bool		initdeferred;	/* INITIALLY {DEFERRED|IMMEDIATE} */
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 245,250 **** PG_KEYWORD("names", NAMES, UNRESERVED_KEYWORD)
--- 245,251 ----
  PG_KEYWORD("national", NATIONAL, COL_NAME_KEYWORD)
  PG_KEYWORD("natural", NATURAL, TYPE_FUNC_NAME_KEYWORD)
  PG_KEYWORD("nchar", NCHAR, COL_NAME_KEYWORD)
+ PG_KEYWORD("new", NEW, UNRESERVED_KEYWORD)
  PG_KEYWORD("next", NEXT, UNRESERVED_KEYWORD)
  PG_KEYWORD("no", NO, UNRESERVED_KEYWORD)
  PG_KEYWORD("none", NONE, COL_NAME_KEYWORD)
***************
*** 262,267 **** PG_KEYWORD("of", OF, UNRESERVED_KEYWORD)
--- 263,269 ----
  PG_KEYWORD("off", OFF, UNRESERVED_KEYWORD)
  PG_KEYWORD("offset", OFFSET, RESERVED_KEYWORD)
  PG_KEYWORD("oids", OIDS, UNRESERVED_KEYWORD)
+ PG_KEYWORD("old", OLD, UNRESERVED_KEYWORD)
  PG_KEYWORD("on", ON, RESERVED_KEYWORD)
  PG_KEYWORD("only", ONLY, RESERVED_KEYWORD)
  PG_KEYWORD("operator", OPERATOR, UNRESERVED_KEYWORD)
***************
*** 305,310 **** PG_KEYWORD("recheck", RECHECK, UNRESERVED_KEYWORD)
--- 307,313 ----
  PG_KEYWORD("recursive", RECURSIVE, UNRESERVED_KEYWORD)
  PG_KEYWORD("ref", REF, UNRESERVED_KEYWORD)
  PG_KEYWORD("references", REFERENCES, RESERVED_KEYWORD)
+ PG_KEYWORD("referencing", REFERENCING, UNRESERVED_KEYWORD)
  PG_KEYWORD("refresh", REFRESH, UNRESERVED_KEYWORD)
  PG_KEYWORD("reindex", REINDEX, UNRESERVED_KEYWORD)
  PG_KEYWORD("relative", RELATIVE_P, UNRESERVED_KEYWORD)
*** a/src/include/utils/reltrigger.h
--- b/src/include/utils/reltrigger.h
***************
*** 34,39 **** typedef struct Trigger
--- 34,41 ----
  	Oid			tgconstraint;
  	bool		tgdeferrable;
  	bool		tginitdeferred;
+ 	char	   *tgoldtable;
+ 	char	   *tgnewtable;
  	int16		tgnargs;
  	int16		tgnattr;
  	int16	   *tgattr;
***************
*** 68,73 **** typedef struct TriggerDesc
--- 70,80 ----
  	/* there are no row-level truncate triggers */
  	bool		trig_truncate_before_statement;
  	bool		trig_truncate_after_statement;
+ 	/* Is there at least one trigger specifying each transition relation? */
+ 	bool		trig_insert_new_table;
+ 	bool		trig_update_old_table;
+ 	bool		trig_update_new_table;
+ 	bool		trig_delete_old_table;
  } TriggerDesc;
  
  #endif   /* RELTRIGGER_H */
