diff -cprN head/doc/src/sgml/ref/create_trigger.sgml work/doc/src/sgml/ref/create_trigger.sgml
*** head/doc/src/sgml/ref/create_trigger.sgml	2009-09-19 19:23:27.000000000 +0900
--- work/doc/src/sgml/ref/create_trigger.sgml	2009-10-14 12:15:45.343050314 +0900
*************** CREATE TRIGGER <replaceable class="PARAM
*** 122,127 ****
--- 122,137 ----
        this specifies the event that will fire the trigger. Multiple
        events can be specified using <literal>OR</literal>.
       </para>
+ 
+      <para>
+       For <command>UPDATE</command> triggers, it is possible to
+       specify a list of columns using this syntax:
+ <synopsis>
+ UPDATE OF <replaceable>colname1</replaceable> [, <replaceable>colname2</replaceable>, ...]
+ </synopsis>
+       The trigger will only fire if at least one of the listed columns
+       is mentioned as a target of the update.
+      </para>
      </listitem>
     </varlistentry>
  
diff -cprN head/doc/src/sgml/trigger.sgml work/doc/src/sgml/trigger.sgml
*** head/doc/src/sgml/trigger.sgml	2009-08-05 07:04:37.000000000 +0900
--- work/doc/src/sgml/trigger.sgml	2009-10-14 12:15:45.343050314 +0900
***************
*** 36,45 ****
      performed.  Triggers can be defined to execute either before or after any
      <command>INSERT</command>, <command>UPDATE</command>, or
      <command>DELETE</command> operation, either once per modified row,
!     or once per <acronym>SQL</acronym> statement.  Triggers can also fire
!     for <command>TRUNCATE</command> statements.  If a trigger event occurs,
!     the trigger's function is called at the appropriate time to handle the
!     event.
     </para>
  
     <para>
--- 36,48 ----
      performed.  Triggers can be defined to execute either before or after any
      <command>INSERT</command>, <command>UPDATE</command>, or
      <command>DELETE</command> operation, either once per modified row,
!     or once per <acronym>SQL</acronym>
!     statement.  <command>UPDATE</command> triggers can moreover be set
!     to only fire if certain columns are mentioned in
!     the <literal>SET</literal> clause of the <command>UPDATE</command>
!     statement.  Triggers can also fire for <command>TRUNCATE</command>
!     statements.  If a trigger event occurs, the trigger's function is
!     called at the appropriate time to handle the event.
     </para>
  
     <para>
diff -cprN head/src/backend/commands/trigger.c work/src/backend/commands/trigger.c
*** head/src/backend/commands/trigger.c	2009-10-10 10:43:45.000000000 +0900
--- work/src/backend/commands/trigger.c	2009-10-14 13:18:14.881976192 +0900
***************
*** 30,37 ****
--- 30,40 ----
  #include "executor/executor.h"
  #include "executor/instrument.h"
  #include "miscadmin.h"
+ #include "nodes/bitmapset.h"
  #include "nodes/makefuncs.h"
  #include "parser/parse_func.h"
+ #include "parser/parse_relation.h"
+ #include "parser/parsetree.h"
  #include "pgstat.h"
  #include "storage/bufmgr.h"
  #include "tcop/utility.h"
*************** static HeapTuple ExecCallTriggerFunc(Tri
*** 66,72 ****
  					MemoryContext per_tuple_context);
  static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
  					  bool row_trigger, HeapTuple oldtup, HeapTuple newtup,
! 					  List *recheckIndexes);
  
  
  /*
--- 69,79 ----
  					MemoryContext per_tuple_context);
  static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
  					  bool row_trigger, HeapTuple oldtup, HeapTuple newtup,
! 					  List *recheckIndexes, Bitmapset *modifiedCols);
! static bool TriggerEnabled(Trigger *trigger, Bitmapset *modifiedCols);
! 
! #define GetModifiedColumns(relinfo, estate) \
! 	(rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->modifiedCols)
  
  
  /*
*************** CreateTrigger(CreateTrigStmt *stmt,
*** 98,103 ****
--- 105,112 ----
  			  bool checkPermissions)
  {
  	int16		tgtype;
+ 	int			ncolumns;
+ 	int2       *columns;
  	int2vector *tgattr;
  	Datum		values[Natts_pg_trigger];
  	bool		nulls[Natts_pg_trigger];
*************** CreateTrigger(CreateTrigStmt *stmt,
*** 337,344 ****
  														CStringGetDatum(""));
  	}
  
! 	/* tgattr is currently always a zero-length array */
! 	tgattr = buildint2vector(NULL, 0);
  	values[Anum_pg_trigger_tgattr - 1] = PointerGetDatum(tgattr);
  
  	tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
--- 346,384 ----
  														CStringGetDatum(""));
  	}
  
! 	/* build column references for UPDATE OF */
! 	ncolumns = list_length(stmt->columns);
! 	if (ncolumns == 0)
! 		columns = NULL;
! 	else
! 	{
! 		ListCell   *cell;
! 		int			x = 0;
! 
! 		columns = (int2 *) palloc(ncolumns * sizeof(int2));
! 
! 		foreach (cell, stmt->columns)
! 		{
! 			char   *name = strVal(lfirst(cell));
! 			int		attnum;
! 			int		y;
! 
! 			/* Lookup column name. System columns are not allowed. */
! 			attnum = attnameAttNum(rel, name, false);
! 
! 			/* Check for duplicates */
! 			for (y = x - 1; y >= 0; y--)
! 			{
! 				if (columns[y] == attnum)
! 					ereport(ERROR,
! 						(errcode(ERRCODE_DUPLICATE_COLUMN),
! 						 errmsg("column \"%s\" specified more than once", name)));
! 			}
! 
! 			columns[x++] = attnum;
! 		}
! 	}
! 	tgattr = buildint2vector(columns, ncolumns);
  	values[Anum_pg_trigger_tgattr - 1] = PointerGetDatum(tgattr);
  
  	tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
*************** CreateTrigger(CreateTrigStmt *stmt,
*** 434,439 ****
--- 474,496 ----
  		Assert(!OidIsValid(indexOid));
  	}
  
+ 	/* Add dependency on columns */
+ 	if (columns != NULL)
+ 	{
+ 		int					i;
+ 		Form_pg_attribute  *attrs;
+ 
+ 		attrs = RelationGetDescr(rel)->attrs;
+ 
+ 		referenced.classId = RelationRelationId;
+ 		referenced.objectId = RelationGetRelid(rel);
+ 		for (i = 0; i < ncolumns; i++)
+ 		{
+ 			referenced.objectSubId = attrs[columns[i] - 1]->attnum;
+ 			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ 		}
+ 	}
+ 
  	/* Keep lock on target rel until end of xact */
  	heap_close(rel, NoLock);
  
*************** ExecBSInsertTriggers(EState *estate, Res
*** 1626,1643 ****
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  		HeapTuple	newtuple;
  
! 		if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! 		{
! 			if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
! 				trigger->tgenabled == TRIGGER_DISABLED)
! 				continue;
! 		}
! 		else	/* ORIGIN or LOCAL role */
! 		{
! 			if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
! 				trigger->tgenabled == TRIGGER_DISABLED)
! 				continue;
! 		}
  		LocTriggerData.tg_trigger = trigger;
  		newtuple = ExecCallTriggerFunc(&LocTriggerData,
  									   tgindx[i],
--- 1683,1691 ----
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  		HeapTuple	newtuple;
  
! 		if (!TriggerEnabled(trigger, NULL))
! 			continue;
! 
  		LocTriggerData.tg_trigger = trigger;
  		newtuple = ExecCallTriggerFunc(&LocTriggerData,
  									   tgindx[i],
*************** ExecASInsertTriggers(EState *estate, Res
*** 1659,1665 ****
  
  	if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
  		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! 							  false, NULL, NULL, NIL);
  }
  
  HeapTuple
--- 1707,1713 ----
  
  	if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
  		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! 							  false, NULL, NULL, NIL, NULL);
  }
  
  HeapTuple
*************** ExecBRInsertTriggers(EState *estate, Res
*** 1685,1702 ****
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  
! 		if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! 		{
! 			if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
! 				trigger->tgenabled == TRIGGER_DISABLED)
! 				continue;
! 		}
! 		else	/* ORIGIN or LOCAL role */
! 		{
! 			if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
! 				trigger->tgenabled == TRIGGER_DISABLED)
! 				continue;
! 		}
  		LocTriggerData.tg_trigtuple = oldtuple = newtuple;
  		LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
  		LocTriggerData.tg_trigger = trigger;
--- 1733,1741 ----
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  
! 		if (!TriggerEnabled(trigger, NULL))
! 			continue;
! 
  		LocTriggerData.tg_trigtuple = oldtuple = newtuple;
  		LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
  		LocTriggerData.tg_trigger = trigger;
*************** ExecARInsertTriggers(EState *estate, Res
*** 1721,1727 ****
  
  	if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
  		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! 							  true, NULL, trigtuple, recheckIndexes);
  }
  
  void
--- 1760,1766 ----
  
  	if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
  		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! 							  true, NULL, trigtuple, recheckIndexes, NULL);
  }
  
  void
*************** ExecBSDeleteTriggers(EState *estate, Res
*** 1757,1774 ****
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  		HeapTuple	newtuple;
  
! 		if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! 		{
! 			if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
! 				trigger->tgenabled == TRIGGER_DISABLED)
! 				continue;
! 		}
! 		else	/* ORIGIN or LOCAL role */
! 		{
! 			if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
! 				trigger->tgenabled == TRIGGER_DISABLED)
! 				continue;
! 		}
  		LocTriggerData.tg_trigger = trigger;
  		newtuple = ExecCallTriggerFunc(&LocTriggerData,
  									   tgindx[i],
--- 1796,1804 ----
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  		HeapTuple	newtuple;
  
! 		if (!TriggerEnabled(trigger, NULL))
! 			continue;
! 
  		LocTriggerData.tg_trigger = trigger;
  		newtuple = ExecCallTriggerFunc(&LocTriggerData,
  									   tgindx[i],
*************** ExecASDeleteTriggers(EState *estate, Res
*** 1790,1796 ****
  
  	if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
  		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! 							  false, NULL, NULL, NIL);
  }
  
  bool
--- 1820,1826 ----
  
  	if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
  		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! 							  false, NULL, NULL, NIL, NULL);
  }
  
  bool
*************** ExecBRDeleteTriggers(EState *estate, Pla
*** 1824,1841 ****
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  
! 		if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! 		{
! 			if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
! 				trigger->tgenabled == TRIGGER_DISABLED)
! 				continue;
! 		}
! 		else	/* ORIGIN or LOCAL role */
! 		{
! 			if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
! 				trigger->tgenabled == TRIGGER_DISABLED)
! 				continue;
! 		}
  		LocTriggerData.tg_trigtuple = trigtuple;
  		LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
  		LocTriggerData.tg_trigger = trigger;
--- 1854,1862 ----
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  
! 		if (!TriggerEnabled(trigger, NULL))
! 			continue;
! 
  		LocTriggerData.tg_trigtuple = trigtuple;
  		LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
  		LocTriggerData.tg_trigger = trigger;
*************** ExecARDeleteTriggers(EState *estate, Res
*** 1869,1875 ****
  												   tupleid, NULL);
  
  		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! 							  true, trigtuple, NULL, NIL);
  		heap_freetuple(trigtuple);
  	}
  }
--- 1890,1896 ----
  												   tupleid, NULL);
  
  		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! 							  true, trigtuple, NULL, NIL, NULL);
  		heap_freetuple(trigtuple);
  	}
  }
*************** ExecBSUpdateTriggers(EState *estate, Res
*** 1882,1887 ****
--- 1903,1909 ----
  	int		   *tgindx;
  	int			i;
  	TriggerData LocTriggerData;
+ 	Bitmapset   *modifiedCols;
  
  	trigdesc = relinfo->ri_TrigDesc;
  
*************** ExecBSUpdateTriggers(EState *estate, Res
*** 1894,1899 ****
--- 1916,1923 ----
  	if (ntrigs == 0)
  		return;
  
+ 	modifiedCols = GetModifiedColumns(relinfo, estate);
+ 
  	LocTriggerData.type = T_TriggerData;
  	LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
  		TRIGGER_EVENT_BEFORE;
*************** ExecBSUpdateTriggers(EState *estate, Res
*** 1907,1924 ****
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  		HeapTuple	newtuple;
  
! 		if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! 		{
! 			if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
! 				trigger->tgenabled == TRIGGER_DISABLED)
! 				continue;
! 		}
! 		else	/* ORIGIN or LOCAL role */
! 		{
! 			if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
! 				trigger->tgenabled == TRIGGER_DISABLED)
! 				continue;
! 		}
  		LocTriggerData.tg_trigger = trigger;
  		newtuple = ExecCallTriggerFunc(&LocTriggerData,
  									   tgindx[i],
--- 1931,1939 ----
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  		HeapTuple	newtuple;
  
! 		if (!TriggerEnabled(trigger, modifiedCols))
! 			continue;
! 
  		LocTriggerData.tg_trigger = trigger;
  		newtuple = ExecCallTriggerFunc(&LocTriggerData,
  									   tgindx[i],
*************** ExecASUpdateTriggers(EState *estate, Res
*** 1940,1946 ****
  
  	if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
  		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! 							  false, NULL, NULL, NIL);
  }
  
  HeapTuple
--- 1955,1962 ----
  
  	if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
  		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! 							  false, NULL, NULL, NIL,
! 							  GetModifiedColumns(relinfo, estate));
  }
  
  HeapTuple
*************** ExecBRUpdateTriggers(EState *estate, Pla
*** 1957,1968 ****
--- 1973,1987 ----
  	HeapTuple	intuple = newtuple;
  	TupleTableSlot *newSlot;
  	int			i;
+ 	Bitmapset   *modifiedCols;
  
  	trigtuple = GetTupleForTrigger(estate, subplanstate, relinfo, tupleid,
  								   &newSlot);
  	if (trigtuple == NULL)
  		return NULL;
  
+ 	modifiedCols = GetModifiedColumns(relinfo, estate);
+ 
  	/*
  	 * In READ COMMITTED isolation level it's possible that newtuple was
  	 * changed due to concurrent update.  In that case we have a raw subplan
*************** ExecBRUpdateTriggers(EState *estate, Pla
*** 1980,1997 ****
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  
! 		if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! 		{
! 			if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
! 				trigger->tgenabled == TRIGGER_DISABLED)
! 				continue;
! 		}
! 		else	/* ORIGIN or LOCAL role */
! 		{
! 			if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
! 				trigger->tgenabled == TRIGGER_DISABLED)
! 				continue;
! 		}
  		LocTriggerData.tg_trigtuple = trigtuple;
  		LocTriggerData.tg_newtuple = oldtuple = newtuple;
  		LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
--- 1999,2007 ----
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  
! 		if (!TriggerEnabled(trigger, modifiedCols))
! 			continue;
! 
  		LocTriggerData.tg_trigtuple = trigtuple;
  		LocTriggerData.tg_newtuple = oldtuple = newtuple;
  		LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
*************** ExecARUpdateTriggers(EState *estate, Res
*** 2024,2030 ****
  												   tupleid, NULL);
  
  		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! 							  true, trigtuple, newtuple, recheckIndexes);
  		heap_freetuple(trigtuple);
  	}
  }
--- 2034,2041 ----
  												   tupleid, NULL);
  
  		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! 							  true, trigtuple, newtuple, recheckIndexes,
! 							  GetModifiedColumns(relinfo, estate));
  		heap_freetuple(trigtuple);
  	}
  }
*************** ExecBSTruncateTriggers(EState *estate, R
*** 2062,2079 ****
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  		HeapTuple	newtuple;
  
! 		if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! 		{
! 			if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
! 				trigger->tgenabled == TRIGGER_DISABLED)
! 				continue;
! 		}
! 		else	/* ORIGIN or LOCAL role */
! 		{
! 			if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
! 				trigger->tgenabled == TRIGGER_DISABLED)
! 				continue;
! 		}
  		LocTriggerData.tg_trigger = trigger;
  		newtuple = ExecCallTriggerFunc(&LocTriggerData,
  									   tgindx[i],
--- 2073,2081 ----
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  		HeapTuple	newtuple;
  
! 		if (!TriggerEnabled(trigger, NULL))
! 			continue;
! 
  		LocTriggerData.tg_trigger = trigger;
  		newtuple = ExecCallTriggerFunc(&LocTriggerData,
  									   tgindx[i],
*************** ExecASTruncateTriggers(EState *estate, R
*** 2095,2101 ****
  
  	if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0)
  		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE,
! 							  false, NULL, NULL, NIL);
  }
  
  
--- 2097,2103 ----
  
  	if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0)
  		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE,
! 							  false, NULL, NULL, NIL, NULL);
  }
  
  
*************** AfterTriggerPendingOnRel(Oid relid)
*** 3825,3831 ****
  static void
  AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
  					  HeapTuple oldtup, HeapTuple newtup,
! 					  List *recheckIndexes)
  {
  	Relation	rel = relinfo->ri_RelationDesc;
  	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
--- 3827,3833 ----
  static void
  AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
  					  HeapTuple oldtup, HeapTuple newtup,
! 					  List *recheckIndexes, Bitmapset *modifiedCols)
  {
  	Relation	rel = relinfo->ri_RelationDesc;
  	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
*************** AfterTriggerSaveEvent(ResultRelInfo *rel
*** 3927,3945 ****
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  
! 		/* Ignore disabled triggers */
! 		if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
! 		{
! 			if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
! 				trigger->tgenabled == TRIGGER_DISABLED)
! 				continue;
! 		}
! 		else	/* ORIGIN or LOCAL role */
! 		{
! 			if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
! 				trigger->tgenabled == TRIGGER_DISABLED)
! 				continue;
! 		}
  
  		/*
  		 * If this is an UPDATE of a PK table or FK table that does not change
--- 3929,3936 ----
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  
! 		if (!TriggerEnabled(trigger, modifiedCols))
! 			continue;
  
  		/*
  		 * If this is an UPDATE of a PK table or FK table that does not change
*************** AfterTriggerSaveEvent(ResultRelInfo *rel
*** 4012,4014 ****
--- 4003,4044 ----
  							 &new_event, &new_shared);
  	}
  }
+ 
+ static bool
+ TriggerEnabled(Trigger *trigger, Bitmapset *modifiedCols)
+ {
+ 	if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
+ 	{
+ 		if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
+ 			trigger->tgenabled == TRIGGER_DISABLED)
+ 			return false;
+ 	}
+ 	else	/* ORIGIN or LOCAL role */
+ 	{
+ 		if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
+ 			trigger->tgenabled == TRIGGER_DISABLED)
+ 			return false;
+ 	}
+ 
+ 	/* Check for column-level triggers */
+ 	if (trigger->tgnattr > 0 && modifiedCols != NULL &&
+ 		(trigger->tgtype & TRIGGER_TYPE_UPDATE) != 0)
+ 	{
+ 		int			i;
+ 		bool		modified;
+ 
+ 		modified = false;
+ 		for (i = 0; i < trigger->tgnattr; i++)
+ 		{
+ 			if (bms_is_member(trigger->tgattr[i] - FirstLowInvalidHeapAttributeNumber, modifiedCols))
+ 			{
+ 				modified = true;
+ 				break;
+ 			}
+ 		}
+ 		if (!modified)
+ 			return false;
+ 	}
+ 
+ 	return true;
+ }
diff -cprN head/src/backend/parser/gram.y work/src/backend/parser/gram.y
*** head/src/backend/parser/gram.y	2009-10-13 08:41:43.000000000 +0900
--- work/src/backend/parser/gram.y	2009-10-14 12:15:45.348019975 +0900
*************** static TypeName *TableFuncTypeName(List 
*** 251,257 ****
  
  %type <boolean> TriggerActionTime TriggerForSpec opt_trusted opt_restart_seqs
  
! %type <ival>	TriggerEvents TriggerOneEvent
  %type <value>	TriggerFuncArg
  
  %type <str>		relation_name copy_file_name
--- 251,257 ----
  
  %type <boolean> TriggerActionTime TriggerForSpec opt_trusted opt_restart_seqs
  
! %type <list>	TriggerEvents TriggerOneEvent
  %type <value>	TriggerFuncArg
  
  %type <str>		relation_name copy_file_name
*************** CreateTrigStmt:
*** 3240,3246 ****
  					n->args = $13;
  					n->before = $4;
  					n->row = $8;
! 					n->events = $5;
  					n->isconstraint  = FALSE;
  					n->deferrable	 = FALSE;
  					n->initdeferred  = FALSE;
--- 3240,3247 ----
  					n->args = $13;
  					n->before = $4;
  					n->row = $8;
! 					n->events = intVal(linitial($5));
! 					n->columns = llast($5);
  					n->isconstraint  = FALSE;
  					n->deferrable	 = FALSE;
  					n->initdeferred  = FALSE;
*************** CreateTrigStmt:
*** 3260,3266 ****
  					n->args = $18;
  					n->before = FALSE;
  					n->row = TRUE;
! 					n->events = $6;
  					n->isconstraint  = TRUE;
  					n->deferrable = ($10 & 1) != 0;
  					n->initdeferred = ($10 & 2) != 0;
--- 3261,3268 ----
  					n->args = $18;
  					n->before = FALSE;
  					n->row = TRUE;
! 					n->events = intVal(linitial($6));
! 					n->columns = llast($6);
  					n->isconstraint  = TRUE;
  					n->deferrable = ($10 & 1) != 0;
  					n->initdeferred = ($10 & 2) != 0;
*************** TriggerEvents:
*** 3279,3295 ****
  				{ $$ = $1; }
  			| TriggerEvents OR TriggerOneEvent
  				{
! 					if ($1 & $3)
  						parser_yyerror("duplicate trigger events specified");
! 					$$ = $1 | $3;
  				}
  		;
  
  TriggerOneEvent:
! 			INSERT								{ $$ = TRIGGER_TYPE_INSERT; }
! 			| DELETE_P							{ $$ = TRIGGER_TYPE_DELETE; }
! 			| UPDATE							{ $$ = TRIGGER_TYPE_UPDATE; }
! 			| TRUNCATE							{ $$ = TRIGGER_TYPE_TRUNCATE; }
  		;
  
  TriggerForSpec:
--- 3281,3302 ----
  				{ $$ = $1; }
  			| TriggerEvents OR TriggerOneEvent
  				{
! 					int		events1 = intVal(linitial($1));
! 					int		events2 = intVal(linitial($3));
! 
! 					if (events1 & events2)
  						parser_yyerror("duplicate trigger events specified");
! 					$$ = list_make2(makeInteger(events1 | events2),
! 										list_concat(llast($1), llast($3)));
  				}
  		;
  
  TriggerOneEvent:
! 			INSERT					{ $$ = list_make2(makeInteger(TRIGGER_TYPE_INSERT), NIL); }
! 			| DELETE_P				{ $$ = list_make2(makeInteger(TRIGGER_TYPE_DELETE), NIL); }
! 			| UPDATE				{ $$ = list_make2(makeInteger(TRIGGER_TYPE_UPDATE), NIL); }
! 			| UPDATE OF columnList	{ $$ = list_make2(makeInteger(TRIGGER_TYPE_UPDATE), $3); }
! 			| TRUNCATE				{ $$ = list_make2(makeInteger(TRIGGER_TYPE_TRUNCATE), NIL); }
  		;
  
  TriggerForSpec:
diff -cprN head/src/backend/utils/adt/ruleutils.c work/src/backend/utils/adt/ruleutils.c
*** head/src/backend/utils/adt/ruleutils.c	2009-10-10 10:43:49.000000000 +0900
--- work/src/backend/utils/adt/ruleutils.c	2009-10-14 12:15:45.350019985 +0900
*************** pg_get_triggerdef_worker(Oid trigid, boo
*** 543,548 ****
--- 543,562 ----
  			appendStringInfo(&buf, " OR UPDATE");
  		else
  			appendStringInfo(&buf, " UPDATE");
+ 		if (trigrec->tgattr.dim1 > 0)
+ 		{
+ 			int		i;
+ 
+ 			appendStringInfoString(&buf, " OF ");
+ 			for (i = 0; i < trigrec->tgattr.dim1; i++)
+ 			{
+ 				if (i > 0)
+ 					appendStringInfoString(&buf, ", ");
+ 				appendStringInfoString(&buf,
+ 					quote_identifier(get_relid_attribute_name(
+ 					trigrec->tgrelid, trigrec->tgattr.values[i])));
+ 			}
+ 		}
  	}
  	if (TRIGGER_FOR_TRUNCATE(trigrec->tgtype))
  	{
diff -cprN head/src/include/catalog/pg_trigger.h work/src/include/catalog/pg_trigger.h
*** head/src/include/catalog/pg_trigger.h	2009-07-28 11:56:31.000000000 +0900
--- work/src/include/catalog/pg_trigger.h	2009-10-14 12:15:45.350019985 +0900
*************** CATALOG(pg_trigger,2620)
*** 53,59 ****
  	int2		tgnargs;		/* # of extra arguments in tgargs */
  
  	/* VARIABLE LENGTH FIELDS: */
! 	int2vector	tgattr;			/* reserved for column-specific triggers */
  	bytea		tgargs;			/* first\000second\000tgnargs\000 */
  } FormData_pg_trigger;
  
--- 53,59 ----
  	int2		tgnargs;		/* # of extra arguments in tgargs */
  
  	/* VARIABLE LENGTH FIELDS: */
! 	int2vector	tgattr;			/* column-specific triggers */
  	bytea		tgargs;			/* first\000second\000tgnargs\000 */
  } FormData_pg_trigger;
  
diff -cprN head/src/include/nodes/parsenodes.h work/src/include/nodes/parsenodes.h
*** head/src/include/nodes/parsenodes.h	2009-10-13 09:53:08.000000000 +0900
--- work/src/include/nodes/parsenodes.h	2009-10-14 12:15:45.351020361 +0900
*************** typedef struct CreateTrigStmt
*** 1575,1580 ****
--- 1575,1581 ----
  	bool		row;			/* ROW/STATEMENT */
  	/* events uses the TRIGGER_TYPE bits defined in catalog/pg_trigger.h */
  	int16		events;			/* INSERT/UPDATE/DELETE/TRUNCATE */
+ 	List	   *columns;		/* column names, or NIL for all columns */
  
  	/* The following are used for constraint triggers (RI and unique checks) */
  	bool		isconstraint;	/* This is a constraint trigger */
diff -cprN head/src/test/regress/expected/triggers.out work/src/test/regress/expected/triggers.out
*** head/src/test/regress/expected/triggers.out	2009-10-08 01:27:18.000000000 +0900
--- work/src/test/regress/expected/triggers.out	2009-10-14 13:25:21.409894000 +0900
*************** CREATE TABLE main_table (a int, b int);
*** 278,314 ****
  COPY main_table (a,b) FROM stdin;
  CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
  BEGIN
! 	RAISE NOTICE ''trigger_func() called: action = %, when = %, level = %'', TG_OP, TG_WHEN, TG_LEVEL;
  	RETURN NULL;
  END;';
  CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
  CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
  --
  -- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
  -- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
  --
! CREATE TRIGGER before_upd_stmt_trig AFTER UPDATE ON main_table
! EXECUTE PROCEDURE trigger_func();
! CREATE TRIGGER before_upd_row_trig AFTER UPDATE ON main_table
! FOR EACH ROW EXECUTE PROCEDURE trigger_func();
  INSERT INTO main_table DEFAULT VALUES;
! NOTICE:  trigger_func() called: action = INSERT, when = BEFORE, level = STATEMENT
! NOTICE:  trigger_func() called: action = INSERT, when = AFTER, level = STATEMENT
  UPDATE main_table SET a = a + 1 WHERE b < 30;
! NOTICE:  trigger_func() called: action = UPDATE, when = AFTER, level = ROW
! NOTICE:  trigger_func() called: action = UPDATE, when = AFTER, level = ROW
! NOTICE:  trigger_func() called: action = UPDATE, when = AFTER, level = ROW
! NOTICE:  trigger_func() called: action = UPDATE, when = AFTER, level = ROW
! NOTICE:  trigger_func() called: action = UPDATE, when = AFTER, level = STATEMENT
  -- UPDATE that effects zero rows should still call per-statement trigger
  UPDATE main_table SET a = a + 2 WHERE b > 100;
! NOTICE:  trigger_func() called: action = UPDATE, when = AFTER, level = STATEMENT
  -- COPY should fire per-row and per-statement INSERT triggers
  COPY main_table (a, b) FROM stdin;
! NOTICE:  trigger_func() called: action = INSERT, when = BEFORE, level = STATEMENT
! NOTICE:  trigger_func() called: action = INSERT, when = AFTER, level = STATEMENT
  SELECT * FROM main_table ORDER BY a, b;
   a  | b  
  ----+----
--- 278,314 ----
  COPY main_table (a,b) FROM stdin;
  CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
  BEGIN
! 	RAISE NOTICE ''trigger_func(%) called: action = %, when = %, level = %'', TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL;
  	RETURN NULL;
  END;';
  CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_ins_stmt');
  CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_ins_stmt');
  --
  -- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
  -- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
  --
! CREATE TRIGGER after_upd_stmt_trig AFTER UPDATE ON main_table
! EXECUTE PROCEDURE trigger_func('after_upd_stmt');
! CREATE TRIGGER after_upd_row_trig AFTER UPDATE ON main_table
! FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_row');
  INSERT INTO main_table DEFAULT VALUES;
! NOTICE:  trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT
! NOTICE:  trigger_func(after_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT
  UPDATE main_table SET a = a + 1 WHERE b < 30;
! NOTICE:  trigger_func(after_upd_row) called: action = UPDATE, when = AFTER, level = ROW
! NOTICE:  trigger_func(after_upd_row) called: action = UPDATE, when = AFTER, level = ROW
! NOTICE:  trigger_func(after_upd_row) called: action = UPDATE, when = AFTER, level = ROW
! NOTICE:  trigger_func(after_upd_row) called: action = UPDATE, when = AFTER, level = ROW
! NOTICE:  trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
  -- UPDATE that effects zero rows should still call per-statement trigger
  UPDATE main_table SET a = a + 2 WHERE b > 100;
! NOTICE:  trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
  -- COPY should fire per-row and per-statement INSERT triggers
  COPY main_table (a, b) FROM stdin;
! NOTICE:  trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT
! NOTICE:  trigger_func(after_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT
  SELECT * FROM main_table ORDER BY a, b;
   a  | b  
  ----+----
*************** SELECT * FROM main_table ORDER BY a, b;
*** 322,327 ****
--- 322,402 ----
      |   
  (8 rows)
  
+ -- Column-level triggers should only fire on after row-level updates
+ DROP TRIGGER after_upd_row_trig ON main_table;
+ CREATE TRIGGER before_upd_a_row_trig BEFORE UPDATE OF a ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_upd_a_row');
+ CREATE TRIGGER after_upd_b_row_trig AFTER UPDATE OF b ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_b_row');
+ CREATE TRIGGER after_upd_a_b_row_trig AFTER UPDATE OF a, b ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_a_b_row');
+ CREATE TRIGGER before_upd_a_stmt_trig BEFORE UPDATE OF a ON main_table
+ FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_upd_a_stmt');
+ CREATE TRIGGER after_upd_b_stmt_trig AFTER UPDATE OF b ON main_table
+ FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_upd_b_stmt');
+ SELECT pg_get_triggerdef(oid) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'after_upd_a_b_row_trig';
+                                                              pg_get_triggerdef                                                             
+ -------------------------------------------------------------------------------------------------------------------------------------------
+  CREATE TRIGGER after_upd_a_b_row_trig AFTER UPDATE OF a, b ON main_table FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_a_b_row')
+ (1 row)
+ 
+ SELECT pg_get_triggerdef(oid, true) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'after_upd_a_b_row_trig';
+                     pg_get_triggerdef                    
+ ---------------------------------------------------------
+  CREATE TRIGGER after_upd_a_b_row_trig
+      AFTER UPDATE OF a, b ON main_table
+      FOR EACH ROW
+      EXECUTE PROCEDURE trigger_func('after_upd_a_b_row')
+ (1 row)
+ 
+ UPDATE main_table SET a = 50;
+ NOTICE:  trigger_func(before_upd_a_stmt) called: action = UPDATE, when = BEFORE, level = STATEMENT
+ NOTICE:  trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW
+ NOTICE:  trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW
+ NOTICE:  trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW
+ NOTICE:  trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW
+ NOTICE:  trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW
+ NOTICE:  trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW
+ NOTICE:  trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW
+ NOTICE:  trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW
+ NOTICE:  trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
+ UPDATE main_table SET b = 10;
+ NOTICE:  trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW
+ NOTICE:  trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW
+ NOTICE:  trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW
+ NOTICE:  trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW
+ NOTICE:  trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW
+ NOTICE:  trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW
+ NOTICE:  trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW
+ NOTICE:  trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW
+ NOTICE:  trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW
+ NOTICE:  trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW
+ NOTICE:  trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW
+ NOTICE:  trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW
+ NOTICE:  trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW
+ NOTICE:  trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW
+ NOTICE:  trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW
+ NOTICE:  trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW
+ NOTICE:  trigger_func(after_upd_b_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
+ NOTICE:  trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
+ CREATE TRIGGER error_upd_and_col BEFORE UPDATE OR UPDATE OF a ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('error_upd_and_col');
+ ERROR:  duplicate trigger events specified at or near "ON"
+ LINE 1: ...ER error_upd_and_col BEFORE UPDATE OR UPDATE OF a ON main_ta...
+                                                              ^
+ CREATE TRIGGER error_upd_a_a BEFORE UPDATE OF a, a ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('error_upd_a_a');
+ ERROR:  column "a" specified more than once
+ ALTER TABLE main_table DROP COLUMN b;
+ ERROR:  cannot drop table main_table column b because other objects depend on it
+ DETAIL:  trigger after_upd_b_row_trig on table main_table depends on table main_table column b
+ trigger after_upd_a_b_row_trig on table main_table depends on table main_table column b
+ trigger after_upd_b_stmt_trig on table main_table depends on table main_table column b
+ HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+ DROP TRIGGER after_upd_a_b_row_trig ON main_table;
+ DROP TRIGGER after_upd_b_row_trig ON main_table;
+ DROP TRIGGER after_upd_b_stmt_trig ON main_table;
+ ALTER TABLE main_table DROP COLUMN b;
  -- Test enable/disable triggers
  create table trigtest (i serial primary key);
  NOTICE:  CREATE TABLE will create implicit sequence "trigtest_i_seq" for serial column "trigtest.i"
diff -cprN head/src/test/regress/sql/triggers.sql work/src/test/regress/sql/triggers.sql
*** head/src/test/regress/sql/triggers.sql	2009-10-08 01:27:18.000000000 +0900
--- work/src/test/regress/sql/triggers.sql	2009-10-14 13:23:27.437208181 +0900
*************** COPY main_table (a,b) FROM stdin;
*** 220,244 ****
  
  CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
  BEGIN
! 	RAISE NOTICE ''trigger_func() called: action = %, when = %, level = %'', TG_OP, TG_WHEN, TG_LEVEL;
  	RETURN NULL;
  END;';
  
  CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
  
  CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
  
  --
  -- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
  -- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
  --
! CREATE TRIGGER before_upd_stmt_trig AFTER UPDATE ON main_table
! EXECUTE PROCEDURE trigger_func();
  
! CREATE TRIGGER before_upd_row_trig AFTER UPDATE ON main_table
! FOR EACH ROW EXECUTE PROCEDURE trigger_func();
  
  INSERT INTO main_table DEFAULT VALUES;
  
--- 220,244 ----
  
  CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
  BEGIN
! 	RAISE NOTICE ''trigger_func(%) called: action = %, when = %, level = %'', TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL;
  	RETURN NULL;
  END;';
  
  CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_ins_stmt');
  
  CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_ins_stmt');
  
  --
  -- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
  -- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
  --
! CREATE TRIGGER after_upd_stmt_trig AFTER UPDATE ON main_table
! EXECUTE PROCEDURE trigger_func('after_upd_stmt');
  
! CREATE TRIGGER after_upd_row_trig AFTER UPDATE ON main_table
! FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_row');
  
  INSERT INTO main_table DEFAULT VALUES;
  
*************** COPY main_table (a, b) FROM stdin;
*** 254,259 ****
--- 254,291 ----
  
  SELECT * FROM main_table ORDER BY a, b;
  
+ -- Column-level triggers should only fire on after row-level updates
+ DROP TRIGGER after_upd_row_trig ON main_table;
+ 
+ CREATE TRIGGER before_upd_a_row_trig BEFORE UPDATE OF a ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_upd_a_row');
+ CREATE TRIGGER after_upd_b_row_trig AFTER UPDATE OF b ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_b_row');
+ CREATE TRIGGER after_upd_a_b_row_trig AFTER UPDATE OF a, b ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_a_b_row');
+ 
+ CREATE TRIGGER before_upd_a_stmt_trig BEFORE UPDATE OF a ON main_table
+ FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_upd_a_stmt');
+ CREATE TRIGGER after_upd_b_stmt_trig AFTER UPDATE OF b ON main_table
+ FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_upd_b_stmt');
+ 
+ SELECT pg_get_triggerdef(oid) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'after_upd_a_b_row_trig';
+ SELECT pg_get_triggerdef(oid, true) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'after_upd_a_b_row_trig';
+ 
+ UPDATE main_table SET a = 50;
+ UPDATE main_table SET b = 10;
+ 
+ CREATE TRIGGER error_upd_and_col BEFORE UPDATE OR UPDATE OF a ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('error_upd_and_col');
+ CREATE TRIGGER error_upd_a_a BEFORE UPDATE OF a, a ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('error_upd_a_a');
+ 
+ ALTER TABLE main_table DROP COLUMN b;
+ DROP TRIGGER after_upd_a_b_row_trig ON main_table;
+ DROP TRIGGER after_upd_b_row_trig ON main_table;
+ DROP TRIGGER after_upd_b_stmt_trig ON main_table;
+ ALTER TABLE main_table DROP COLUMN b;
+ 
  -- Test enable/disable triggers
  
  create table trigtest (i serial primary key);