diff -cprN head/doc/src/sgml/ref/create_trigger.sgml column-trigger/doc/src/sgml/ref/create_trigger.sgml
*** head/doc/src/sgml/ref/create_trigger.sgml	2009-09-03 10:27:51.483152714 +0900
--- column-trigger/doc/src/sgml/ref/create_trigger.sgml	2009-09-03 10:30:24.377498000 +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 column-trigger/doc/src/sgml/trigger.sgml
*** head/doc/src/sgml/trigger.sgml	2009-09-03 10:27:51.436149664 +0900
--- column-trigger/doc/src/sgml/trigger.sgml	2009-09-03 10:29:18.860719000 +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 updated.
     </para>
  
     <para>
diff -cprN head/src/backend/access/heap/heapam.c column-trigger/src/backend/access/heap/heapam.c
*** head/src/backend/access/heap/heapam.c	2009-08-24 11:18:31.000000000 +0900
--- column-trigger/src/backend/access/heap/heapam.c	2009-09-03 09:24:46.158859461 +0900
*************** l2:
*** 2853,2859 ****
   * Check if the specified attribute's value is same in both given tuples.
   * Subroutine for HeapSatisfiesHOTUpdate.
   */
! static bool
  heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum,
  					   HeapTuple tup1, HeapTuple tup2)
  {
--- 2853,2859 ----
   * Check if the specified attribute's value is same in both given tuples.
   * Subroutine for HeapSatisfiesHOTUpdate.
   */
! bool
  heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum,
  					   HeapTuple tup1, HeapTuple tup2)
  {
diff -cprN head/src/backend/commands/tablecmds.c column-trigger/src/backend/commands/tablecmds.c
*** head/src/backend/commands/tablecmds.c	2009-08-24 04:23:41.000000000 +0900
--- column-trigger/src/backend/commands/tablecmds.c	2009-09-03 09:57:00.584926915 +0900
*************** ATExecDropColumn(List **wqueue, Relation
*** 4346,4351 ****
--- 4346,4354 ----
  				 errmsg("cannot drop inherited column \"%s\"",
  						colName)));
  
+ 	/* TODO: (TRIGGER) Don't drop if this column is being used by a trigger */
+ 	/* Possibly handled by dependency.c? */
+ 
  	ReleaseSysCache(tuple);
  
  	/*
*************** ATExecDropColumn(List **wqueue, Relation
*** 4384,4389 ****
--- 4387,4393 ----
  
  			if (recurse)
  			{
+ 				/* TODO: (TRIGGER) Drop column-based triggers */
  				/*
  				 * If the child column has other definition sources, just
  				 * decrement its inheritance count; if not, recurse to delete
diff -cprN head/src/backend/commands/trigger.c column-trigger/src/backend/commands/trigger.c
*** head/src/backend/commands/trigger.c	2009-08-05 01:08:36.000000000 +0900
--- column-trigger/src/backend/commands/trigger.c	2009-09-03 10:13:12.388599000 +0900
***************
*** 32,37 ****
--- 32,38 ----
  #include "miscadmin.h"
  #include "nodes/makefuncs.h"
  #include "parser/parse_func.h"
+ #include "parser/parse_relation.h"
  #include "pgstat.h"
  #include "storage/bufmgr.h"
  #include "tcop/utility.h"
*************** static HeapTuple ExecCallTriggerFunc(Tri
*** 66,71 ****
--- 67,74 ----
  static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
  					  bool row_trigger, HeapTuple oldtup, HeapTuple newtup,
  					  List *recheckIndexes);
+ static bool TriggerEnabled(Trigger *trigger, Relation rel, HeapTuple oldtup,
+ 						   HeapTuple newtup);
  
  
  /*
*************** CreateTrigger(CreateTrigStmt *stmt,
*** 97,102 ****
--- 100,107 ----
  			  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);
--- 341,379 ----
  														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 ****
--- 469,493 ----
  		Assert(!OidIsValid(indexOid));
  	}
  
+ 	/* TODO: (TRIGGER) Dependencies for columns. */
+ #ifdef NOT_USED
+ 	if (columns != NULL)
+ 	{
+ 		int					i;
+ 		Form_pg_attribute  *attrs;
+ 
+ 		attrs = RelationGetDescr(rel)->attrs;
+ 
+ 		for (i = 0; i < ncolumns; i++)
+ 		{
+ 			referenced.classId = TypeRelationId;
+ 			referenced.objectId = attrs[columns[i] - 1];
+ 			referenced.objectSubId = 0;
+ 			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ 		}
+ 	}
+ #endif
+ 
  	/* 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],
--- 1680,1688 ----
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  		HeapTuple	newtuple;
  
! 		if (!TriggerEnabled(trigger, relinfo->ri_RelationDesc, NULL, NULL))
! 			continue;
! 
  		LocTriggerData.tg_trigger = trigger;
  		newtuple = ExecCallTriggerFunc(&LocTriggerData,
  									   tgindx[i],
*************** 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;
--- 1730,1738 ----
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  
! 		if (!TriggerEnabled(trigger, relinfo->ri_RelationDesc, NULL, newtuple))
! 			continue;
! 
  		LocTriggerData.tg_trigtuple = oldtuple = newtuple;
  		LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
  		LocTriggerData.tg_trigger = trigger;
*************** 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],
--- 1793,1801 ----
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  		HeapTuple	newtuple;
  
! 		if (!TriggerEnabled(trigger, relinfo->ri_RelationDesc, NULL, NULL))
! 			continue;
! 
  		LocTriggerData.tg_trigger = trigger;
  		newtuple = ExecCallTriggerFunc(&LocTriggerData,
  									   tgindx[i],
*************** 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;
--- 1849,1857 ----
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  
! 		if (!TriggerEnabled(trigger, relinfo->ri_RelationDesc, trigtuple, NULL))
! 			continue;
! 
  		LocTriggerData.tg_trigtuple = trigtuple;
  		LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
  		LocTriggerData.tg_trigger = trigger;
*************** 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],
--- 1923,1931 ----
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  		HeapTuple	newtuple;
  
! 		if (!TriggerEnabled(trigger, relinfo->ri_RelationDesc, NULL, NULL))
! 			continue;
! 
  		LocTriggerData.tg_trigger = trigger;
  		newtuple = ExecCallTriggerFunc(&LocTriggerData,
  									   tgindx[i],
*************** 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;
--- 1984,1992 ----
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  
! 		if (!TriggerEnabled(trigger, relinfo->ri_RelationDesc, trigtuple, newtuple))
! 			continue;
! 
  		LocTriggerData.tg_trigtuple = trigtuple;
  		LocTriggerData.tg_newtuple = oldtuple = newtuple;
  		LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
*************** 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],
--- 2057,2065 ----
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  		HeapTuple	newtuple;
  
! 		if (!TriggerEnabled(trigger, relinfo->ri_RelationDesc, NULL, NULL))
! 			continue;
! 
  		LocTriggerData.tg_trigger = trigger;
  		newtuple = ExecCallTriggerFunc(&LocTriggerData,
  									   tgindx[i],
*************** 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
--- 3907,3914 ----
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  
! 		if (!TriggerEnabled(trigger, rel, oldtup, newtup))
! 			continue;
  
  		/*
  		 * If this is an UPDATE of a PK table or FK table that does not change
*************** AfterTriggerSaveEvent(ResultRelInfo *rel
*** 4000,4002 ****
--- 3981,4031 ----
  							 &new_event, &new_shared);
  	}
  }
+ 
+ static bool
+ TriggerEnabled(Trigger *trigger, Relation rel, HeapTuple oldtup, HeapTuple newtup)
+ {
+ 	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 &&
+ 		(trigger->tgtype & (TRIGGER_TYPE_UPDATE | TRIGGER_TYPE_ROW)) ==
+ 		(TRIGGER_TYPE_UPDATE | TRIGGER_TYPE_ROW))
+ 	{
+ 		int			i;
+ 		bool		modified;
+ 		TupleDesc	tupdesc = RelationGetDescr(rel);
+ 
+ 		Assert(oldtup != NULL);
+ 		Assert(newtup != NULL);
+ 
+ 		modified = false;
+ 		for (i = 0; i < trigger->tgnattr; i++)
+ 		{
+ 			int		n = trigger->tgattr[i];
+ 
+ 			if (n <= tupdesc->natts &&
+ 				!tupdesc->attrs[n - 1]->attisdropped &&
+ 				!heap_tuple_attr_equals(tupdesc, n, oldtup, newtup))
+ 			{
+ 				modified = true;
+ 				break;
+ 			}
+ 		}
+ 		if (!modified)
+ 			return false;
+ 	}
+ 
+ 	return true;
+ }
diff -cprN head/src/backend/parser/gram.y column-trigger/src/backend/parser/gram.y
*** head/src/backend/parser/gram.y	2009-08-19 08:40:20.000000000 +0900
--- column-trigger/src/backend/parser/gram.y	2009-09-03 09:29:24.482941918 +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/bin/pg_dump/pg_dump.c column-trigger/src/bin/pg_dump/pg_dump.c
*** head/src/bin/pg_dump/pg_dump.c	2009-08-05 04:46:51.000000000 +0900
--- column-trigger/src/bin/pg_dump/pg_dump.c	2009-09-03 10:33:25.919870376 +0900
*************** static void check_sql_result(PGresult *r
*** 200,205 ****
--- 200,220 ----
  				 ExecStatusType expected);
  
  
+ static char *
+ get_str_or_null(PGresult *res, int tup, int field)
+ {
+ 	char *value;
+ 
+ 	if (field < 0)
+ 		return NULL;
+ 
+ 	value = PQgetvalue(res, tup, field);
+ 	if (value && value[0])
+ 		return strdup(value);
+ 
+ 	return NULL;
+ }
+ 
  int
  main(int argc, char **argv)
  {
*************** getTriggers(TableInfo tblinfo[], int num
*** 4261,4267 ****
  				i_tgconstrrelname,
  				i_tgenabled,
  				i_tgdeferrable,
! 				i_tginitdeferred;
  	int			ntups;
  
  	for (i = 0; i < numTables; i++)
--- 4276,4283 ----
  				i_tgconstrrelname,
  				i_tgenabled,
  				i_tgdeferrable,
! 				i_tginitdeferred,
! 				i_tgattr;
  	int			ntups;
  
  	for (i = 0; i < numTables; i++)
*************** getTriggers(TableInfo tblinfo[], int num
*** 4281,4287 ****
  		selectSourceSchema(tbinfo->dobj.namespace->dobj.name);
  
  		resetPQExpBuffer(query);
! 		if (g_fout->remoteVersion >= 80300)
  		{
  			/*
  			 * We ignore triggers that are tied to a foreign-key constraint
--- 4297,4324 ----
  		selectSourceSchema(tbinfo->dobj.namespace->dobj.name);
  
  		resetPQExpBuffer(query);
! 		if (g_fout->remoteVersion >= 80500)
! 		{
! 			/*
! 			 * We ignore triggers that are tied to a foreign-key constraint
! 			 */
! 			appendPQExpBuffer(query,
! 							  "SELECT tgname, "
! 							  "tgfoid::pg_catalog.regproc AS tgfname, "
! 							  "tgtype, tgnargs, tgargs, tgenabled, "
! 							  "tgisconstraint, tgconstrname, tgdeferrable, "
! 							  "tgconstrrelid, tginitdeferred, tableoid, oid, "
! 					 "tgconstrrelid::pg_catalog.regclass AS tgconstrrelname, "
! 			"pg_catalog.array_to_string((SELECT pg_catalog.array_agg(attname) "
! 			"FROM pg_catalog.pg_attribute, pg_catalog.unnest(tgattr) AS n "
! 							  "WHERE attnum = n AND attrelid = tgrelid "
! 							  "AND NOT attisdropped), ', ') AS tgattr "
! 							  "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
*** 4368,4373 ****
--- 4405,4411 ----
  		i_tgenabled = PQfnumber(res, "tgenabled");
  		i_tgdeferrable = PQfnumber(res, "tgdeferrable");
  		i_tginitdeferred = PQfnumber(res, "tginitdeferred");
+ 		i_tgattr = PQfnumber(res, "tgattr");
  
  		tginfo = (TriggerInfo *) malloc(ntups * sizeof(TriggerInfo));
  
*************** getTriggers(TableInfo tblinfo[], int num
*** 4388,4393 ****
--- 4426,4432 ----
  			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';
+ 			tginfo[j].tgattr = get_str_or_null(res, j, i_tgattr);
  
  			if (tginfo[j].tgisconstraint)
  			{
*************** dumpTrigger(Archive *fout, TriggerInfo *
*** 11066,11071 ****
--- 11105,11112 ----
  			appendPQExpBuffer(query, " OR UPDATE");
  		else
  			appendPQExpBuffer(query, " UPDATE");
+ 		if (tginfo->tgattr)
+ 			appendPQExpBuffer(query, " OF %s", tginfo->tgattr);
  	}
  	if (TRIGGER_FOR_TRUNCATE(tginfo->tgtype))
  	{
diff -cprN head/src/bin/pg_dump/pg_dump.h 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
--- column-trigger/src/bin/pg_dump/pg_dump.h	2009-09-03 09:26:55.673887897 +0900
*************** typedef struct _triggerInfo
*** 327,332 ****
--- 327,333 ----
  	char		tgenabled;
  	bool		tgdeferrable;
  	bool		tginitdeferred;
+ 	char	   *tgattr;
  } TriggerInfo;
  
  /*
diff -cprN head/src/include/access/heapam.h column-trigger/src/include/access/heapam.h
*** head/src/include/access/heapam.h	2009-08-24 11:18:32.000000000 +0900
--- column-trigger/src/include/access/heapam.h	2009-09-03 09:24:46.170875501 +0900
*************** extern bool heap_hot_search_buffer(ItemP
*** 86,91 ****
--- 86,93 ----
  					   Snapshot snapshot, bool *all_dead);
  extern bool heap_hot_search(ItemPointer tid, Relation relation,
  				Snapshot snapshot, bool *all_dead);
+ extern bool heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum,
+ 								   HeapTuple tup1, HeapTuple tup2);
  
  extern void heap_get_latest_tid(Relation relation, Snapshot snapshot,
  					ItemPointer tid);
diff -cprN head/src/include/catalog/pg_trigger.h column-trigger/src/include/catalog/pg_trigger.h
*** head/src/include/catalog/pg_trigger.h	2009-07-28 11:56:31.000000000 +0900
--- column-trigger/src/include/catalog/pg_trigger.h	2009-09-03 09:24:46.170875501 +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 column-trigger/src/include/nodes/parsenodes.h
*** head/src/include/nodes/parsenodes.h	2009-08-03 07:14:53.000000000 +0900
--- column-trigger/src/include/nodes/parsenodes.h	2009-09-03 09:30:04.706842600 +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/test/regress/expected/triggers.out column-trigger/src/test/regress/expected/triggers.out
*** head/src/test/regress/expected/triggers.out	2008-11-06 03:49:28.000000000 +0900
--- column-trigger/src/test/regress/expected/triggers.out	2009-09-03 10:17:14.065898000 +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 before_upd_stmt_trig AFTER UPDATE ON main_table
! EXECUTE PROCEDURE trigger_func('before_upd_stmt');
  CREATE TRIGGER before_upd_row_trig AFTER UPDATE ON main_table
! FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_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(before_upd_row) called: action = UPDATE, when = AFTER, level = ROW
! NOTICE:  trigger_func(before_upd_row) called: action = UPDATE, when = AFTER, level = ROW
! NOTICE:  trigger_func(before_upd_row) called: action = UPDATE, when = AFTER, level = ROW
! NOTICE:  trigger_func(before_upd_row) called: action = UPDATE, when = AFTER, level = ROW
! NOTICE:  trigger_func(before_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(before_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,367 ----
      |   
  (8 rows)
  
+ -- Column-level triggers should only fire on after row-level updates
+ DROP TRIGGER before_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_a_b_row_trig AFTER UPDATE OF a, b ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_a_b_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_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_a_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_a_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_a_b_row) called: action = UPDATE, when = AFTER, level = ROW
+ NOTICE:  trigger_func(before_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;
+ DROP TRIGGER before_upd_a_row_trig ON main_table;
+ UPDATE main_table SET a = 50;
+ NOTICE:  trigger_func(after_upd_a_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_a_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_a_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(before_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
  -- 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 column-trigger/src/test/regress/sql/triggers.sql
*** head/src/test/regress/sql/triggers.sql	2008-11-06 03:49:28.000000000 +0900
--- column-trigger/src/test/regress/sql/triggers.sql	2009-09-03 10:16:55.136394000 +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 before_upd_stmt_trig AFTER UPDATE ON main_table
! EXECUTE PROCEDURE trigger_func('before_upd_stmt');
  
  CREATE TRIGGER before_upd_row_trig AFTER UPDATE ON main_table
! FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_upd_row');
  
  INSERT INTO main_table DEFAULT VALUES;
  
*************** COPY main_table (a, b) FROM stdin;
*** 254,259 ****
--- 254,279 ----
  
  SELECT * FROM main_table ORDER BY a, b;
  
+ -- Column-level triggers should only fire on after row-level updates
+ DROP TRIGGER before_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_a_b_row_trig AFTER UPDATE OF a, b ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_a_b_row');
+ 
+ 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 before_upd_a_row_trig ON main_table;
+ UPDATE main_table SET a = 50;
+ 
  -- Test enable/disable triggers
  
  create table trigtest (i serial primary key);
