trigger-when_20091118.patch

application/octet-stream

Filename: trigger-when_20091118.patch
Type: application/octet-stream
Part: 0
Message: Re: TRIGGER with WHEN clause

Patch

Format: context
File+
doc/src/sgml/catalogs.sgml 9 0
doc/src/sgml/ref/create_trigger.sgml 25 0
src/backend/catalog/index.c 1 1
src/backend/commands/copy.c 3 2
src/backend/commands/tablecmds.c 5 3
src/backend/commands/trigger.c 225 40
src/backend/executor/execMain.c 1 0
src/backend/executor/execQual.c 22 34
src/backend/executor/execUtils.c 1 0
src/backend/nodes/copyfuncs.c 1 0
src/backend/nodes/equalfuncs.c 1 0
src/backend/parser/gram.y 11 4
src/backend/tcop/utility.c 1 1
src/backend/utils/adt/ruleutils.c 51 6
src/include/catalog/pg_trigger.h 4 2
src/include/catalog/toasting.h 1 0
src/include/commands/trigger.h 1 1
src/include/nodes/execnodes.h 2 1
src/include/nodes/parsenodes.h 1 0
src/include/utils/rel.h 1 0
src/test/regress/expected/triggers.out 91 0
src/test/regress/sql/triggers.sql 37 0
diff -cprN head/doc/src/sgml/catalogs.sgml work/doc/src/sgml/catalogs.sgml
*** head/doc/src/sgml/catalogs.sgml	2009-10-15 07:14:21.000000000 +0900
--- work/doc/src/sgml/catalogs.sgml	2009-11-18 16:29:02.215614422 +0900
***************
*** 4756,4761 ****
--- 4756,4770 ----
        <entry></entry>
        <entry>Argument strings to pass to trigger, each NULL-terminated</entry>
       </row>
+ 
+      <row>
+       <entry><structfield>tgqual</structfield></entry>
+       <entry><type>text</type></entry>
+       <entry></entry>
+       <entry>Expression tree (in the form of a <function>nodeToString()</function>
+         representation) for the trigger's qualifying condition, or NULL
+         if the trigger has no <literal>WHEN</> clause.</entry>
+      </row>
      </tbody>
     </tgroup>
    </table>
diff -cprN head/doc/src/sgml/ref/create_trigger.sgml work/doc/src/sgml/ref/create_trigger.sgml
*** head/doc/src/sgml/ref/create_trigger.sgml	2009-10-15 07:14:21.000000000 +0900
--- work/doc/src/sgml/ref/create_trigger.sgml	2009-11-18 17:42:57.312988000 +0900
*************** PostgreSQL documentation
*** 23,28 ****
--- 23,29 ----
  <synopsis>
  CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTER } { <replaceable class="PARAMETER">event</replaceable> [ OR ... ] }
      ON <replaceable class="PARAMETER">table</replaceable> [ FOR [ EACH ] { ROW | STATEMENT } ]
+     WHEN ( <replaceable class="parameter">condition</replaceable> )
      EXECUTE PROCEDURE <replaceable class="PARAMETER">function_name</replaceable> ( <replaceable class="PARAMETER">arguments</replaceable> )
  </synopsis>
   </refsynopsisdiv>
*************** UPDATE OF <replaceable>column_name1</rep
*** 160,165 ****
--- 161,190 ----
     </varlistentry>
  
     <varlistentry>
+     <term><replaceable class="parameter">condition</replaceable></term>
+     <listitem>
+      <para>
+       Any <acronym>SQL</acronym> conditional expression (returning
+       <type>boolean</type>). Only <literal>FOR EACH ROW</literal> triggers
+       can refer <literal>NEW</> and <literal>OLD</> tuples.
+       <literal>INSERT</literal> trigger can refer <literal>NEW</>,
+       <literal>DELETE</literal> trigger can refer <literal>OLD</>,
+       and <literal>UPDATE</literal> trigger can refer both of them.
+       Note that if a trigger has multiple events, it can refer only tuples
+       that can be referred in all of the events. For example,
+       <literal>INSERT</> <literal>OR</> <literal>DELETE</> trigger cannot
+       refer neither <literal>NEW</> nor <literal>OLD</> tuples.
+      </para>
+ 
+      <para>
+        System columns are not available in the <literal>WHEN</> clause
+        because those values are initialized after triggers are called.
+        They might return wrong values if they used in expressions of the clause.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><replaceable class="parameter">function_name</replaceable></term>
      <listitem>
       <para>
diff -cprN head/src/backend/catalog/index.c work/src/backend/catalog/index.c
*** head/src/backend/catalog/index.c	2009-10-15 07:14:21.000000000 +0900
--- work/src/backend/catalog/index.c	2009-11-18 16:29:02.216972882 +0900
*************** index_create(Oid heapRelationId,
*** 798,804 ****
  				trigger->initdeferred = initdeferred;
  				trigger->constrrel = NULL;
  
! 				(void) CreateTrigger(trigger, conOid, indexRelationId,
  									 isprimary ? "PK_ConstraintTrigger" :
  									 "Unique_ConstraintTrigger",
  									 false);
--- 798,804 ----
  				trigger->initdeferred = initdeferred;
  				trigger->constrrel = NULL;
  
! 				(void) CreateTrigger(trigger, NULL, conOid, indexRelationId,
  									 isprimary ? "PK_ConstraintTrigger" :
  									 "Unique_ConstraintTrigger",
  									 false);
diff -cprN head/src/backend/commands/copy.c work/src/backend/commands/copy.c
*** head/src/backend/commands/copy.c	2009-09-22 05:10:21.000000000 +0900
--- work/src/backend/commands/copy.c	2009-11-18 17:01:00.624558839 +0900
*************** CopyFrom(CopyState cstate)
*** 1810,1816 ****
  	estate->es_result_relation_info = resultRelInfo;
  
  	/* Set up a tuple slot too */
! 	slot = MakeSingleTupleTableSlot(tupDesc);
  
  	econtext = GetPerTupleExprContext(estate);
  
--- 1810,1817 ----
  	estate->es_result_relation_info = resultRelInfo;
  
  	/* Set up a tuple slot too */
! 	slot = ExecInitExtraTupleSlot(estate);
! 	ExecSetSlotDescriptor(slot, tupDesc);
  
  	econtext = GetPerTupleExprContext(estate);
  
*************** CopyFrom(CopyState cstate)
*** 2198,2204 ****
  	pfree(defmap);
  	pfree(defexprs);
  
! 	ExecDropSingleTupleTableSlot(slot);
  
  	ExecCloseIndices(resultRelInfo);
  
--- 2199,2205 ----
  	pfree(defmap);
  	pfree(defexprs);
  
! 	ExecResetTupleTable(estate->es_tupleTable, true);
  
  	ExecCloseIndices(resultRelInfo);
  
diff -cprN head/src/backend/commands/tablecmds.c work/src/backend/commands/tablecmds.c
*** head/src/backend/commands/tablecmds.c	2009-11-04 21:24:23.000000000 +0900
--- work/src/backend/commands/tablecmds.c	2009-11-18 16:29:02.217919047 +0900
*************** CreateFKCheckTrigger(RangeVar *myRel, Co
*** 5482,5488 ****
  	fk_trigger->constrrel = fkconstraint->pktable;
  	fk_trigger->args = NIL;
  
! 	(void) CreateTrigger(fk_trigger, constraintOid, indexOid,
  						 "RI_ConstraintTrigger", false);
  
  	/* Make changes-so-far visible */
--- 5482,5488 ----
  	fk_trigger->constrrel = fkconstraint->pktable;
  	fk_trigger->args = NIL;
  
! 	(void) CreateTrigger(fk_trigger, NULL, constraintOid, indexOid,
  						 "RI_ConstraintTrigger", false);
  
  	/* Make changes-so-far visible */
*************** createForeignKeyTriggers(Relation rel, C
*** 5523,5528 ****
--- 5523,5529 ----
  	fk_trigger = makeNode(CreateTrigStmt);
  	fk_trigger->trigname = fkconstraint->conname;
  	fk_trigger->relation = fkconstraint->pktable;
+ 	fk_trigger->whenClause = NULL;
  	fk_trigger->before = false;
  	fk_trigger->row = true;
  	fk_trigger->events = TRIGGER_TYPE_DELETE;
*************** createForeignKeyTriggers(Relation rel, C
*** 5563,5569 ****
  	}
  	fk_trigger->args = NIL;
  
! 	(void) CreateTrigger(fk_trigger, constraintOid, indexOid,
  						 "RI_ConstraintTrigger", false);
  
  	/* Make changes-so-far visible */
--- 5564,5570 ----
  	}
  	fk_trigger->args = NIL;
  
! 	(void) CreateTrigger(fk_trigger, NULL, constraintOid, indexOid,
  						 "RI_ConstraintTrigger", false);
  
  	/* Make changes-so-far visible */
*************** createForeignKeyTriggers(Relation rel, C
*** 5576,5581 ****
--- 5577,5583 ----
  	fk_trigger = makeNode(CreateTrigStmt);
  	fk_trigger->trigname = fkconstraint->conname;
  	fk_trigger->relation = fkconstraint->pktable;
+ 	fk_trigger->whenClause = NULL;
  	fk_trigger->before = false;
  	fk_trigger->row = true;
  	fk_trigger->events = TRIGGER_TYPE_UPDATE;
*************** createForeignKeyTriggers(Relation rel, C
*** 5616,5622 ****
  	}
  	fk_trigger->args = NIL;
  
! 	(void) CreateTrigger(fk_trigger, constraintOid, indexOid,
  						 "RI_ConstraintTrigger", false);
  }
  
--- 5618,5624 ----
  	}
  	fk_trigger->args = NIL;
  
! 	(void) CreateTrigger(fk_trigger, NULL, constraintOid, indexOid,
  						 "RI_ConstraintTrigger", false);
  }
  
diff -cprN head/src/backend/commands/trigger.c work/src/backend/commands/trigger.c
*** head/src/backend/commands/trigger.c	2009-10-28 05:14:27.000000000 +0900
--- work/src/backend/commands/trigger.c	2009-11-18 16:32:15.510633752 +0900
***************
*** 32,41 ****
--- 32,43 ----
  #include "miscadmin.h"
  #include "nodes/bitmapset.h"
  #include "nodes/makefuncs.h"
+ #include "parser/parse_clause.h"
  #include "parser/parse_func.h"
  #include "parser/parse_relation.h"
  #include "parser/parsetree.h"
  #include "pgstat.h"
+ #include "rewrite/rewriteManip.h"
  #include "storage/bufmgr.h"
  #include "tcop/utility.h"
  #include "utils/acl.h"
*************** static HeapTuple GetTupleForTrigger(ESta
*** 66,79 ****
  				   ItemPointer tid,
  				   TupleTableSlot **newSlot);
  static bool TriggerEnabled(Trigger *trigger, TriggerEvent event,
! 						   Bitmapset *modifiedCols);
  static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata,
  					int tgindx,
  					FmgrInfo *finfo,
  					Instrumentation *instr,
  					MemoryContext per_tuple_context);
! static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
! 					  bool row_trigger, HeapTuple oldtup, HeapTuple newtup,
  					  List *recheckIndexes, Bitmapset *modifiedCols);
  
  
--- 68,83 ----
  				   ItemPointer tid,
  				   TupleTableSlot **newSlot);
  static bool TriggerEnabled(Trigger *trigger, TriggerEvent event,
! 					Bitmapset *modifiedCols, EState *estate,
! 					TupleDesc tupdesc, HeapTuple oldtup, HeapTuple newtup);
! static void ClearTriggerTableSlots(EState *estate);
  static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata,
  					int tgindx,
  					FmgrInfo *finfo,
  					Instrumentation *instr,
  					MemoryContext per_tuple_context);
! static void AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
! 					  int event, HeapTuple oldtup, HeapTuple newtup,
  					  List *recheckIndexes, Bitmapset *modifiedCols);
  
  
*************** static void AfterTriggerSaveEvent(Result
*** 101,107 ****
   * but a foreign-key constraint.  This is a kluge for backwards compatibility.
   */
  Oid
! CreateTrigger(CreateTrigStmt *stmt,
  			  Oid constraintOid, Oid indexOid, const char *prefix,
  			  bool checkPermissions)
  {
--- 105,111 ----
   * but a foreign-key constraint.  This is a kluge for backwards compatibility.
   */
  Oid
! CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
  			  Oid constraintOid, Oid indexOid, const char *prefix,
  			  bool checkPermissions)
  {
*************** CreateTrigger(CreateTrigStmt *stmt,
*** 128,133 ****
--- 132,138 ----
  	Oid			constrrelid = InvalidOid;
  	ObjectAddress myself,
  				referenced;
+ 	char	   *qual;
  
  	rel = heap_openrv(stmt->relation, AccessExclusiveLock);
  
*************** CreateTrigger(CreateTrigStmt *stmt,
*** 224,229 ****
--- 229,296 ----
  		return InvalidOid;
  	}
  
+ 	if (stmt->whenClause == NULL)
+ 		qual = NULL;
+ 	else
+ 	{
+ 		ParseState	   *pstate;
+ 		Node		   *whenClause;
+ 		int				num_rte;
+ 
+ 		/* Set up pstate */
+ 		pstate = make_parsestate(NULL);
+ 		pstate->p_sourcetext = queryString;
+ 
+ 		/*
+ 		 * NOTE: 'OLD' must always have a varno equal to 1 and 'NEW' equal to 2.
+ 		 * Set up their RTEs in the main pstate for use in parsing the rule
+ 		 * qualification.
+ 		 */
+ 		if (TRIGGER_FOR_ROW(tgtype))
+ 		{
+ 			RangeTblEntry *rte;
+ 
+ 			if ((TRIGGER_FOR_DELETE(tgtype) || TRIGGER_FOR_UPDATE(tgtype)) &&
+ 				!TRIGGER_FOR_INSERT(tgtype))
+ 			{
+ 				rte = addRangeTableEntryForRelation(pstate, rel,
+ 										makeAlias("old", NIL), false, false);
+ 				rte->requiredPerms = 0;
+ 				addRTEtoQuery(pstate, rte, false, true, true);
+ 			}
+ 
+ 			if ((TRIGGER_FOR_INSERT(tgtype) || TRIGGER_FOR_UPDATE(tgtype)) &&
+ 				!TRIGGER_FOR_DELETE(tgtype))
+ 			{
+ 				rte = addRangeTableEntryForRelation(pstate, rel,
+ 										makeAlias("new", NIL), false, false);
+ 				rte->requiredPerms = 0;
+ 				addRTEtoQuery(pstate, rte, false, true, true);
+ 			}
+ 		}
+ 		num_rte = list_length(pstate->p_rtable);
+ 
+ 		/* take care of the when clause */
+ 		whenClause = transformWhereClause(pstate,
+ 			(Node *) copyObject(stmt->whenClause), "WHEN");
+ 
+ 		if (list_length(pstate->p_rtable) != num_rte)
+ 			ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ 				 errmsg("trigger WHEN condition cannot contain references to other relations")));
+ 
+ 		/*
+ 		 * adjust varno of NEW reference to PRS2_NEW_VARNO.
+ 		 * XXX: Are there any better ways?
+ 		 */
+ 		if (num_rte == 1 && TRIGGER_FOR_INSERT(tgtype))
+ 			ChangeVarNodes(whenClause, 1, PRS2_NEW_VARNO, 0);
+ 
+ 		free_parsestate(pstate);
+ 
+ 		qual = nodeToString(whenClause);
+ 	}
+ 
  	/*
  	 * Generate the trigger's OID now, so that we can use it in the name if
  	 * needed.
*************** CreateTrigger(CreateTrigStmt *stmt,
*** 387,392 ****
--- 454,465 ----
  	tgattr = buildint2vector(columns, ncolumns);
  	values[Anum_pg_trigger_tgattr - 1] = PointerGetDatum(tgattr);
  
+ 	/* set tgqual if trigger has WHEN clause */
+ 	if (qual)
+ 		values[Anum_pg_trigger_tgqual - 1] = CStringGetTextDatum(qual);
+ 	else
+ 		nulls[Anum_pg_trigger_tgqual - 1] = true;
+ 
  	tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
  
  	/* force tuple to have the desired OID */
*************** RelationBuildTriggers(Relation relation)
*** 1184,1189 ****
--- 1257,1264 ----
  	{
  		Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(htup);
  		Trigger    *build;
+ 		Datum		qual_datum;
+ 		bool		isnull;
  
  		if (numtrigs >= maxtrigs)
  		{
*************** RelationBuildTriggers(Relation relation)
*** 1238,1243 ****
--- 1313,1334 ----
  		else
  			build->tgargs = NULL;
  
+ 		qual_datum = heap_getattr(htup, Anum_pg_trigger_tgqual,
+ 								  RelationGetDescr(tgrel), &isnull);
+ 		if (!isnull)
+ 		{
+ 			char *qual_str = TextDatumGetCString(qual_datum);
+ 
+ 			/* Fix references to OLD and NEW to INNER and OUTER */
+ 			build->tgqual = stringToNode(qual_str);
+ 			ChangeVarNodes(build->tgqual, PRS2_OLD_VARNO, INNER, 0);
+ 			ChangeVarNodes(build->tgqual, PRS2_NEW_VARNO, OUTER, 0);
+ 
+ 			pfree(qual_str);
+ 		}
+ 		else
+ 			build->tgqual = NULL;
+ 
  		numtrigs++;
  	}
  
*************** CopyTriggerDesc(TriggerDesc *trigdesc)
*** 1396,1401 ****
--- 1487,1493 ----
  				newargs[j] = pstrdup(trigger->tgargs[j]);
  			trigger->tgargs = newargs;
  		}
+ 		trigger->tgqual = copyObject(trigger->tgqual);
  		trigger++;
  	}
  
*************** ExecBSInsertTriggers(EState *estate, Res
*** 1682,1693 ****
  	LocTriggerData.tg_newtuple = NULL;
  	LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
  	LocTriggerData.tg_newtuplebuf = InvalidBuffer;
  	for (i = 0; i < ntrigs; i++)
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  		HeapTuple	newtuple;
  
! 		if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL))
  			continue;
  
  		LocTriggerData.tg_trigger = trigger;
--- 1774,1787 ----
  	LocTriggerData.tg_newtuple = NULL;
  	LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
  	LocTriggerData.tg_newtuplebuf = InvalidBuffer;
+ 	ClearTriggerTableSlots(estate);
  	for (i = 0; i < ntrigs; i++)
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  		HeapTuple	newtuple;
  
! 		if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL,
! 							estate, NULL, NULL, NULL))
  			continue;
  
  		LocTriggerData.tg_trigger = trigger;
*************** ExecASInsertTriggers(EState *estate, Res
*** 1710,1717 ****
  	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
  
  	if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
! 		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! 							  false, NULL, NULL, NIL, NULL);
  }
  
  HeapTuple
--- 1804,1811 ----
  	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
  
  	if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
! 		AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT,
! 							  NULL, NULL, NIL, NULL);
  }
  
  HeapTuple
*************** ExecBRInsertTriggers(EState *estate, Res
*** 1733,1743 ****
  	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
  	LocTriggerData.tg_newtuple = NULL;
  	LocTriggerData.tg_newtuplebuf = InvalidBuffer;
  	for (i = 0; i < ntrigs; i++)
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  
! 		if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL))
  			continue;
  
  		LocTriggerData.tg_trigtuple = oldtuple = newtuple;
--- 1827,1840 ----
  	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
  	LocTriggerData.tg_newtuple = NULL;
  	LocTriggerData.tg_newtuplebuf = InvalidBuffer;
+ 	ClearTriggerTableSlots(estate);
  	for (i = 0; i < ntrigs; i++)
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  
! 		if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL,
! 							estate, RelationGetDescr(relinfo->ri_RelationDesc),
! 							NULL, newtuple))
  			continue;
  
  		LocTriggerData.tg_trigtuple = oldtuple = newtuple;
*************** ExecARInsertTriggers(EState *estate, Res
*** 1763,1770 ****
  	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
  
  	if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
! 		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! 							  true, NULL, trigtuple, recheckIndexes, NULL);
  }
  
  void
--- 1860,1868 ----
  	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
  
  	if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
! 		AfterTriggerSaveEvent(estate, relinfo,
! 							  TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW,
! 							  NULL, trigtuple, recheckIndexes, NULL);
  }
  
  void
*************** ExecBSDeleteTriggers(EState *estate, Res
*** 1795,1806 ****
  	LocTriggerData.tg_newtuple = NULL;
  	LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
  	LocTriggerData.tg_newtuplebuf = InvalidBuffer;
  	for (i = 0; i < ntrigs; i++)
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  		HeapTuple	newtuple;
  
! 		if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL))
  			continue;
  
  		LocTriggerData.tg_trigger = trigger;
--- 1893,1906 ----
  	LocTriggerData.tg_newtuple = NULL;
  	LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
  	LocTriggerData.tg_newtuplebuf = InvalidBuffer;
+ 	ClearTriggerTableSlots(estate);
  	for (i = 0; i < ntrigs; i++)
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  		HeapTuple	newtuple;
  
! 		if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL,
! 							estate, NULL, NULL, NULL))
  			continue;
  
  		LocTriggerData.tg_trigger = trigger;
*************** ExecASDeleteTriggers(EState *estate, Res
*** 1823,1830 ****
  	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
  
  	if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
! 		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! 							  false, NULL, NULL, NIL, NULL);
  }
  
  bool
--- 1923,1930 ----
  	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
  
  	if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
! 		AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_DELETE,
! 							  NULL, NULL, NIL, NULL);
  }
  
  bool
*************** ExecBRDeleteTriggers(EState *estate, EPQ
*** 1854,1864 ****
  	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
  	LocTriggerData.tg_newtuple = NULL;
  	LocTriggerData.tg_newtuplebuf = InvalidBuffer;
  	for (i = 0; i < ntrigs; i++)
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  
! 		if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL))
  			continue;
  
  		LocTriggerData.tg_trigtuple = trigtuple;
--- 1954,1967 ----
  	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
  	LocTriggerData.tg_newtuple = NULL;
  	LocTriggerData.tg_newtuplebuf = InvalidBuffer;
+ 	ClearTriggerTableSlots(estate);
  	for (i = 0; i < ntrigs; i++)
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  
! 		if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL,
! 							estate, RelationGetDescr(relinfo->ri_RelationDesc),
! 							trigtuple, NULL))
  			continue;
  
  		LocTriggerData.tg_trigtuple = trigtuple;
*************** ExecARDeleteTriggers(EState *estate, Res
*** 1893,1900 ****
  		HeapTuple	trigtuple = GetTupleForTrigger(estate, NULL, relinfo,
  												   tupleid, NULL);
  
! 		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! 							  true, trigtuple, NULL, NIL, NULL);
  		heap_freetuple(trigtuple);
  	}
  }
--- 1996,2004 ----
  		HeapTuple	trigtuple = GetTupleForTrigger(estate, NULL, relinfo,
  												   tupleid, NULL);
  
! 		AfterTriggerSaveEvent(estate, relinfo,
! 							  TRIGGER_EVENT_DELETE | TRIGGER_EVENT_ROW,
! 							  trigtuple, NULL, NIL, NULL);
  		heap_freetuple(trigtuple);
  	}
  }
*************** ExecBSUpdateTriggers(EState *estate, Res
*** 1930,1941 ****
  	LocTriggerData.tg_newtuple = NULL;
  	LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
  	LocTriggerData.tg_newtuplebuf = InvalidBuffer;
  	for (i = 0; i < ntrigs; i++)
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  		HeapTuple	newtuple;
  
! 		if (!TriggerEnabled(trigger, LocTriggerData.tg_event, modifiedCols))
  			continue;
  
  		LocTriggerData.tg_trigger = trigger;
--- 2034,2047 ----
  	LocTriggerData.tg_newtuple = NULL;
  	LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
  	LocTriggerData.tg_newtuplebuf = InvalidBuffer;
+ 	ClearTriggerTableSlots(estate);
  	for (i = 0; i < ntrigs; i++)
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  		HeapTuple	newtuple;
  
! 		if (!TriggerEnabled(trigger, LocTriggerData.tg_event, modifiedCols,
! 							estate, NULL, NULL, NULL))
  			continue;
  
  		LocTriggerData.tg_trigger = trigger;
*************** ExecASUpdateTriggers(EState *estate, Res
*** 1958,1965 ****
  	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
  
  	if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
! 		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! 							  false, NULL, NULL, NIL,
  							  GetModifiedColumns(relinfo, estate));
  }
  
--- 2064,2071 ----
  	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
  
  	if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
! 		AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE,
! 							  NULL, NULL, NIL,
  							  GetModifiedColumns(relinfo, estate));
  }
  
*************** ExecBRUpdateTriggers(EState *estate, EPQ
*** 1999,2009 ****
  		TRIGGER_EVENT_ROW |
  		TRIGGER_EVENT_BEFORE;
  	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
  	for (i = 0; i < ntrigs; i++)
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  
! 		if (!TriggerEnabled(trigger, LocTriggerData.tg_event, modifiedCols))
  			continue;
  
  		LocTriggerData.tg_trigtuple = trigtuple;
--- 2105,2118 ----
  		TRIGGER_EVENT_ROW |
  		TRIGGER_EVENT_BEFORE;
  	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
+ 	ClearTriggerTableSlots(estate);
  	for (i = 0; i < ntrigs; i++)
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  
! 		if (!TriggerEnabled(trigger, LocTriggerData.tg_event, modifiedCols,
! 							estate, RelationGetDescr(relinfo->ri_RelationDesc),
! 							trigtuple, newtuple))
  			continue;
  
  		LocTriggerData.tg_trigtuple = trigtuple;
*************** ExecARUpdateTriggers(EState *estate, Res
*** 2037,2044 ****
  		HeapTuple	trigtuple = GetTupleForTrigger(estate, NULL, relinfo,
  												   tupleid, NULL);
  
! 		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! 							  true, trigtuple, newtuple, recheckIndexes,
  							  GetModifiedColumns(relinfo, estate));
  		heap_freetuple(trigtuple);
  	}
--- 2146,2154 ----
  		HeapTuple	trigtuple = GetTupleForTrigger(estate, NULL, relinfo,
  												   tupleid, NULL);
  
! 		AfterTriggerSaveEvent(estate, relinfo,
! 							  TRIGGER_EVENT_UPDATE | TRIGGER_EVENT_ROW,
! 							  trigtuple, newtuple, recheckIndexes,
  							  GetModifiedColumns(relinfo, estate));
  		heap_freetuple(trigtuple);
  	}
*************** ExecBSTruncateTriggers(EState *estate, R
*** 2072,2083 ****
  	LocTriggerData.tg_newtuple = NULL;
  	LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
  	LocTriggerData.tg_newtuplebuf = InvalidBuffer;
  	for (i = 0; i < ntrigs; i++)
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  		HeapTuple	newtuple;
  
! 		if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL))
  			continue;
  
  		LocTriggerData.tg_trigger = trigger;
--- 2182,2195 ----
  	LocTriggerData.tg_newtuple = NULL;
  	LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
  	LocTriggerData.tg_newtuplebuf = InvalidBuffer;
+ 	ClearTriggerTableSlots(estate);
  	for (i = 0; i < ntrigs; i++)
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  		HeapTuple	newtuple;
  
! 		if (!TriggerEnabled(trigger, LocTriggerData.tg_event, NULL,
! 							estate, NULL, NULL, NULL))
  			continue;
  
  		LocTriggerData.tg_trigger = trigger;
*************** ExecASTruncateTriggers(EState *estate, R
*** 2100,2107 ****
  	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
  
  	if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0)
! 		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE,
! 							  false, NULL, NULL, NIL, NULL);
  }
  
  
--- 2212,2219 ----
  	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
  
  	if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0)
! 		AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_TRUNCATE,
! 							  NULL, NULL, NIL, NULL);
  }
  
  
*************** ltrmark:;
*** 2219,2225 ****
   * Is trigger enabled to fire?
   */
  static bool
! TriggerEnabled(Trigger *trigger, TriggerEvent event, Bitmapset *modifiedCols)
  {
  	/* Check replication-role-dependent enable state */
  	if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
--- 2331,2338 ----
   * Is trigger enabled to fire?
   */
  static bool
! TriggerEnabled(Trigger *trigger, TriggerEvent event, Bitmapset *modifiedCols,
! 		EState *estate, TupleDesc tupdesc, HeapTuple oldtup, HeapTuple newtup)
  {
  	/* Check replication-role-dependent enable state */
  	if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
*************** TriggerEnabled(Trigger *trigger, Trigger
*** 2258,2266 ****
--- 2371,2443 ----
  			return false;
  	}
  
+ 	/* Check for WHEN clause */
+ 	if (trigger->tgqual)
+ 	{
+ 		ExprContext		   *econtext;		
+ 		List			   *predicate;
+ 		TupleTableSlot	   *oldslot = NULL;
+ 		TupleTableSlot	   *newslot = NULL;
+ 
+ 		Assert(estate != NULL);
+ 
+ 		econtext = GetPerTupleExprContext(estate);
+ 
+ 		predicate = list_make1(ExecPrepareExpr((Expr *) trigger->tgqual, estate));
+ 
+ 		/* set OLD and NEW tuples into tupleslots */
+ 		if (TRIGGER_FIRED_FOR_ROW(event))
+ 		{
+ 			Assert(tupdesc != NULL);
+ 
+ 			if (TRIGGER_FIRED_BY_DELETE(event) || TRIGGER_FIRED_BY_UPDATE(event))
+ 			{
+ 				Assert(oldtup != NULL);
+ 
+ 				if (estate->es_trig_oldtup_slot == NULL)
+ 					estate->es_trig_oldtup_slot = ExecInitExtraTupleSlot(estate);
+ 
+ 				oldslot = estate->es_trig_oldtup_slot;
+ 				if (oldslot->tts_tupleDescriptor != tupdesc)
+ 					ExecSetSlotDescriptor(oldslot, tupdesc);
+ 				if (oldslot->tts_tuple != oldtup)
+ 					ExecStoreTuple(oldtup, oldslot, InvalidBuffer, false);
+ 
+ 			}
+ 			if (TRIGGER_FIRED_BY_INSERT(event) || TRIGGER_FIRED_BY_UPDATE(event))
+ 			{
+ 				Assert(newtup != NULL);
+ 
+ 				if (estate->es_trig_tuple_slot == NULL)
+ 					estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate);
+ 
+ 				newslot = estate->es_trig_tuple_slot;
+ 				if (newslot->tts_tupleDescriptor != tupdesc)
+ 					ExecSetSlotDescriptor(newslot, tupdesc);
+ 				if (newslot->tts_tuple != newtup)
+ 					ExecStoreTuple(newtup, newslot, InvalidBuffer, false);
+ 			}
+ 		}
+ 
+ 		econtext->ecxt_innertuple = oldslot;
+ 		econtext->ecxt_outertuple = newslot;
+ 		if (!ExecQual(predicate, econtext, false))
+ 			return false;
+ 	}
+ 
  	return true;
  }
  
+ /* clear slots used by trigger to avoid dangling-reference to tuples */
+ static void
+ ClearTriggerTableSlots(EState *estate)
+ {
+ 	if (estate->es_trig_tuple_slot)
+ 		ExecClearTuple(estate->es_trig_tuple_slot);
+ 	if (estate->es_trig_oldtup_slot)
+ 		ExecClearTuple(estate->es_trig_oldtup_slot);
+ }
+ 
  
  /* ----------
   * After-trigger stuff
*************** AfterTriggerPendingOnRel(Oid relid)
*** 3883,3889 ****
   * ----------
   */
  static void
! AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
  					  HeapTuple oldtup, HeapTuple newtup,
  					  List *recheckIndexes, Bitmapset *modifiedCols)
  {
--- 4060,4066 ----
   * ----------
   */
  static void
! AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, int event,
  					  HeapTuple oldtup, HeapTuple newtup,
  					  List *recheckIndexes, Bitmapset *modifiedCols)
  {
*************** AfterTriggerSaveEvent(ResultRelInfo *rel
*** 3894,3899 ****
--- 4071,4087 ----
  	int			i;
  	int			ntriggers;
  	int		   *tgindx;
+ 	int			offset;
+ 	bool		row_trigger;
+ 
+ 	Assert((event & ~(TRIGGER_EVENT_OPMASK | TRIGGER_EVENT_ROW)) == 0);
+ 
+ 	/*
+ 	 * The event code will be used both as a bitmask and an array offset, so
+ 	 * split it into offset of our array and row trigger flag.
+ 	 */
+ 	offset = (event & TRIGGER_EVENT_OPMASK);
+ 	row_trigger = TRIGGER_FIRED_FOR_ROW(event);
  
  	/*
  	 * Check state.  We use normal tests not Asserts because it is possible
*************** AfterTriggerSaveEvent(ResultRelInfo *rel
*** 3907,3919 ****
  
  	/*
  	 * Validate the event code and collect the associated tuple CTIDs.
- 	 *
- 	 * The event code will be used both as a bitmask and an array offset, so
- 	 * validation is important to make sure we don't walk off the edge of our
- 	 * arrays.
  	 */
  	new_event.ate_flags = 0;
! 	switch (event)
  	{
  		case TRIGGER_EVENT_INSERT:
  			if (row_trigger)
--- 4095,4103 ----
  
  	/*
  	 * Validate the event code and collect the associated tuple CTIDs.
  	 */
  	new_event.ate_flags = 0;
! 	switch (offset)
  	{
  		case TRIGGER_EVENT_INSERT:
  			if (row_trigger)
*************** AfterTriggerSaveEvent(ResultRelInfo *rel
*** 3980,3999 ****
  	 */
  	if (row_trigger)
  	{
! 		ntriggers = trigdesc->n_after_row[event];
! 		tgindx = trigdesc->tg_after_row[event];
  	}
  	else
  	{
! 		ntriggers = trigdesc->n_after_statement[event];
! 		tgindx = trigdesc->tg_after_statement[event];
  	}
  
  	for (i = 0; i < ntriggers; i++)
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  
! 		if (!TriggerEnabled(trigger, event, modifiedCols))
  			continue;
  
  		/*
--- 4164,4186 ----
  	 */
  	if (row_trigger)
  	{
! 		ntriggers = trigdesc->n_after_row[offset];
! 		tgindx = trigdesc->tg_after_row[offset];
  	}
  	else
  	{
! 		ntriggers = trigdesc->n_after_statement[offset];
! 		tgindx = trigdesc->tg_after_statement[offset];
  	}
  
+ 	ClearTriggerTableSlots(estate);
  	for (i = 0; i < ntriggers; i++)
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  
! 		if (!TriggerEnabled(trigger, event, modifiedCols,
! 							estate, RelationGetDescr(relinfo->ri_RelationDesc),
! 							oldtup, newtup))
  			continue;
  
  		/*
*************** AfterTriggerSaveEvent(ResultRelInfo *rel
*** 4054,4062 ****
  		/*
  		 * Fill in event structure and add it to the current query's queue.
  		 */
! 		new_shared.ats_event =
! 			(event & TRIGGER_EVENT_OPMASK) |
! 			(row_trigger ? TRIGGER_EVENT_ROW : 0) |
  			(trigger->tgdeferrable ? AFTER_TRIGGER_DEFERRABLE : 0) |
  			(trigger->tginitdeferred ? AFTER_TRIGGER_INITDEFERRED : 0);
  		new_shared.ats_tgoid = trigger->tgoid;
--- 4241,4247 ----
  		/*
  		 * Fill in event structure and add it to the current query's queue.
  		 */
! 		new_shared.ats_event = event |
  			(trigger->tgdeferrable ? AFTER_TRIGGER_DEFERRABLE : 0) |
  			(trigger->tginitdeferred ? AFTER_TRIGGER_INITDEFERRED : 0);
  		new_shared.ats_tgoid = trigger->tgoid;
diff -cprN head/src/backend/executor/execMain.c work/src/backend/executor/execMain.c
*** head/src/backend/executor/execMain.c	2009-10-26 11:26:29.000000000 +0900
--- work/src/backend/executor/execMain.c	2009-11-18 16:29:02.219689260 +0900
*************** InitPlan(QueryDesc *queryDesc, int eflag
*** 752,757 ****
--- 752,758 ----
  	 */
  	estate->es_tupleTable = NIL;
  	estate->es_trig_tuple_slot = NULL;
+ 	estate->es_trig_oldtup_slot = NULL;
  
  	/* mark EvalPlanQual not active */
  	estate->es_epqTuple = NULL;
diff -cprN head/src/backend/executor/execQual.c work/src/backend/executor/execQual.c
*** head/src/backend/executor/execQual.c	2009-11-05 07:26:05.000000000 +0900
--- work/src/backend/executor/execQual.c	2009-11-18 16:29:02.220969834 +0900
*************** static Datum ExecEvalArrayCoerceExpr(Arr
*** 170,175 ****
--- 170,176 ----
  						bool *isNull, ExprDoneCond *isDone);
  static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
  					  bool *isNull, ExprDoneCond *isDone);
+ static TupleTableSlot *get_slot(ExprContext *econtext, Index varno);
  
  
  /* ----------------------------------------------------------------
*************** ExecEvalVar(ExprState *exprstate, ExprCo
*** 501,523 ****
  	 */
  	attnum = variable->varattno;
  
! 	switch (variable->varno)
! 	{
! 		case INNER:				/* get the tuple from the inner node */
! 			slot = econtext->ecxt_innertuple;
! 			Assert(attnum > 0);
! 			break;
! 
! 		case OUTER:				/* get the tuple from the outer node */
! 			slot = econtext->ecxt_outertuple;
! 			Assert(attnum > 0);
! 			break;
! 
! 		default:				/* get the tuple from the relation being
! 								 * scanned */
! 			slot = econtext->ecxt_scantuple;
! 			break;
! 	}
  
  	if (attnum != InvalidAttrNumber)
  	{
--- 502,508 ----
  	 */
  	attnum = variable->varattno;
  
! 	slot = get_slot(econtext, variable->varno);
  
  	if (attnum != InvalidAttrNumber)
  	{
*************** ExecEvalScalarVar(ExprState *exprstate, 
*** 682,702 ****
  		*isDone = ExprSingleResult;
  
  	/* Get the input slot and attribute number we want */
! 	switch (variable->varno)
! 	{
! 		case INNER:				/* get the tuple from the inner node */
! 			slot = econtext->ecxt_innertuple;
! 			break;
! 
! 		case OUTER:				/* get the tuple from the outer node */
! 			slot = econtext->ecxt_outertuple;
! 			break;
! 
! 		default:				/* get the tuple from the relation being
! 								 * scanned */
! 			slot = econtext->ecxt_scantuple;
! 			break;
! 	}
  
  	attnum = variable->varattno;
  
--- 667,673 ----
  		*isDone = ExprSingleResult;
  
  	/* Get the input slot and attribute number we want */
! 	slot = get_slot(econtext, variable->varno);
  
  	attnum = variable->varattno;
  
*************** ExecEvalWholeRowVar(ExprState *exprstate
*** 715,721 ****
  					bool *isNull, ExprDoneCond *isDone)
  {
  	Var		   *variable = (Var *) exprstate->expr;
! 	TupleTableSlot *slot = econtext->ecxt_scantuple;
  	HeapTuple	tuple;
  	TupleDesc	tupleDesc;
  	HeapTupleHeader dtuple;
--- 686,692 ----
  					bool *isNull, ExprDoneCond *isDone)
  {
  	Var		   *variable = (Var *) exprstate->expr;
! 	TupleTableSlot *slot = get_slot(econtext, variable->varno);
  	HeapTuple	tuple;
  	TupleDesc	tupleDesc;
  	HeapTupleHeader dtuple;
*************** ExecEvalWholeRowSlow(ExprState *exprstat
*** 766,772 ****
  					 bool *isNull, ExprDoneCond *isDone)
  {
  	Var		   *variable = (Var *) exprstate->expr;
! 	TupleTableSlot *slot = econtext->ecxt_scantuple;
  	HeapTuple	tuple;
  	TupleDesc	var_tupdesc;
  	HeapTupleHeader dtuple;
--- 737,743 ----
  					 bool *isNull, ExprDoneCond *isDone)
  {
  	Var		   *variable = (Var *) exprstate->expr;
! 	TupleTableSlot *slot = get_slot(econtext, variable->varno);
  	HeapTuple	tuple;
  	TupleDesc	var_tupdesc;
  	HeapTupleHeader dtuple;
*************** ExecProject(ProjectionInfo *projInfo, Ex
*** 5179,5181 ****
--- 5150,5169 ----
  	 */
  	return ExecStoreVirtualTuple(slot);
  }
+ 
+ static TupleTableSlot *
+ get_slot(ExprContext *econtext, Index varno)
+ {
+ 	switch (varno)
+ 	{
+ 		case INNER:	/* get the tuple from the inner node */
+ 			Assert(econtext->ecxt_innertuple != NULL);
+ 			return econtext->ecxt_innertuple;
+ 		case OUTER:	/* get the tuple from the outer node */
+ 			Assert(econtext->ecxt_outertuple != NULL);
+ 			return econtext->ecxt_outertuple;
+ 		default:	/* get the tuple from the relation being scanned */
+ 			Assert(econtext->ecxt_scantuple != NULL);
+ 			return econtext->ecxt_scantuple;
+ 	}
+ }
diff -cprN head/src/backend/executor/execUtils.c work/src/backend/executor/execUtils.c
*** head/src/backend/executor/execUtils.c	2009-10-26 11:26:29.000000000 +0900
--- work/src/backend/executor/execUtils.c	2009-11-18 16:29:02.221857932 +0900
*************** CreateExecutorState(void)
*** 117,122 ****
--- 117,123 ----
  
  	estate->es_trig_target_relations = NIL;
  	estate->es_trig_tuple_slot = NULL;
+ 	estate->es_trig_oldtup_slot = NULL;
  
  	estate->es_param_list_info = NULL;
  	estate->es_param_exec_vals = NULL;
diff -cprN head/src/backend/nodes/copyfuncs.c work/src/backend/nodes/copyfuncs.c
*** head/src/backend/nodes/copyfuncs.c	2009-11-17 06:32:06.000000000 +0900
--- work/src/backend/nodes/copyfuncs.c	2009-11-18 16:29:02.221857932 +0900
*************** _copyCreateTrigStmt(CreateTrigStmt *from
*** 3180,3185 ****
--- 3180,3186 ----
  	COPY_SCALAR_FIELD(row);
  	COPY_SCALAR_FIELD(events);
  	COPY_NODE_FIELD(columns);
+ 	COPY_NODE_FIELD(whenClause);
  	COPY_SCALAR_FIELD(isconstraint);
  	COPY_SCALAR_FIELD(deferrable);
  	COPY_SCALAR_FIELD(initdeferred);
diff -cprN head/src/backend/nodes/equalfuncs.c work/src/backend/nodes/equalfuncs.c
*** head/src/backend/nodes/equalfuncs.c	2009-11-17 06:32:06.000000000 +0900
--- work/src/backend/nodes/equalfuncs.c	2009-11-18 16:29:02.222689821 +0900
*************** _equalCreateTrigStmt(CreateTrigStmt *a, 
*** 1670,1675 ****
--- 1670,1676 ----
  	COMPARE_SCALAR_FIELD(row);
  	COMPARE_SCALAR_FIELD(events);
  	COMPARE_NODE_FIELD(columns);
+ 	COMPARE_NODE_FIELD(whenClause);
  	COMPARE_SCALAR_FIELD(isconstraint);
  	COMPARE_SCALAR_FIELD(deferrable);
  	COMPARE_SCALAR_FIELD(initdeferred);
diff -cprN head/src/backend/parser/gram.y work/src/backend/parser/gram.y
*** head/src/backend/parser/gram.y	2009-11-17 06:32:06.000000000 +0900
--- work/src/backend/parser/gram.y	2009-11-18 16:29:02.223620775 +0900
*************** static TypeName *TableFuncTypeName(List 
*** 249,254 ****
--- 249,255 ----
  
  %type <list>	TriggerEvents TriggerOneEvent
  %type <value>	TriggerFuncArg
+ %type <node>	TriggerWhen
  
  %type <str>		copy_file_name
  				database_name access_method_clause access_method attr_name
*************** AlterUserMappingStmt: ALTER USER MAPPING
*** 3227,3240 ****
  
  CreateTrigStmt:
  			CREATE TRIGGER name TriggerActionTime TriggerEvents ON
! 			qualified_name TriggerForSpec EXECUTE PROCEDURE
! 			func_name '(' TriggerFuncArgs ')'
  				{
  					CreateTrigStmt *n = makeNode(CreateTrigStmt);
  					n->trigname = $3;
  					n->relation = $7;
! 					n->funcname = $11;
! 					n->args = $13;
  					n->before = $4;
  					n->row = $8;
  					n->events = intVal(linitial($5));
--- 3228,3242 ----
  
  CreateTrigStmt:
  			CREATE TRIGGER name TriggerActionTime TriggerEvents ON
! 			qualified_name TriggerForSpec
! 			TriggerWhen EXECUTE PROCEDURE func_name '(' TriggerFuncArgs ')'
  				{
  					CreateTrigStmt *n = makeNode(CreateTrigStmt);
  					n->trigname = $3;
  					n->relation = $7;
! 					n->whenClause = $9;
! 					n->funcname = $12;
! 					n->args = $14;
  					n->before = $4;
  					n->row = $8;
  					n->events = intVal(linitial($5));
*************** TriggerFuncArg:
*** 3355,3360 ****
--- 3357,3367 ----
  			| ColId									{ $$ = makeString($1); }
  		;
  
+ TriggerWhen:
+ 			WHEN '(' a_expr ')'						{ $$ = $3; }
+ 			| /*EMPTY*/								{ $$ = NULL; }
+ 		;
+ 
  OptConstrFromTable:
  			FROM qualified_name						{ $$ = $2; }
  			| /*EMPTY*/								{ $$ = NULL; }
diff -cprN head/src/backend/tcop/utility.c work/src/backend/tcop/utility.c
*** head/src/backend/tcop/utility.c	2009-11-17 06:32:07.000000000 +0900
--- work/src/backend/tcop/utility.c	2009-11-18 16:29:02.224641175 +0900
*************** ProcessUtility(Node *parsetree,
*** 939,945 ****
  			break;
  
  		case T_CreateTrigStmt:
! 			CreateTrigger((CreateTrigStmt *) parsetree,
  						  InvalidOid, InvalidOid, NULL, true);
  			break;
  
--- 939,945 ----
  			break;
  
  		case T_CreateTrigStmt:
! 			CreateTrigger((CreateTrigStmt *) parsetree, queryString,
  						  InvalidOid, InvalidOid, NULL, true);
  			break;
  
diff -cprN head/src/backend/utils/adt/ruleutils.c work/src/backend/utils/adt/ruleutils.c
*** head/src/backend/utils/adt/ruleutils.c	2009-11-06 08:24:25.000000000 +0900
--- work/src/backend/utils/adt/ruleutils.c	2009-11-18 16:29:02.225607111 +0900
***************
*** 42,47 ****
--- 42,48 ----
  #include "parser/keywords.h"
  #include "parser/parse_func.h"
  #include "parser/parse_oper.h"
+ #include "parser/parse_relation.h"
  #include "parser/parser.h"
  #include "parser/parsetree.h"
  #include "rewrite/rewriteHandler.h"
*************** pg_get_triggerdef_worker(Oid trigid, boo
*** 487,492 ****
--- 488,495 ----
  	SysScanDesc tgscan;
  	int			findx = 0;
  	char	   *tgname;
+ 	Datum		value;
+ 	bool		isnull;
  
  	/*
  	 * Fetch the pg_trigger tuple by the Oid of the trigger
*************** pg_get_triggerdef_worker(Oid trigid, boo
*** 596,618 ****
  		appendStringInfo(&buf, "FOR EACH STATEMENT");
  	appendStringInfoString(&buf, pretty ? "\n    " : " ");
  
  	appendStringInfo(&buf, "EXECUTE PROCEDURE %s(",
  					 generate_function_name(trigrec->tgfoid, 0,
  											NIL, NULL, NULL));
  
  	if (trigrec->tgnargs > 0)
  	{
- 		bytea	   *val;
- 		bool		isnull;
  		char	   *p;
  		int			i;
  
! 		val = DatumGetByteaP(fastgetattr(ht_trig,
! 										 Anum_pg_trigger_tgargs,
! 										 tgrel->rd_att, &isnull));
  		if (isnull)
  			elog(ERROR, "tgargs is null for trigger %u", trigid);
! 		p = (char *) VARDATA(val);
  		for (i = 0; i < trigrec->tgnargs; i++)
  		{
  			if (i > 0)
--- 599,663 ----
  		appendStringInfo(&buf, "FOR EACH STATEMENT");
  	appendStringInfoString(&buf, pretty ? "\n    " : " ");
  
+ 	/* If the trigger has an event qualification, add it */
+ 	value = fastgetattr(ht_trig, Anum_pg_trigger_tgqual,
+ 						tgrel->rd_att, &isnull);
+ 	if (!isnull)
+ 	{
+ 		Node			   *qual;
+ 		deparse_context		context;
+ 		deparse_namespace	dpns;
+ 		RangeTblEntry	   *oldrte;
+ 		RangeTblEntry	   *newrte;
+ 
+ 		appendStringInfo(&buf, "WHEN (");
+ 
+ 		qual = stringToNode(TextDatumGetCString(value));
+ 
+ 		context.buf = &buf;
+ 		context.namespaces = list_make1(&dpns);
+ 		context.windowClause = NIL;
+ 		context.windowTList = NIL;
+ 		context.varprefix = true;
+ 		context.prettyFlags = PRETTYFLAG_PAREN | (pretty ? PRETTYFLAG_INDENT : 0);
+ 		context.indentLevel = PRETTYINDENT_STD;
+ 
+ 		/* Build a minimal NEW and OLD RTEs for the rel */
+ 		oldrte = makeNode(RangeTblEntry);
+ 		oldrte->rtekind = RTE_RELATION;
+ 		oldrte->alias = oldrte->eref = makeAlias("old", NIL);
+ 		oldrte->relid = trigrec->tgrelid;
+ 
+ 		newrte = makeNode(RangeTblEntry);
+ 		newrte->rtekind = RTE_RELATION;
+ 		newrte->alias = newrte->eref = makeAlias("new", NIL);
+ 		newrte->relid = trigrec->tgrelid;
+ 
+ 		/* Build two-elements rtable */
+ 		dpns.rtable = list_make2(oldrte, newrte);
+ 		dpns.ctes = NIL;
+ 		dpns.subplans = NIL;
+ 		dpns.outer_plan = dpns.inner_plan = NULL;
+ 
+ 		get_rule_expr(qual, &context, false);
+ 
+ 		appendStringInfo(&buf, ")%s", pretty ? "\n    " : " ");
+ 	}
+ 
  	appendStringInfo(&buf, "EXECUTE PROCEDURE %s(",
  					 generate_function_name(trigrec->tgfoid, 0,
  											NIL, NULL, NULL));
  
  	if (trigrec->tgnargs > 0)
  	{
  		char	   *p;
  		int			i;
  
! 		value = fastgetattr(ht_trig, Anum_pg_trigger_tgargs,
! 							tgrel->rd_att, &isnull);
  		if (isnull)
  			elog(ERROR, "tgargs is null for trigger %u", trigid);
! 		p = (char *) VARDATA(DatumGetByteaP(value));
  		for (i = 0; i < trigrec->tgnargs; i++)
  		{
  			if (i > 0)
diff -cprN head/src/include/catalog/pg_trigger.h work/src/include/catalog/pg_trigger.h
*** head/src/include/catalog/pg_trigger.h	2009-10-15 07:14:24.000000000 +0900
--- work/src/include/catalog/pg_trigger.h	2009-11-18 16:29:02.225607111 +0900
*************** CATALOG(pg_trigger,2620)
*** 52,60 ****
  	bool		tginitdeferred; /* constraint trigger is deferred initially */
  	int2		tgnargs;		/* # of extra arguments in tgargs */
  
! 	/* VARIABLE LENGTH FIELDS (note: these are not supposed to be null) */
  	int2vector	tgattr;			/* column numbers, if trigger is on columns */
  	bytea		tgargs;			/* first\000second\000tgnargs\000 */
  } FormData_pg_trigger;
  
  /* ----------------
--- 52,61 ----
  	bool		tginitdeferred; /* constraint trigger is deferred initially */
  	int2		tgnargs;		/* # of extra arguments in tgargs */
  
! 	/* VARIABLE LENGTH FIELDS (note: tgattr and tgargs are not supposed to be null) */
  	int2vector	tgattr;			/* column numbers, if trigger is on columns */
  	bytea		tgargs;			/* first\000second\000tgnargs\000 */
+ 	text		tgqual;			/* expression tree of WHEN clause, or NULL */
  } FormData_pg_trigger;
  
  /* ----------------
*************** typedef FormData_pg_trigger *Form_pg_tri
*** 68,74 ****
   *		compiler constants for pg_trigger
   * ----------------
   */
! #define Natts_pg_trigger				15
  #define Anum_pg_trigger_tgrelid			1
  #define Anum_pg_trigger_tgname			2
  #define Anum_pg_trigger_tgfoid			3
--- 69,75 ----
   *		compiler constants for pg_trigger
   * ----------------
   */
! #define Natts_pg_trigger				16
  #define Anum_pg_trigger_tgrelid			1
  #define Anum_pg_trigger_tgname			2
  #define Anum_pg_trigger_tgfoid			3
*************** typedef FormData_pg_trigger *Form_pg_tri
*** 84,89 ****
--- 85,91 ----
  #define Anum_pg_trigger_tgnargs			13
  #define Anum_pg_trigger_tgattr			14
  #define Anum_pg_trigger_tgargs			15
+ #define Anum_pg_trigger_tgqual			16
  
  /* Bits within tgtype */
  #define TRIGGER_TYPE_ROW				(1 << 0)
diff -cprN head/src/include/catalog/toasting.h work/src/include/catalog/toasting.h
*** head/src/include/catalog/toasting.h	2009-10-08 07:14:25.000000000 +0900
--- work/src/include/catalog/toasting.h	2009-11-18 16:48:52.390591559 +0900
*************** DECLARE_TOAST(pg_description, 2834, 2835
*** 47,52 ****
--- 47,53 ----
  DECLARE_TOAST(pg_proc, 2836, 2837);
  DECLARE_TOAST(pg_rewrite, 2838, 2839);
  DECLARE_TOAST(pg_statistic, 2840, 2841);
+ DECLARE_TOAST(pg_trigger, 2336, 2337);
  
  /* shared catalogs */
  DECLARE_TOAST(pg_authid, 2842, 2843);
diff -cprN head/src/include/commands/trigger.h work/src/include/commands/trigger.h
*** head/src/include/commands/trigger.h	2009-10-26 11:26:41.000000000 +0900
--- work/src/include/commands/trigger.h	2009-11-18 16:29:02.225607111 +0900
*************** extern PGDLLIMPORT int SessionReplicatio
*** 104,110 ****
  #define TRIGGER_FIRES_ON_REPLICA			'R'
  #define TRIGGER_DISABLED					'D'
  
! extern Oid CreateTrigger(CreateTrigStmt *stmt,
  			  Oid constraintOid, Oid indexOid, const char *prefix,
  			  bool checkPermissions);
  
--- 104,110 ----
  #define TRIGGER_FIRES_ON_REPLICA			'R'
  #define TRIGGER_DISABLED					'D'
  
! extern Oid CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
  			  Oid constraintOid, Oid indexOid, const char *prefix,
  			  bool checkPermissions);
  
diff -cprN head/src/include/nodes/execnodes.h work/src/include/nodes/execnodes.h
*** head/src/include/nodes/execnodes.h	2009-10-26 11:26:41.000000000 +0900
--- work/src/include/nodes/execnodes.h	2009-11-18 16:29:02.226604133 +0900
*************** typedef struct EState
*** 345,351 ****
  
  	/* Stuff used for firing triggers: */
  	List	   *es_trig_target_relations;		/* trigger-only ResultRelInfos */
! 	TupleTableSlot *es_trig_tuple_slot; /* for trigger output tuples */
  
  	/* Parameter info: */
  	ParamListInfo es_param_list_info;	/* values of external params */
--- 345,352 ----
  
  	/* Stuff used for firing triggers: */
  	List	   *es_trig_target_relations;		/* trigger-only ResultRelInfos */
! 	TupleTableSlot *es_trig_tuple_slot;		/* for trigger output tuples */
! 	TupleTableSlot *es_trig_oldtup_slot;	/* for trigger old tuples */
  
  	/* Parameter info: */
  	ParamListInfo es_param_list_info;	/* values of external params */
diff -cprN head/src/include/nodes/parsenodes.h work/src/include/nodes/parsenodes.h
*** head/src/include/nodes/parsenodes.h	2009-11-17 06:32:07.000000000 +0900
--- work/src/include/nodes/parsenodes.h	2009-11-18 16:29:02.226604133 +0900
*************** typedef struct CreateTrigStmt
*** 1571,1576 ****
--- 1571,1577 ----
  	/* 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 */
+ 	Node	   *whenClause;		/* qualifications */
  
  	/* The following are used for constraint triggers (RI and unique checks) */
  	bool		isconstraint;	/* This is a constraint trigger */
diff -cprN head/src/include/utils/rel.h work/src/include/utils/rel.h
*** head/src/include/utils/rel.h	2009-07-28 11:56:31.000000000 +0900
--- work/src/include/utils/rel.h	2009-11-18 16:29:02.227553816 +0900
*************** typedef struct Trigger
*** 66,71 ****
--- 66,72 ----
  	int16		tgnattr;
  	int16	   *tgattr;
  	char	  **tgargs;
+ 	Node	   *tgqual;
  } Trigger;
  
  typedef struct TriggerDesc
diff -cprN head/src/test/regress/expected/triggers.out work/src/test/regress/expected/triggers.out
*** head/src/test/regress/expected/triggers.out	2009-10-15 07:14:25.000000000 +0900
--- work/src/test/regress/expected/triggers.out	2009-11-18 17:01:24.539549414 +0900
*************** SELECT * FROM main_table ORDER BY a, b;
*** 322,327 ****
--- 322,403 ----
      |   
  (8 rows)
  
+ -- Trigger with WHEN clause
+ CREATE TRIGGER modified_a BEFORE UPDATE OF a ON main_table
+ FOR EACH ROW WHEN (OLD.a <> NEW.a) EXECUTE PROCEDURE trigger_func('modified_a');
+ CREATE TRIGGER modified_any BEFORE UPDATE OF a ON main_table
+ FOR EACH ROW WHEN (OLD.* IS DISTINCT FROM NEW.*) EXECUTE PROCEDURE trigger_func('modified_any');
+ CREATE TRIGGER insert_a AFTER INSERT ON main_table
+ FOR EACH ROW WHEN (NEW.a = 123) EXECUTE PROCEDURE trigger_func('insert_a');
+ CREATE TRIGGER delete_a AFTER DELETE ON main_table
+ FOR EACH ROW WHEN (OLD.a = 123) EXECUTE PROCEDURE trigger_func('delete_a');
+ CREATE TRIGGER insert_when BEFORE INSERT ON main_table
+ FOR EACH STATEMENT WHEN (true) EXECUTE PROCEDURE trigger_func('insert_when');
+ CREATE TRIGGER delete_when AFTER DELETE ON main_table
+ FOR EACH STATEMENT WHEN (true) EXECUTE PROCEDURE trigger_func('delete_when');
+ INSERT INTO main_table (a) VALUES (123), (456);
+ NOTICE:  trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT
+ NOTICE:  trigger_func(insert_when) called: action = INSERT, when = BEFORE, level = STATEMENT
+ NOTICE:  trigger_func(insert_a) called: action = INSERT, when = AFTER, level = ROW
+ NOTICE:  trigger_func(after_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT
+ COPY main_table FROM stdin;
+ NOTICE:  trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT
+ NOTICE:  trigger_func(insert_when) called: action = INSERT, when = BEFORE, level = STATEMENT
+ NOTICE:  trigger_func(insert_a) called: action = INSERT, when = AFTER, level = ROW
+ NOTICE:  trigger_func(after_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT
+ DELETE FROM main_table WHERE a IN (123, 456);
+ NOTICE:  trigger_func(delete_a) called: action = DELETE, when = AFTER, level = ROW
+ NOTICE:  trigger_func(delete_a) called: action = DELETE, when = AFTER, level = ROW
+ NOTICE:  trigger_func(delete_when) called: action = DELETE, when = AFTER, level = STATEMENT
+ UPDATE main_table SET a = 50, b = 60;
+ NOTICE:  trigger_func(modified_any) called: action = UPDATE, when = BEFORE, level = ROW
+ NOTICE:  trigger_func(modified_any) called: action = UPDATE, when = BEFORE, level = ROW
+ NOTICE:  trigger_func(modified_a) called: action = UPDATE, when = BEFORE, level = ROW
+ NOTICE:  trigger_func(modified_a) called: action = UPDATE, when = BEFORE, level = ROW
+ NOTICE:  trigger_func(modified_a) called: action = UPDATE, when = BEFORE, level = ROW
+ NOTICE:  trigger_func(modified_a) called: action = UPDATE, when = BEFORE, level = ROW
+ NOTICE:  trigger_func(modified_a) called: action = UPDATE, when = BEFORE, 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
+ SELECT * FROM main_table ORDER BY a, b;
+  a  | b  
+ ----+----
+   6 | 10
+  21 | 20
+  30 | 40
+  31 | 10
+  50 | 35
+  50 | 60
+  81 | 15
+     |   
+ (8 rows)
+ 
+ SELECT pg_get_triggerdef(oid, true) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'modified_a';
+                 pg_get_triggerdef                 
+ --------------------------------------------------
+  CREATE TRIGGER modified_a
+      BEFORE UPDATE OF a ON main_table
+      FOR EACH ROW
+      WHEN (old.a <> new.a)
+      EXECUTE PROCEDURE trigger_func('modified_a')
+ (1 row)
+ 
+ SELECT pg_get_triggerdef(oid, true) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'modified_any';
+                  pg_get_triggerdef                  
+ ----------------------------------------------------
+  CREATE TRIGGER modified_any
+      BEFORE UPDATE OF a ON main_table
+      FOR EACH ROW
+      WHEN (old.* IS DISTINCT FROM new.*)
+      EXECUTE PROCEDURE trigger_func('modified_any')
+ (1 row)
+ 
+ DROP TRIGGER modified_a ON main_table;
+ DROP TRIGGER modified_any ON main_table;
+ DROP TRIGGER insert_a ON main_table;
+ DROP TRIGGER delete_a ON main_table;
+ DROP TRIGGER insert_when ON main_table;
+ DROP TRIGGER delete_when ON main_table;
  -- Test column-level triggers
  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_f
*** 393,398 ****
--- 469,489 ----
  ERROR:  syntax error at or near "OF"
  LINE 1: CREATE TRIGGER error_ins_a BEFORE INSERT OF a ON main_table
                                                   ^
+ CREATE TRIGGER error_ins_when BEFORE INSERT OR UPDATE ON main_table
+ FOR EACH ROW WHEN (OLD.a <> NEW.a) EXECUTE PROCEDURE trigger_func('error_ins_when');
+ ERROR:  missing FROM-clause entry for table "old"
+ LINE 2: FOR EACH ROW WHEN (OLD.a <> NEW.a) EXECUTE PROCEDURE trigger...
+                            ^
+ CREATE TRIGGER error_del_when BEFORE DELETE OR UPDATE ON main_table
+ FOR EACH ROW WHEN (OLD.a <> NEW.a) EXECUTE PROCEDURE trigger_func('error_del_when');
+ ERROR:  missing FROM-clause entry for table "new"
+ LINE 2: FOR EACH ROW WHEN (OLD.a <> NEW.a) EXECUTE PROCEDURE trigger...
+                                     ^
+ CREATE TRIGGER error_stmt_when BEFORE UPDATE OF a ON main_table
+ FOR EACH STATEMENT WHEN (OLD.* IS DISTINCT FROM NEW.*) EXECUTE PROCEDURE trigger_func('error_stmt_when');
+ ERROR:  missing FROM-clause entry for table "old"
+ LINE 2: FOR EACH STATEMENT WHEN (OLD.* IS DISTINCT FROM NEW.*) EXECU...
+                                  ^
  -- check dependency restrictions
  ALTER TABLE main_table DROP COLUMN b;
  ERROR:  cannot drop table main_table column b because other objects depend on it
diff -cprN head/src/test/regress/sql/triggers.sql work/src/test/regress/sql/triggers.sql
*** head/src/test/regress/sql/triggers.sql	2009-10-15 07:14:25.000000000 +0900
--- work/src/test/regress/sql/triggers.sql	2009-11-18 16:34:10.518923028 +0900
*************** COPY main_table (a, b) FROM stdin;
*** 254,259 ****
--- 254,289 ----
  
  SELECT * FROM main_table ORDER BY a, b;
  
+ -- Trigger with WHEN clause
+ CREATE TRIGGER modified_a BEFORE UPDATE OF a ON main_table
+ FOR EACH ROW WHEN (OLD.a <> NEW.a) EXECUTE PROCEDURE trigger_func('modified_a');
+ CREATE TRIGGER modified_any BEFORE UPDATE OF a ON main_table
+ FOR EACH ROW WHEN (OLD.* IS DISTINCT FROM NEW.*) EXECUTE PROCEDURE trigger_func('modified_any');
+ CREATE TRIGGER insert_a AFTER INSERT ON main_table
+ FOR EACH ROW WHEN (NEW.a = 123) EXECUTE PROCEDURE trigger_func('insert_a');
+ CREATE TRIGGER delete_a AFTER DELETE ON main_table
+ FOR EACH ROW WHEN (OLD.a = 123) EXECUTE PROCEDURE trigger_func('delete_a');
+ CREATE TRIGGER insert_when BEFORE INSERT ON main_table
+ FOR EACH STATEMENT WHEN (true) EXECUTE PROCEDURE trigger_func('insert_when');
+ CREATE TRIGGER delete_when AFTER DELETE ON main_table
+ FOR EACH STATEMENT WHEN (true) EXECUTE PROCEDURE trigger_func('delete_when');
+ INSERT INTO main_table (a) VALUES (123), (456);
+ COPY main_table FROM stdin;
+ 123	999
+ 456	999
+ \.
+ DELETE FROM main_table WHERE a IN (123, 456);
+ UPDATE main_table SET a = 50, b = 60;
+ SELECT * FROM main_table ORDER BY a, b;
+ SELECT pg_get_triggerdef(oid, true) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'modified_a';
+ SELECT pg_get_triggerdef(oid, true) FROM pg_trigger WHERE tgrelid = 'main_table'::regclass AND tgname = 'modified_any';
+ DROP TRIGGER modified_a ON main_table;
+ DROP TRIGGER modified_any ON main_table;
+ DROP TRIGGER insert_a ON main_table;
+ DROP TRIGGER delete_a ON main_table;
+ DROP TRIGGER insert_when ON main_table;
+ DROP TRIGGER delete_when ON main_table;
+ 
  -- Test column-level triggers
  DROP TRIGGER after_upd_row_trig ON main_table;
  
*************** FOR EACH ROW EXECUTE PROCEDURE trigger_f
*** 283,288 ****
--- 313,325 ----
  CREATE TRIGGER error_ins_a BEFORE INSERT OF a ON main_table
  FOR EACH ROW EXECUTE PROCEDURE trigger_func('error_ins_a');
  
+ CREATE TRIGGER error_ins_when BEFORE INSERT OR UPDATE ON main_table
+ FOR EACH ROW WHEN (OLD.a <> NEW.a) EXECUTE PROCEDURE trigger_func('error_ins_when');
+ CREATE TRIGGER error_del_when BEFORE DELETE OR UPDATE ON main_table
+ FOR EACH ROW WHEN (OLD.a <> NEW.a) EXECUTE PROCEDURE trigger_func('error_del_when');
+ CREATE TRIGGER error_stmt_when BEFORE UPDATE OF a ON main_table
+ FOR EACH STATEMENT WHEN (OLD.* IS DISTINCT FROM NEW.*) EXECUTE PROCEDURE trigger_func('error_stmt_when');
+ 
  -- check dependency restrictions
  ALTER TABLE main_table DROP COLUMN b;
  -- this should succeed, but we'll roll it back to keep the triggers around