diff -cprN head/doc/src/sgml/func.sgml standard-column-trigger/doc/src/sgml/func.sgml
*** head/doc/src/sgml/func.sgml	2009-08-17 04:55:21.000000000 +0900
--- standard-column-trigger/doc/src/sgml/func.sgml	2009-09-14 17:44:53.781713886 +0900
*************** SELECT pg_type_is_visible('myschema.widg
*** 12370,12375 ****
--- 12370,12380 ----
         <entry>get <command>CREATE [ CONSTRAINT ] TRIGGER</> command for trigger</entry>
        </row>
        <row>
+        <entry><function>pg_get_triggerdef</function>(<parameter>trigger_oid</parameter>, <parameter>pretty_bool</>)</entry>
+        <entry><type>text</type></entry>
+        <entry>get <command>CREATE [ CONSTRAINT ] TRIGGER</> command for trigger</entry>
+       </row>
+       <row>
         <entry><literal><function>pg_get_userbyid</function>(<parameter>role_oid</parameter>)</literal></entry>
         <entry><type>name</type></entry>
         <entry>get role name with given OID</entry>
diff -cprN head/doc/src/sgml/ref/create_trigger.sgml standard-column-trigger/doc/src/sgml/ref/create_trigger.sgml
*** head/doc/src/sgml/ref/create_trigger.sgml	2008-11-14 19:22:46.000000000 +0900
--- standard-column-trigger/doc/src/sgml/ref/create_trigger.sgml	2009-09-14 17:23:24.167839706 +0900
*************** CREATE TRIGGER <replaceable class="PARAM
*** 121,126 ****
--- 121,130 ----
        <command>DELETE</command>, or <command>TRUNCATE</command>;
        this specifies the event that will fire the trigger. Multiple
        events can be specified using <literal>OR</literal>.
+       An optional list of comma-separated columns can be given after
+       the <command>UPDATE</command> command, by adding the word
+       <literal>OF</literal> after <command>UPDATE</command> and before
+       the list of columns.
       </para>
      </listitem>
     </varlistentry>
diff -cprN head/doc/src/sgml/trigger.sgml standard-column-trigger/doc/src/sgml/trigger.sgml
*** head/doc/src/sgml/trigger.sgml	2009-08-05 07:04:37.000000000 +0900
--- standard-column-trigger/doc/src/sgml/trigger.sgml	2009-09-14 17:23:24.168760484 +0900
***************
*** 39,45 ****
      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>
--- 39,46 ----
      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. In addition, UPDATE triggers can be set to only fire if certain
!     columns are mentioned in SET columns of the UPDATE statement.
     </para>
  
     <para>
diff -cprN head/src/backend/commands/trigger.c standard-column-trigger/src/backend/commands/trigger.c
*** head/src/backend/commands/trigger.c	2009-08-05 01:08:36.000000000 +0900
--- standard-column-trigger/src/backend/commands/trigger.c	2009-09-14 17:23:24.268741789 +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
*** 65,71 ****
  					MemoryContext per_tuple_context);
  static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
  					  bool row_trigger, HeapTuple oldtup, HeapTuple newtup,
! 					  List *recheckIndexes);
  
  
  /*
--- 68,75 ----
  					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);
  
  
  /*
*************** CreateTrigger(CreateTrigStmt *stmt,
*** 97,102 ****
--- 101,108 ----
  			  bool checkPermissions)
  {
  	int16		tgtype;
+ 	int			ncolumns;
+ 	int2       *columns;
  	int2vector *tgattr;
  	Datum		values[Natts_pg_trigger];
  	bool		nulls[Natts_pg_trigger];
*************** CreateTrigger(CreateTrigStmt *stmt,
*** 336,343 ****
  														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);
--- 342,380 ----
  														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,
*** 433,438 ****
--- 470,492 ----
  		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
*** 1625,1642 ****
  		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],
--- 1679,1687 ----
  		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
*** 1658,1664 ****
  
  	if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
  		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! 							  false, NULL, NULL, NIL);
  }
  
  HeapTuple
--- 1703,1709 ----
  
  	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
*** 1684,1701 ****
  	{
  		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;
--- 1729,1737 ----
  	{
  		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
*** 1720,1726 ****
  
  	if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
  		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! 							  true, NULL, trigtuple, recheckIndexes);
  }
  
  void
--- 1756,1762 ----
  
  	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
*** 1756,1773 ****
  		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],
--- 1792,1800 ----
  		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
*** 1789,1795 ****
  
  	if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
  		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! 							  false, NULL, NULL, NIL);
  }
  
  bool
--- 1816,1822 ----
  
  	if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
  		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! 							  false, NULL, NULL, NIL, NULL);
  }
  
  bool
*************** ExecBRDeleteTriggers(EState *estate, Res
*** 1821,1838 ****
  	{
  		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;
--- 1848,1856 ----
  	{
  		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
*** 1866,1872 ****
  												   tupleid, NULL);
  
  		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! 							  true, trigtuple, NULL, NIL);
  		heap_freetuple(trigtuple);
  	}
  }
--- 1884,1890 ----
  												   tupleid, NULL);
  
  		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! 							  true, trigtuple, NULL, NIL, NULL);
  		heap_freetuple(trigtuple);
  	}
  }
*************** ExecBSUpdateTriggers(EState *estate, Res
*** 1904,1921 ****
  		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],
--- 1922,1930 ----
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  		HeapTuple	newtuple;
  
! 		if (!TriggerEnabled(trigger, NULL))
! 			continue;
! 
  		LocTriggerData.tg_trigger = trigger;
  		newtuple = ExecCallTriggerFunc(&LocTriggerData,
  									   tgindx[i],
*************** ExecASUpdateTriggers(EState *estate, Res
*** 1937,1943 ****
  
  	if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
  		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! 							  false, NULL, NULL, NIL);
  }
  
  HeapTuple
--- 1946,1952 ----
  
  	if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
  		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! 							  false, NULL, NULL, NIL, NULL);
  }
  
  HeapTuple
*************** ExecBRUpdateTriggers(EState *estate, Res
*** 1953,1963 ****
--- 1962,1975 ----
  	HeapTuple	intuple = newtuple;
  	TupleTableSlot *newSlot;
  	int			i;
+ 	Bitmapset   *modifiedCols;
  
  	trigtuple = GetTupleForTrigger(estate, relinfo, tupleid, &newSlot);
  	if (trigtuple == NULL)
  		return NULL;
  
+ 	modifiedCols = rt_fetch(relinfo->ri_RangeTableIndex, estate->es_range_table)->modifiedCols;
+ 
  	/*
  	 * In READ COMMITTED isolation level it's possible that newtuple was
  	 * changed due to concurrent update.
*************** ExecBRUpdateTriggers(EState *estate, Res
*** 1974,1991 ****
  	{
  		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;
--- 1986,1994 ----
  	{
  		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
*** 2018,2024 ****
  												   tupleid, NULL);
  
  		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! 							  true, trigtuple, newtuple, recheckIndexes);
  		heap_freetuple(trigtuple);
  	}
  }
--- 2021,2028 ----
  												   tupleid, NULL);
  
  		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! 							  true, trigtuple, newtuple, recheckIndexes,
! 							  rt_fetch(relinfo->ri_RangeTableIndex, estate->es_range_table)->modifiedCols);
  		heap_freetuple(trigtuple);
  	}
  }
*************** ExecBSTruncateTriggers(EState *estate, R
*** 2056,2073 ****
  		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],
--- 2060,2068 ----
  		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
*** 2089,2095 ****
  
  	if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0)
  		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE,
! 							  false, NULL, NULL, NIL);
  }
  
  
--- 2084,2090 ----
  
  	if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0)
  		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE,
! 							  false, NULL, NULL, NIL, NULL);
  }
  
  
*************** AfterTriggerPendingOnRel(Oid relid)
*** 3813,3819 ****
  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;
--- 3808,3814 ----
  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
*** 3915,3933 ****
  	{
  		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
--- 3910,3917 ----
  	{
  		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
*** 4000,4002 ****
--- 3984,4026 ----
  							 &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 | TRIGGER_TYPE_ROW)) ==
+ 		(TRIGGER_TYPE_UPDATE | TRIGGER_TYPE_ROW))
+ 	{
+ 		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 standard-column-trigger/src/backend/parser/gram.y
*** head/src/backend/parser/gram.y	2009-08-19 08:40:20.000000000 +0900
--- standard-column-trigger/src/backend/parser/gram.y	2009-09-14 17:23:24.271734848 +0900
*************** static TypeName *TableFuncTypeName(List 
*** 248,254 ****
  %type <boolean> TriggerActionTime TriggerForSpec opt_trusted opt_restart_seqs
  %type <str>		opt_lancompiler
  
! %type <ival>	TriggerEvents TriggerOneEvent
  %type <value>	TriggerFuncArg
  
  %type <str>		relation_name copy_file_name
--- 248,254 ----
  %type <boolean> TriggerActionTime TriggerForSpec opt_trusted opt_restart_seqs
  %type <str>		opt_lancompiler
  
! %type <list>	TriggerEvents TriggerOneEvent
  %type <value>	TriggerFuncArg
  
  %type <str>		relation_name copy_file_name
*************** CreateTrigStmt:
*** 3160,3166 ****
  					n->args = $13;
  					n->before = $4;
  					n->row = $8;
! 					n->events = $5;
  					n->isconstraint  = FALSE;
  					n->deferrable	 = FALSE;
  					n->initdeferred  = FALSE;
--- 3160,3167 ----
  					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:
*** 3180,3186 ****
  					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;
--- 3181,3188 ----
  					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:
*** 3199,3215 ****
  				{ $$ = $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:
--- 3201,3222 ----
  				{ $$ = $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 standard-column-trigger/src/backend/utils/adt/ruleutils.c
*** head/src/backend/utils/adt/ruleutils.c	2009-08-02 04:59:41.000000000 +0900
--- standard-column-trigger/src/backend/utils/adt/ruleutils.c	2009-09-14 17:59:36.415778604 +0900
*************** static char *deparse_expression_pretty(N
*** 139,144 ****
--- 139,145 ----
  						  bool forceprefix, bool showimplicit,
  						  int prettyFlags, int startIndent);
  static char *pg_get_viewdef_worker(Oid viewoid, int prettyFlags);
+ static char *pg_get_triggerdef_worker(Oid trigid, bool pretty);
  static void decompile_column_index_array(Datum column_index_array, Oid relId,
  							 StringInfo buf);
  static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
*************** Datum
*** 462,467 ****
--- 463,484 ----
  pg_get_triggerdef(PG_FUNCTION_ARGS)
  {
  	Oid			trigid = PG_GETARG_OID(0);
+ 
+ 	PG_RETURN_TEXT_P(string_to_text(pg_get_triggerdef_worker(trigid, 0)));
+ }
+ 
+ Datum
+ pg_get_triggerdef_ext(PG_FUNCTION_ARGS)
+ {
+ 	Oid			trigid = PG_GETARG_OID(0);
+ 	bool		pretty = PG_GETARG_BOOL(1);
+ 
+ 	PG_RETURN_TEXT_P(string_to_text(pg_get_triggerdef_worker(trigid, pretty)));
+ }
+ 
+ static char *
+ pg_get_triggerdef_worker(Oid trigid, bool pretty)
+ {
  	HeapTuple	ht_trig;
  	Form_pg_trigger trigrec;
  	StringInfoData buf;
*************** pg_get_triggerdef(PG_FUNCTION_ARGS)
*** 498,506 ****
  	initStringInfo(&buf);
  
  	tgname = NameStr(trigrec->tgname);
! 	appendStringInfo(&buf, "CREATE %sTRIGGER %s ",
  					 trigrec->tgisconstraint ? "CONSTRAINT " : "",
! 					 quote_identifier(tgname));
  
  	if (TRIGGER_FOR_BEFORE(trigrec->tgtype))
  		appendStringInfo(&buf, "BEFORE");
--- 515,524 ----
  	initStringInfo(&buf);
  
  	tgname = NameStr(trigrec->tgname);
! 	appendStringInfo(&buf, "CREATE %sTRIGGER %s%s",
  					 trigrec->tgisconstraint ? "CONSTRAINT " : "",
! 					 quote_identifier(tgname),
! 					 (pretty ? "\n    " : " "));
  
  	if (TRIGGER_FOR_BEFORE(trigrec->tgtype))
  		appendStringInfo(&buf, "BEFORE");
*************** pg_get_triggerdef(PG_FUNCTION_ARGS)
*** 525,530 ****
--- 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))
  	{
*************** pg_get_triggerdef(PG_FUNCTION_ARGS)
*** 533,561 ****
  		else
  			appendStringInfo(&buf, " TRUNCATE");
  	}
! 	appendStringInfo(&buf, " ON %s ",
! 					 generate_relation_name(trigrec->tgrelid, NIL));
  
  	if (trigrec->tgisconstraint)
  	{
  		if (trigrec->tgconstrrelid != InvalidOid)
! 			appendStringInfo(&buf, "FROM %s ",
! 							 generate_relation_name(trigrec->tgconstrrelid,
! 													NIL));
  		if (!trigrec->tgdeferrable)
  			appendStringInfo(&buf, "NOT ");
  		appendStringInfo(&buf, "DEFERRABLE INITIALLY ");
  		if (trigrec->tginitdeferred)
! 			appendStringInfo(&buf, "DEFERRED ");
  		else
! 			appendStringInfo(&buf, "IMMEDIATE ");
! 
  	}
  
  	if (TRIGGER_FOR_ROW(trigrec->tgtype))
! 		appendStringInfo(&buf, "FOR EACH ROW ");
  	else
! 		appendStringInfo(&buf, "FOR EACH STATEMENT ");
  
  	appendStringInfo(&buf, "EXECUTE PROCEDURE %s(",
  					 generate_function_name(trigrec->tgfoid, 0, NULL, NULL));
--- 565,595 ----
  		else
  			appendStringInfo(&buf, " TRUNCATE");
  	}
! 	appendStringInfo(&buf, " ON %s%s",
! 					 generate_relation_name(trigrec->tgrelid, NIL),
! 					 (pretty ? "\n    " : " "));
  
  	if (trigrec->tgisconstraint)
  	{
  		if (trigrec->tgconstrrelid != InvalidOid)
! 			appendStringInfo(&buf, "FROM %s%s",
! 						 generate_relation_name(trigrec->tgconstrrelid, NIL),
! 						 (pretty ? "\n    " : " "));
  		if (!trigrec->tgdeferrable)
  			appendStringInfo(&buf, "NOT ");
  		appendStringInfo(&buf, "DEFERRABLE INITIALLY ");
  		if (trigrec->tginitdeferred)
! 			appendStringInfo(&buf, "DEFERRED");
  		else
! 			appendStringInfo(&buf, "IMMEDIATE");
! 		appendStringInfoString(&buf, pretty ? "\n    " : " ");
  	}
  
  	if (TRIGGER_FOR_ROW(trigrec->tgtype))
! 		appendStringInfo(&buf, "FOR EACH ROW");
  	else
! 		appendStringInfo(&buf, "FOR EACH STATEMENT");
! 	appendStringInfoString(&buf, pretty ? "\n    " : " ");
  
  	appendStringInfo(&buf, "EXECUTE PROCEDURE %s(",
  					 generate_function_name(trigrec->tgfoid, 0, NULL, NULL));
*************** pg_get_triggerdef(PG_FUNCTION_ARGS)
*** 593,599 ****
  
  	heap_close(tgrel, AccessShareLock);
  
! 	PG_RETURN_TEXT_P(string_to_text(buf.data));
  }
  
  /* ----------
--- 627,633 ----
  
  	heap_close(tgrel, AccessShareLock);
  
! 	return buf.data;
  }
  
  /* ----------
diff -cprN head/src/bin/pg_dump/pg_dump.c standard-column-trigger/src/bin/pg_dump/pg_dump.c
*** head/src/bin/pg_dump/pg_dump.c	2009-09-12 04:17:04.000000000 +0900
--- standard-column-trigger/src/bin/pg_dump/pg_dump.c	2009-09-14 18:48:39.680720773 +0900
*************** getTriggers(TableInfo tblinfo[], int num
*** 4263,4269 ****
  				i_tgconstrrelname,
  				i_tgenabled,
  				i_tgdeferrable,
! 				i_tginitdeferred;
  	int			ntups;
  
  	for (i = 0; i < numTables; i++)
--- 4263,4270 ----
  				i_tgconstrrelname,
  				i_tgenabled,
  				i_tgdeferrable,
! 				i_tginitdeferred,
! 				i_tgdef;
  	int			ntups;
  
  	for (i = 0; i < numTables; i++)
*************** getTriggers(TableInfo tblinfo[], int num
*** 4283,4289 ****
  		selectSourceSchema(tbinfo->dobj.namespace->dobj.name);
  
  		resetPQExpBuffer(query);
! 		if (g_fout->remoteVersion >= 80300)
  		{
  			/*
  			 * We ignore triggers that are tied to a foreign-key constraint
--- 4284,4302 ----
  		selectSourceSchema(tbinfo->dobj.namespace->dobj.name);
  
  		resetPQExpBuffer(query);
! 		if (g_fout->remoteVersion >= 80500)
! 		{
! 			appendPQExpBuffer(query,
! 							  "SELECT tgname, "
! 							  "tgfoid::pg_catalog.regproc AS tgfname, "
! 						  "pg_catalog.pg_get_triggerdef(oid, true) AS tgdef, "
! 							  "tgenabled, tableoid, oid "
! 							  "FROM pg_catalog.pg_trigger t "
! 							  "WHERE tgrelid = '%u'::pg_catalog.oid "
! 							  "AND tgconstraint = 0",
! 							  tbinfo->dobj.catId.oid);
! 		}
! 		else if (g_fout->remoteVersion >= 80300)
  		{
  			/*
  			 * We ignore triggers that are tied to a foreign-key constraint
*************** getTriggers(TableInfo tblinfo[], int num
*** 4370,4375 ****
--- 4383,4389 ----
  		i_tgenabled = PQfnumber(res, "tgenabled");
  		i_tgdeferrable = PQfnumber(res, "tgdeferrable");
  		i_tginitdeferred = PQfnumber(res, "tginitdeferred");
+ 		i_tgdef = PQfnumber(res, "tgdef");
  
  		tginfo = (TriggerInfo *) malloc(ntups * sizeof(TriggerInfo));
  
*************** getTriggers(TableInfo tblinfo[], int num
*** 4382,4419 ****
  			tginfo[j].dobj.name = strdup(PQgetvalue(res, j, i_tgname));
  			tginfo[j].dobj.namespace = tbinfo->dobj.namespace;
  			tginfo[j].tgtable = tbinfo;
- 			tginfo[j].tgfname = strdup(PQgetvalue(res, j, i_tgfname));
- 			tginfo[j].tgtype = atoi(PQgetvalue(res, j, i_tgtype));
- 			tginfo[j].tgnargs = atoi(PQgetvalue(res, j, i_tgnargs));
- 			tginfo[j].tgargs = strdup(PQgetvalue(res, j, i_tgargs));
- 			tginfo[j].tgisconstraint = *(PQgetvalue(res, j, i_tgisconstraint)) == 't';
  			tginfo[j].tgenabled = *(PQgetvalue(res, j, i_tgenabled));
! 			tginfo[j].tgdeferrable = *(PQgetvalue(res, j, i_tgdeferrable)) == 't';
! 			tginfo[j].tginitdeferred = *(PQgetvalue(res, j, i_tginitdeferred)) == 't';
  
! 			if (tginfo[j].tgisconstraint)
! 			{
! 				tginfo[j].tgconstrname = strdup(PQgetvalue(res, j, i_tgconstrname));
! 				tginfo[j].tgconstrrelid = atooid(PQgetvalue(res, j, i_tgconstrrelid));
! 				if (OidIsValid(tginfo[j].tgconstrrelid))
  				{
! 					if (PQgetisnull(res, j, i_tgconstrrelname))
  					{
! 						write_msg(NULL, "query produced null referenced table name for foreign key trigger \"%s\" on table \"%s\" (OID of table: %u)\n",
! 								  tginfo[j].dobj.name, tbinfo->dobj.name,
! 								  tginfo[j].tgconstrrelid);
! 						exit_nicely();
  					}
! 					tginfo[j].tgconstrrelname = strdup(PQgetvalue(res, j, i_tgconstrrelname));
  				}
  				else
  					tginfo[j].tgconstrrelname = NULL;
! 			}
! 			else
! 			{
! 				tginfo[j].tgconstrname = NULL;
! 				tginfo[j].tgconstrrelid = InvalidOid;
! 				tginfo[j].tgconstrrelname = NULL;
  			}
  		}
  
--- 4396,4442 ----
  			tginfo[j].dobj.name = strdup(PQgetvalue(res, j, i_tgname));
  			tginfo[j].dobj.namespace = tbinfo->dobj.namespace;
  			tginfo[j].tgtable = tbinfo;
  			tginfo[j].tgenabled = *(PQgetvalue(res, j, i_tgenabled));
! 			if (i_tgdef >= 0)
! 			{
! 				tginfo[j].tgdef = strdup(PQgetvalue(res, j, i_tgdef));
! 			}
! 			else
! 			{
! 				tginfo[j].tgdef = NULL;
! 
! 				tginfo[j].tgfname = strdup(PQgetvalue(res, j, i_tgfname));
! 				tginfo[j].tgtype = atoi(PQgetvalue(res, j, i_tgtype));
! 				tginfo[j].tgnargs = atoi(PQgetvalue(res, j, i_tgnargs));
! 				tginfo[j].tgargs = strdup(PQgetvalue(res, j, i_tgargs));
! 				tginfo[j].tgisconstraint = *(PQgetvalue(res, j, i_tgisconstraint)) == 't';
! 				tginfo[j].tgdeferrable = *(PQgetvalue(res, j, i_tgdeferrable)) == 't';
! 				tginfo[j].tginitdeferred = *(PQgetvalue(res, j, i_tginitdeferred)) == 't';
  
! 				if (tginfo[j].tgisconstraint)
  				{
! 					tginfo[j].tgconstrname = strdup(PQgetvalue(res, j, i_tgconstrname));
! 					tginfo[j].tgconstrrelid = atooid(PQgetvalue(res, j, i_tgconstrrelid));
! 					if (OidIsValid(tginfo[j].tgconstrrelid))
  					{
! 						if (PQgetisnull(res, j, i_tgconstrrelname))
! 						{
! 							write_msg(NULL, "query produced null referenced table name for foreign key trigger \"%s\" on table \"%s\" (OID of table: %u)\n",
! 									  tginfo[j].dobj.name, tbinfo->dobj.name,
! 									  tginfo[j].tgconstrrelid);
! 							exit_nicely();
! 						}
! 						tginfo[j].tgconstrrelname = strdup(PQgetvalue(res, j, i_tgconstrrelname));
  					}
! 					else
! 						tginfo[j].tgconstrrelname = NULL;
  				}
  				else
+ 				{
+ 					tginfo[j].tgconstrname = NULL;
+ 					tginfo[j].tgconstrrelid = InvalidOid;
  					tginfo[j].tgconstrrelname = NULL;
! 				}
  			}
  		}
  
*************** dumpTrigger(Archive *fout, TriggerInfo *
*** 11031,11143 ****
  	appendPQExpBuffer(delqry, "%s;\n",
  					  fmtId(tbinfo->dobj.name));
  
! 	if (tginfo->tgisconstraint)
  	{
! 		appendPQExpBuffer(query, "CREATE CONSTRAINT TRIGGER ");
! 		appendPQExpBufferStr(query, fmtId(tginfo->tgconstrname));
  	}
  	else
  	{
! 		appendPQExpBuffer(query, "CREATE TRIGGER ");
! 		appendPQExpBufferStr(query, fmtId(tginfo->dobj.name));
! 	}
! 	appendPQExpBuffer(query, "\n    ");
! 
! 	/* Trigger type */
! 	findx = 0;
! 	if (TRIGGER_FOR_BEFORE(tginfo->tgtype))
! 		appendPQExpBuffer(query, "BEFORE");
! 	else
! 		appendPQExpBuffer(query, "AFTER");
! 	if (TRIGGER_FOR_INSERT(tginfo->tgtype))
! 	{
! 		appendPQExpBuffer(query, " INSERT");
! 		findx++;
! 	}
! 	if (TRIGGER_FOR_DELETE(tginfo->tgtype))
! 	{
! 		if (findx > 0)
! 			appendPQExpBuffer(query, " OR DELETE");
! 		else
! 			appendPQExpBuffer(query, " DELETE");
! 		findx++;
! 	}
! 	if (TRIGGER_FOR_UPDATE(tginfo->tgtype))
! 	{
! 		if (findx > 0)
! 			appendPQExpBuffer(query, " OR UPDATE");
  		else
! 			appendPQExpBuffer(query, " UPDATE");
! 	}
! 	if (TRIGGER_FOR_TRUNCATE(tginfo->tgtype))
! 	{
! 		if (findx > 0)
! 			appendPQExpBuffer(query, " OR TRUNCATE");
  		else
! 			appendPQExpBuffer(query, " TRUNCATE");
! 	}
! 	appendPQExpBuffer(query, " ON %s\n",
! 					  fmtId(tbinfo->dobj.name));
  
! 	if (tginfo->tgisconstraint)
! 	{
! 		if (OidIsValid(tginfo->tgconstrrelid))
  		{
! 			/* If we are using regclass, name is already quoted */
! 			if (g_fout->remoteVersion >= 70300)
! 				appendPQExpBuffer(query, "    FROM %s\n    ",
! 								  tginfo->tgconstrrelname);
  			else
! 				appendPQExpBuffer(query, "    FROM %s\n    ",
! 								  fmtId(tginfo->tgconstrrelname));
  		}
! 		if (!tginfo->tgdeferrable)
! 			appendPQExpBuffer(query, "NOT ");
! 		appendPQExpBuffer(query, "DEFERRABLE INITIALLY ");
! 		if (tginfo->tginitdeferred)
! 			appendPQExpBuffer(query, "DEFERRED\n");
  		else
! 			appendPQExpBuffer(query, "IMMEDIATE\n");
! 	}
  
! 	if (TRIGGER_FOR_ROW(tginfo->tgtype))
! 		appendPQExpBuffer(query, "    FOR EACH ROW\n    ");
! 	else
! 		appendPQExpBuffer(query, "    FOR EACH STATEMENT\n    ");
  
! 	/* In 7.3, result of regproc is already quoted */
! 	if (g_fout->remoteVersion >= 70300)
! 		appendPQExpBuffer(query, "EXECUTE PROCEDURE %s(",
! 						  tginfo->tgfname);
! 	else
! 		appendPQExpBuffer(query, "EXECUTE PROCEDURE %s(",
! 						  fmtId(tginfo->tgfname));
  
! 	tgargs = (char *) PQunescapeBytea((unsigned char *) tginfo->tgargs,
! 									  &lentgargs);
! 	p = tgargs;
! 	for (findx = 0; findx < tginfo->tgnargs; findx++)
! 	{
! 		/* find the embedded null that terminates this trigger argument */
! 		size_t	tlen = strlen(p);
! 
! 		if (p + tlen >= tgargs + lentgargs)
! 		{
! 			/* hm, not found before end of bytea value... */
! 			write_msg(NULL, "invalid argument string (%s) for trigger \"%s\" on table \"%s\"\n",
! 					  tginfo->tgargs,
! 					  tginfo->dobj.name,
! 					  tbinfo->dobj.name);
! 			exit_nicely();
! 		}
  
! 		if (findx > 0)
! 			appendPQExpBuffer(query, ", ");
! 		appendStringLiteralAH(query, p, fout);
! 		p += tlen + 1;
  	}
- 	free(tgargs);
- 	appendPQExpBuffer(query, ");\n");
  
  	if (tginfo->tgenabled != 't' && tginfo->tgenabled != 'O')
  	{
--- 11054,11173 ----
  	appendPQExpBuffer(delqry, "%s;\n",
  					  fmtId(tbinfo->dobj.name));
  
! 	if (tginfo->tgdef)
  	{
! 		appendPQExpBuffer(query, "%s;\n", tginfo->tgdef);
  	}
  	else
  	{
! 		if (tginfo->tgisconstraint)
! 		{
! 			appendPQExpBuffer(query, "CREATE CONSTRAINT TRIGGER ");
! 			appendPQExpBufferStr(query, fmtId(tginfo->tgconstrname));
! 		}
  		else
! 		{
! 			appendPQExpBuffer(query, "CREATE TRIGGER ");
! 			appendPQExpBufferStr(query, fmtId(tginfo->dobj.name));
! 		}
! 		appendPQExpBuffer(query, "\n    ");
! 
! 		/* Trigger type */
! 		findx = 0;
! 		if (TRIGGER_FOR_BEFORE(tginfo->tgtype))
! 			appendPQExpBuffer(query, "BEFORE");
  		else
! 			appendPQExpBuffer(query, "AFTER");
! 		if (TRIGGER_FOR_INSERT(tginfo->tgtype))
! 		{
! 			appendPQExpBuffer(query, " INSERT");
! 			findx++;
! 		}
! 		if (TRIGGER_FOR_DELETE(tginfo->tgtype))
! 		{
! 			if (findx > 0)
! 				appendPQExpBuffer(query, " OR DELETE");
! 			else
! 				appendPQExpBuffer(query, " DELETE");
! 			findx++;
! 		}
! 		if (TRIGGER_FOR_UPDATE(tginfo->tgtype))
! 		{
! 			if (findx > 0)
! 				appendPQExpBuffer(query, " OR UPDATE");
! 			else
! 				appendPQExpBuffer(query, " UPDATE");
! 		}
! 		if (TRIGGER_FOR_TRUNCATE(tginfo->tgtype))
! 		{
! 			if (findx > 0)
! 				appendPQExpBuffer(query, " OR TRUNCATE");
! 			else
! 				appendPQExpBuffer(query, " TRUNCATE");
! 		}
! 		appendPQExpBuffer(query, " ON %s\n",
! 						  fmtId(tbinfo->dobj.name));
  
! 		if (tginfo->tgisconstraint)
  		{
! 			if (OidIsValid(tginfo->tgconstrrelid))
! 			{
! 				/* If we are using regclass, name is already quoted */
! 				if (g_fout->remoteVersion >= 70300)
! 					appendPQExpBuffer(query, "    FROM %s\n    ",
! 									  tginfo->tgconstrrelname);
! 				else
! 					appendPQExpBuffer(query, "    FROM %s\n    ",
! 									  fmtId(tginfo->tgconstrrelname));
! 			}
! 			if (!tginfo->tgdeferrable)
! 				appendPQExpBuffer(query, "NOT ");
! 			appendPQExpBuffer(query, "DEFERRABLE INITIALLY ");
! 			if (tginfo->tginitdeferred)
! 				appendPQExpBuffer(query, "DEFERRED\n");
  			else
! 				appendPQExpBuffer(query, "IMMEDIATE\n");
  		}
! 
! 		if (TRIGGER_FOR_ROW(tginfo->tgtype))
! 			appendPQExpBuffer(query, "    FOR EACH ROW\n    ");
  		else
! 			appendPQExpBuffer(query, "    FOR EACH STATEMENT\n    ");
  
! 		/* In 7.3, result of regproc is already quoted */
! 		if (g_fout->remoteVersion >= 70300)
! 			appendPQExpBuffer(query, "EXECUTE PROCEDURE %s(",
! 							  tginfo->tgfname);
! 		else
! 			appendPQExpBuffer(query, "EXECUTE PROCEDURE %s(",
! 							  fmtId(tginfo->tgfname));
  
! 		tgargs = (char *) PQunescapeBytea((unsigned char *) tginfo->tgargs,
! 										  &lentgargs);
! 		p = tgargs;
! 		for (findx = 0; findx < tginfo->tgnargs; findx++)
! 		{
! 			/* find the embedded null that terminates this trigger argument */
! 			size_t	tlen = strlen(p);
  
! 			if (p + tlen >= tgargs + lentgargs)
! 			{
! 				/* hm, not found before end of bytea value... */
! 				write_msg(NULL, "invalid argument string (%s) for trigger \"%s\" on table \"%s\"\n",
! 						  tginfo->tgargs,
! 						  tginfo->dobj.name,
! 						  tbinfo->dobj.name);
! 				exit_nicely();
! 			}
  
! 			if (findx > 0)
! 				appendPQExpBuffer(query, ", ");
! 			appendStringLiteralAH(query, p, fout);
! 			p += tlen + 1;
! 		}
! 		free(tgargs);
! 		appendPQExpBuffer(query, ");\n");
  	}
  
  	if (tginfo->tgenabled != 't' && tginfo->tgenabled != 'O')
  	{
diff -cprN head/src/bin/pg_dump/pg_dump.h standard-column-trigger/src/bin/pg_dump/pg_dump.h
*** head/src/bin/pg_dump/pg_dump.h	2009-08-03 07:14:52.000000000 +0900
--- standard-column-trigger/src/bin/pg_dump/pg_dump.h	2009-09-14 18:38:58.859946528 +0900
*************** typedef struct _triggerInfo
*** 327,332 ****
--- 327,333 ----
  	char		tgenabled;
  	bool		tgdeferrable;
  	bool		tginitdeferred;
+ 	char	   *tgdef;
  } TriggerInfo;
  
  /*
diff -cprN head/src/include/catalog/pg_proc.h standard-column-trigger/src/include/catalog/pg_proc.h
*** head/src/include/catalog/pg_proc.h	2009-09-01 11:54:52.000000000 +0900
--- standard-column-trigger/src/include/catalog/pg_proc.h	2009-09-14 17:27:45.399743291 +0900
*************** DATA(insert OID = 2599 (  pg_timezone_ab
*** 4082,4087 ****
--- 4082,4089 ----
  DESCR("get the available time zone abbreviations");
  DATA(insert OID = 2856 (  pg_timezone_names		PGNSP PGUID 12 1 1000 0 f f f t t s 0 0 2249 "" "{25,25,1186,16}" "{o,o,o,o}" "{name,abbrev,utc_offset,is_dst}" _null_ pg_timezone_names _null_ _null_ _null_ ));
  DESCR("get the available time zone names");
+ DATA(insert OID = 2730 (  pg_get_triggerdef    PGNSP PGUID 12 1 0 0 f f f t f s 2 0 25 "26 16" _null_ _null_ _null_ _null_ pg_get_triggerdef_ext _null_ _null_ _null_ ));
+ DESCR("trigger description with pretty-print option");
  
  /* non-persistent series generator */
  DATA(insert OID = 1066 (  generate_series PGNSP PGUID 12 1 1000 0 f f f t t i 3 0 23 "23 23 23" _null_ _null_ _null_ _null_ generate_series_step_int4 _null_ _null_ _null_ ));
diff -cprN head/src/include/catalog/pg_trigger.h standard-column-trigger/src/include/catalog/pg_trigger.h
*** head/src/include/catalog/pg_trigger.h	2009-07-28 11:56:31.000000000 +0900
--- standard-column-trigger/src/include/catalog/pg_trigger.h	2009-09-14 17:23:24.277745679 +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 standard-column-trigger/src/include/nodes/parsenodes.h
*** head/src/include/nodes/parsenodes.h	2009-08-03 07:14:53.000000000 +0900
--- standard-column-trigger/src/include/nodes/parsenodes.h	2009-09-14 17:23:24.277745679 +0900
*************** typedef struct CreateTrigStmt
*** 1553,1558 ****
--- 1553,1559 ----
  	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/include/utils/builtins.h standard-column-trigger/src/include/utils/builtins.h
*** head/src/include/utils/builtins.h	2009-09-10 04:00:09.000000000 +0900
--- standard-column-trigger/src/include/utils/builtins.h	2009-09-14 17:24:52.149745975 +0900
*************** extern Datum pg_get_indexdef_ext(PG_FUNC
*** 590,595 ****
--- 590,596 ----
  extern char *pg_get_indexdef_string(Oid indexrelid);
  extern char *pg_get_indexdef_columns(Oid indexrelid, bool pretty);
  extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
+ extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
  extern Datum pg_get_constraintdef(PG_FUNCTION_ARGS);
  extern Datum pg_get_constraintdef_ext(PG_FUNCTION_ARGS);
  extern char *pg_get_constraintdef_string(Oid constraintId);
diff -cprN head/src/test/regress/expected/triggers.out standard-column-trigger/src/test/regress/expected/triggers.out
*** head/src/test/regress/expected/triggers.out	2008-11-06 03:49:28.000000000 +0900
--- standard-column-trigger/src/test/regress/expected/triggers.out	2009-09-14 18:00:12.683748455 +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,394 ----
      |   
  (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');
+ 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_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_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
+ 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;
+ 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 standard-column-trigger/src/test/regress/sql/triggers.sql
*** head/src/test/regress/sql/triggers.sql	2008-11-06 03:49:28.000000000 +0900
--- standard-column-trigger/src/test/regress/sql/triggers.sql	2009-09-14 17:41:40.577757046 +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,285 ----
  
  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');
+ 
+ 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;
+ ALTER TABLE main_table DROP COLUMN b;
+ 
  -- Test enable/disable triggers
  
  create table trigtest (i serial primary key);
