Index: doc/src/sgml/ref/create_trigger.sgml
===================================================================
RCS file: /usr/local/cvsroot/pgsql/doc/src/sgml/ref/create_trigger.sgml,v
retrieving revision 1.43
diff -c -p -r1.43 create_trigger.sgml
*** doc/src/sgml/ref/create_trigger.sgml	9 Dec 2005 19:39:41 -0000	1.43
--- doc/src/sgml/ref/create_trigger.sgml	7 Jul 2006 21:58:04 -0000
*************** PostgreSQL documentation
*** 18,27 ****
--- 18,32 ----
    <primary>CREATE TRIGGER</primary>
   </indexterm>
  
+  <indexterm zone="sql-createtrigger">
+   <primary>WHEN</primary>
+  </indexterm>
+ 
   <refsynopsisdiv>
  <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">expr</> ) ]
      EXECUTE PROCEDURE <replaceable class="PARAMETER">funcname</replaceable> ( <replaceable class="PARAMETER">arguments</replaceable> )
  </synopsis>
   </refsynopsisdiv>
*************** CREATE TRIGGER <replaceable class="PARAM
*** 143,148 ****
--- 148,174 ----
     </varlistentry>
  
     <varlistentry>
+     <term><replaceable class="parameter">expr</replaceable></term>
+     <listitem>
+      <para>
+       An SQL expression which returns a <type>boolean</type> result.
+      </para>
+ 
+      <para>
+       <literal>INSERT</literal> triggers may refer only to the 
+       <literal>NEW</literal> table. <literal>DELETE</literal> triggers
+       may only refer to the <literal>OLD</literal> table.
+       <literal>UPDATE</literal> triggers may refer to both. The
+       expression may not refer to any other tables.
+      </para>
+ 
+      <para>
+       This feature is not supported on <literal>FOR STATEMENT</> triggers.
+      <para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><replaceable class="parameter">funcname</replaceable></term>
      <listitem>
       <para>
Index: src/backend/commands/trigger.c
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/backend/commands/trigger.c,v
retrieving revision 1.203
diff -c -p -r1.203 trigger.c
*** src/backend/commands/trigger.c	16 Jun 2006 20:23:44 -0000	1.203
--- src/backend/commands/trigger.c	8 Jul 2006 10:12:46 -0000
***************
*** 31,37 ****
--- 31,39 ----
  #include "executor/instrument.h"
  #include "miscadmin.h"
  #include "nodes/makefuncs.h"
+ #include "optimizer/clauses.h"
  #include "parser/parse_func.h"
+ #include "rewrite/rewriteManip.h"
  #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/fmgroids.h"
*************** static HeapTuple ExecCallTriggerFunc(Tri
*** 55,60 ****
--- 57,66 ----
  					MemoryContext per_tuple_context);
  static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
  					  bool row_trigger, HeapTuple oldtup, HeapTuple newtup);
+ static void setup_trigger_quals(ResultRelInfo *ri, EState *estate,
+ 								bool before, int event);
+ static bool test_trig_qual(EState *estate, Relation rel, HeapTuple oldtuple,
+         HeapTuple newtuple, List *qual, int event);
  
  
  /*
*************** CreateTrigger(CreateTrigStmt *stmt, bool
*** 182,187 ****
--- 188,203 ----
  	}
  
  	/*
+ 	 * SQL:2003 doesn't specifically prohibit WHEN clauses for statement-level
+ 	 * triggers, but Oracle doesn't allow them, and it's not clear they'd be
+ 	 * useful anyway. Therefore, disallow them for now.
+ 	 */
+ 	if (!stmt->row && stmt->when != NULL)
+ 		ereport(ERROR,
+ 			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 			 errmsg("WHEN clause cannot be specified for statement-level triggers")));
+ 
+ 	/*
  	 * Generate the trigger's OID now, so that we can use it in the name if
  	 * needed.
  	 */
*************** CreateTrigger(CreateTrigStmt *stmt, bool
*** 315,320 ****
--- 331,338 ----
  	values[Anum_pg_trigger_tgconstrrelid - 1] = ObjectIdGetDatum(constrrelid);
  	values[Anum_pg_trigger_tgdeferrable - 1] = BoolGetDatum(stmt->deferrable);
  	values[Anum_pg_trigger_tginitdeferred - 1] = BoolGetDatum(stmt->initdeferred);
+ 	values[Anum_pg_trigger_tgqual - 1] = DirectFunctionCall1(textin,
+ 								CStringGetDatum(nodeToString(stmt->when)));
  
  	if (stmt->args)
  	{
*************** CreateTrigger(CreateTrigStmt *stmt, bool
*** 442,447 ****
--- 460,473 ----
  		}
  	}
  
+ 	if (stmt->when)
+ 	{
+ 		ChangeVarNodes(stmt->when, TRIG_OLD_VARNO, 1, 0);
+ 		ChangeVarNodes(stmt->when, TRIG_NEW_VARNO, 2, 0);
+ 		recordDependencyOnExpr(&myself, stmt->when, stmt->rtable,
+ 							   DEPENDENCY_NORMAL);
+ 	}
+ 
  	/* Keep lock on target rel until end of xact */
  	heap_close(rel, NoLock);
  
*************** RelationBuildTriggers(Relation relation)
*** 875,880 ****
--- 901,909 ----
  	{
  		Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(htup);
  		Trigger    *build;
+ 		Datum		tmp;
+ 		bool		isnull;
+ 		char	   *when_str;
  
  		if (found >= ntrigs)
  			elog(ERROR, "too many trigger records found for relation \"%s\"",
*************** RelationBuildTriggers(Relation relation)
*** 927,932 ****
--- 956,979 ----
  		else
  			build->tgargs = NULL;
  
+ 		/* get the trigger's WHEN clause, if any */
+ 		tmp = heap_getattr(htup, Anum_pg_trigger_tgqual,
+ 						   RelationGetDescr(tgrel), &isnull);
+ 		Assert(!isnull);
+ 
+ 		when_str = DatumGetCString(DirectFunctionCall1(textout,
+ 													   PointerGetDatum(tmp)));
+ 
+         /*
+          * XXX: we leak the node here because FreeTriggerDesc() has no
+          * ability to do a deep free of a Node.
+          *
+          * Ideally, the Node would be created in its own context which
+          * we could just reset. Since we create the node in the 
+          * CacheMemoryContext the effect of this leak will be long lived
+          */
+ 
+ 		build->when = (Node *) stringToNode(when_str);
  		found++;
  	}
  
*************** CopyTriggerDesc(TriggerDesc *trigdesc)
*** 1074,1079 ****
--- 1121,1127 ----
  				newargs[j] = pstrdup(trigger->tgargs[j]);
  			trigger->tgargs = newargs;
  		}
+ 		trigger->when = copyObject(trigdesc->triggers[i].when);
  		trigger++;
  	}
  
*************** FreeTriggerDesc(TriggerDesc *trigdesc)
*** 1175,1180 ****
--- 1223,1229 ----
  				pfree(trigger->tgargs[trigger->tgnargs]);
  			pfree(trigger->tgargs);
  		}
+ 		/* XXX: leaks trigger->when */
  		trigger++;
  	}
  	pfree(trigdesc->triggers);
*************** equalTriggerDescs(TriggerDesc *trigdesc1
*** 1232,1237 ****
--- 1281,1288 ----
  				return false;
  			if (trig1->tgnattr != trig2->tgnattr)
  				return false;
+ 			if (!equal(trig1->when, trig2->when))
+ 				return false;
  			if (trig1->tgnattr > 0 &&
  				memcmp(trig1->tgattr, trig2->tgattr,
  					   trig1->tgnattr * sizeof(int2)) != 0)
*************** ExecBRInsertTriggers(EState *estate, Res
*** 1389,1399 ****
--- 1440,1454 ----
  	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
  	int			ntrigs = trigdesc->n_before_row[TRIGGER_EVENT_INSERT];
  	int		   *tgindx = trigdesc->tg_before_row[TRIGGER_EVENT_INSERT];
+ 	TrigQualState *qual_state;
  	HeapTuple	newtuple = trigtuple;
  	HeapTuple	oldtuple;
  	TriggerData LocTriggerData;
  	int			i;
  
+ 	setup_trigger_quals(relinfo, estate, true, TRIGGER_EVENT_INSERT);
+ 	qual_state = relinfo->ri_TrigQuals;
+ 
  	LocTriggerData.type = T_TriggerData;
  	LocTriggerData.tg_event = TRIGGER_EVENT_INSERT |
  		TRIGGER_EVENT_ROW |
*************** ExecBRInsertTriggers(EState *estate, Res
*** 1401,1412 ****
--- 1456,1481 ----
  	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 (!trigger->tgenabled)
  			continue;
+ 
+ 		/* Check the trigger's WHEN clause, if any */
+ 		if (trigger->when)
+ 		{
+ 			bool res;
+ 
+ 			res = test_trig_qual(estate, relinfo->ri_RelationDesc, 
+ 				NULL, trigtuple, qual_state->quals[tgindx[i]],
+ 				 TRIGGER_EVENT_INSERT);
+ 			if (!res)
+ 				continue;
+ 		}
+ 
  		LocTriggerData.tg_trigtuple = oldtuple = newtuple;
  		LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
  		LocTriggerData.tg_trigger = trigger;
*************** ExecBRDeleteTriggers(EState *estate, Res
*** 1501,1506 ****
--- 1570,1576 ----
  	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
  	int			ntrigs = trigdesc->n_before_row[TRIGGER_EVENT_DELETE];
  	int		   *tgindx = trigdesc->tg_before_row[TRIGGER_EVENT_DELETE];
+ 	TrigQualState *qual_state;
  	bool		result = true;
  	TriggerData LocTriggerData;
  	HeapTuple	trigtuple;
*************** ExecBRDeleteTriggers(EState *estate, Res
*** 1512,1517 ****
--- 1582,1590 ----
  	if (trigtuple == NULL)
  		return false;
  
+ 	setup_trigger_quals(relinfo, estate, true, TRIGGER_EVENT_DELETE);
+ 	qual_state = relinfo->ri_TrigQuals;
+ 
  	LocTriggerData.type = T_TriggerData;
  	LocTriggerData.tg_event = TRIGGER_EVENT_DELETE |
  		TRIGGER_EVENT_ROW |
*************** ExecBRDeleteTriggers(EState *estate, Res
*** 1525,1530 ****
--- 1598,1615 ----
  
  		if (!trigger->tgenabled)
  			continue;
+ 
+ 		/* Check the trigger's WHEN clause, if any */
+ 		if (trigger->when)
+ 		{
+ 			bool res;
+ 
+ 			res = test_trig_qual(estate, relinfo->ri_RelationDesc, 
+ 				trigtuple, NULL, qual_state->quals[tgindx[i]],
+ 				 TRIGGER_EVENT_DELETE);
+ 			if (!res)
+ 				continue;
+ 		}
  		LocTriggerData.tg_trigtuple = trigtuple;
  		LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
  		LocTriggerData.tg_trigger = trigger;
*************** ExecBRUpdateTriggers(EState *estate, Res
*** 1632,1637 ****
--- 1717,1723 ----
  	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
  	int			ntrigs = trigdesc->n_before_row[TRIGGER_EVENT_UPDATE];
  	int		   *tgindx = trigdesc->tg_before_row[TRIGGER_EVENT_UPDATE];
+ 	TrigQualState *qual_state;
  	TriggerData LocTriggerData;
  	HeapTuple	trigtuple;
  	HeapTuple	oldtuple;
*************** ExecBRUpdateTriggers(EState *estate, Res
*** 1643,1648 ****
--- 1729,1737 ----
  	if (trigtuple == NULL)
  		return NULL;
  
+ 	setup_trigger_quals(relinfo, estate, true, TRIGGER_EVENT_UPDATE);
+ 	qual_state = relinfo->ri_TrigQuals;
+ 
  	/*
  	 * In READ COMMITTED isolation level it's possible that newtuple was
  	 * changed due to concurrent update.
*************** ExecBRUpdateTriggers(EState *estate, Res
*** 1661,1666 ****
--- 1750,1768 ----
  
  		if (!trigger->tgenabled)
  			continue;
+ 
+ 		/* Check the trigger's WHEN clause, if any */
+ 		if (trigger->when)
+ 		{
+ 			bool res;
+ 
+ 			res = test_trig_qual(estate, relinfo->ri_RelationDesc, 
+ 				trigtuple, newtuple, qual_state->quals[tgindx[i]],
+ 				 TRIGGER_EVENT_UPDATE);
+ 			if (!res)
+ 				continue;
+ 		}
+ 
  		LocTriggerData.tg_trigtuple = trigtuple;
  		LocTriggerData.tg_newtuple = oldtuple = newtuple;
  		LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
*************** AfterTriggerSaveEvent(ResultRelInfo *rel
*** 3262,3264 ****
--- 3364,3457 ----
  		afterTriggerAddEvent(new_event);
  	}
  }
+ 
+ /*
+  * There is some inefficiency here. Subsequent calls to setup_trigger_quals()
+  * during the same command will end up iterating through the array of
+  * triggers. Ideally, this redundant work should be avoided.
+  */
+ 
+ static void
+ setup_trigger_quals(ResultRelInfo *ri, EState *estate, bool before, int event)
+ {
+ 	TriggerDesc 		*trigdesc = ri->ri_TrigDesc;
+ 	MemoryContext 		 old_cxt;
+ 	TrigQualState 		*qual_state;
+ 	int 				 i;
+ 	int 				 ntrigs;
+ 	int 				*tgindx;
+ 
+ 	old_cxt = MemoryContextSwitchTo(estate->es_query_cxt);
+ 	if (ri->ri_TrigQuals == NULL)
+ 	{
+ 		ri->ri_TrigQuals = palloc(sizeof(TrigQualState));
+ 		ri->ri_TrigQuals->quals = palloc0(trigdesc->numtriggers * sizeof(List *));
+ 	}
+ 
+ 	qual_state = ri->ri_TrigQuals;
+ 
+ 	if (before)
+ 	{
+ 		ntrigs = trigdesc->n_before_row[event];
+ 		tgindx = trigdesc->tg_before_row[event];
+ 	}
+ 	else
+ 	{
+ 		ntrigs = trigdesc->n_after_row[event];
+ 		tgindx = trigdesc->tg_after_row[event];
+ 	}
+ 
+ 	for (i = 0; i < ntrigs; i++)
+ 	{
+ 		Trigger	*trigger;
+ 		int 	 trig_idx;
+ 		List	*qual;
+ 
+ 		trig_idx = tgindx[i];
+ 		trigger = &trigdesc->triggers[trig_idx];
+ 
+ 		if (!trigger->when)
+ 			continue;
+ 
+ 		if (qual_state->quals[trig_idx])
+ 			continue;
+ 
+ 		qual = make_ands_implicit((Expr *) trigger->when);
+ 		qual_state->quals[trig_idx] = (List *) ExecPrepareExpr((Expr *) qual,
+ 															   estate);
+ 	}
+ 
+ 	MemoryContextSwitchTo(old_cxt);
+ }
+ 
+ /*
+  * Test if a trigger qualification evaluates true for the
+  * input tuple(s)
+  */
+ static bool
+ test_trig_qual(EState *estate, Relation rel, HeapTuple oldtuple, 
+ 			   HeapTuple newtuple, List *qual, int event)
+ {
+ 	ExprContext *econtext = GetPerTupleExprContext(estate);
+ 	TupleDesc tupdesc = RelationGetDescr(rel);
+ 
+ 	if (event == TRIGGER_EVENT_INSERT || 
+ 		event == TRIGGER_EVENT_UPDATE)
+ 	{ 
+ 		if (econtext->ecxt_newtuple == NULL)
+ 			econtext->ecxt_newtuple = MakeSingleTupleTableSlot(tupdesc);
+ 		ExecClearTuple(econtext->ecxt_newtuple);
+ 		ExecStoreTuple(newtuple, econtext->ecxt_newtuple,
+ 				   InvalidBuffer, false);
+ 	}
+ 	if (event == TRIGGER_EVENT_UPDATE ||
+ 		event == TRIGGER_EVENT_DELETE)
+ 	{
+ 		if (econtext->ecxt_oldtuple == NULL)
+ 			econtext->ecxt_oldtuple = MakeSingleTupleTableSlot(tupdesc);
+ 		ExecClearTuple(econtext->ecxt_oldtuple);
+ 		ExecStoreTuple(oldtuple, econtext->ecxt_oldtuple, InvalidBuffer, false);
+ 	}
+ 
+ 	return ExecQual(qual, econtext, false);
+ }
Index: src/backend/executor/execMain.c
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/backend/executor/execMain.c,v
retrieving revision 1.273
diff -c -p -r1.273 execMain.c
*** src/backend/executor/execMain.c	3 Jul 2006 22:45:38 -0000	1.273
--- src/backend/executor/execMain.c	7 Jul 2006 18:55:35 -0000
*************** initResultRelInfo(ResultRelInfo *resultR
*** 914,919 ****
--- 914,920 ----
  		resultRelInfo->ri_TrigFunctions = NULL;
  		resultRelInfo->ri_TrigInstrument = NULL;
  	}
+ 	resultRelInfo->ri_TrigQuals = NULL; 
  	resultRelInfo->ri_ConstraintExprs = NULL;
  	resultRelInfo->ri_junkFilter = NULL;
  
Index: src/backend/executor/execQual.c
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/backend/executor/execQual.c,v
retrieving revision 1.191
diff -c -p -r1.191 execQual.c
*** src/backend/executor/execQual.c	16 Jun 2006 18:42:21 -0000	1.191
--- src/backend/executor/execQual.c	7 Jul 2006 19:32:34 -0000
*************** ExecEvalVar(ExprState *exprstate, ExprCo
*** 459,464 ****
--- 459,472 ----
  			Assert(attnum > 0);
  			break;
  
+ 		case TRIG_OLD_VARNO:	/* old tuple in trigger context */
+ 			slot = econtext->ecxt_oldtuple;
+ 			break;
+ 
+ 		case TRIG_NEW_VARNO:	/* new tuple in trigger context */
+ 			slot = econtext->ecxt_newtuple;
+ 			break;
+ 
  		default:				/* get the tuple from the relation being
  								 * scanned */
  			slot = econtext->ecxt_scantuple;
Index: src/backend/executor/execUtils.c
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/backend/executor/execUtils.c,v
retrieving revision 1.135
diff -c -p -r1.135 execUtils.c
*** src/backend/executor/execUtils.c	16 Jun 2006 18:42:22 -0000	1.135
--- src/backend/executor/execUtils.c	7 Jul 2006 13:04:01 -0000
*************** CreateExprContext(EState *estate)
*** 296,301 ****
--- 296,303 ----
  	econtext->ecxt_scantuple = NULL;
  	econtext->ecxt_innertuple = NULL;
  	econtext->ecxt_outertuple = NULL;
+ 	econtext->ecxt_oldtuple = NULL;
+ 	econtext->ecxt_newtuple = NULL;
  
  	econtext->ecxt_per_query_memory = estate->es_query_cxt;
  
*************** FreeExprContext(ExprContext *econtext)
*** 356,361 ****
--- 358,371 ----
  
  	/* Call any registered callbacks */
  	ShutdownExprContext(econtext);
+ 
+ 	/* Clean up special slots for NEW and OLD trigger tuples */
+ 	if (econtext->ecxt_newtuple)
+ 		ExecDropSingleTupleTableSlot(econtext->ecxt_newtuple);
+ 
+ 	if (econtext->ecxt_oldtuple)
+ 		ExecDropSingleTupleTableSlot(econtext->ecxt_oldtuple);
+ 
  	/* And clean up the memory used */
  	MemoryContextDelete(econtext->ecxt_per_tuple_memory);
  	/* Unlink self from owning EState */
Index: src/backend/nodes/copyfuncs.c
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v
retrieving revision 1.342
diff -c -p -r1.342 copyfuncs.c
*** src/backend/nodes/copyfuncs.c	3 Jul 2006 22:45:38 -0000	1.342
--- src/backend/nodes/copyfuncs.c	7 Jul 2006 13:04:02 -0000
*************** _copyCreateTrigStmt(CreateTrigStmt *from
*** 2441,2446 ****
--- 2441,2448 ----
  	COPY_SCALAR_FIELD(deferrable);
  	COPY_SCALAR_FIELD(initdeferred);
  	COPY_NODE_FIELD(constrrel);
+ 	COPY_NODE_FIELD(when);
+ 	COPY_NODE_FIELD(rtable);
  
  	return newnode;
  }
Index: src/backend/nodes/equalfuncs.c
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v
retrieving revision 1.276
diff -c -p -r1.276 equalfuncs.c
*** src/backend/nodes/equalfuncs.c	3 Jul 2006 22:45:38 -0000	1.276
--- src/backend/nodes/equalfuncs.c	7 Jul 2006 13:04:03 -0000
*************** _equalCreateTrigStmt(CreateTrigStmt *a, 
*** 1306,1311 ****
--- 1306,1313 ----
  	COMPARE_SCALAR_FIELD(deferrable);
  	COMPARE_SCALAR_FIELD(initdeferred);
  	COMPARE_NODE_FIELD(constrrel);
+ 	COMPARE_NODE_FIELD(when);
+ 	COMPARE_NODE_FIELD(rtable);
  
  	return true;
  }
Index: src/backend/parser/analyze.c
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/backend/parser/analyze.c,v
retrieving revision 1.338
diff -c -p -r1.338 analyze.c
*** src/backend/parser/analyze.c	3 Jul 2006 22:45:39 -0000	1.338
--- src/backend/parser/analyze.c	7 Jul 2006 19:45:06 -0000
*************** static Query *transformInsertStmt(ParseS
*** 106,111 ****
--- 106,112 ----
  static Query *transformIndexStmt(ParseState *pstate, IndexStmt *stmt);
  static Query *transformRuleStmt(ParseState *query, RuleStmt *stmt,
  				  List **extras_before, List **extras_after);
+ static Query *transformCreateTrigStmt(ParseState *pstate, CreateTrigStmt *stmt);
  static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt);
  static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt);
  static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt);
*************** transformStmt(ParseState *pstate, Node *
*** 324,329 ****
--- 325,335 ----
  									   extras_before, extras_after);
  			break;
  
+ 		case T_CreateTrigStmt:
+ 			result = transformCreateTrigStmt(pstate,
+ 											 (CreateTrigStmt *) parseTree);
+ 			break;
+ 
  		case T_ViewStmt:
  			result = transformViewStmt(pstate, (ViewStmt *) parseTree,
  									   extras_before, extras_after);
*************** transformRuleStmt(ParseState *pstate, Ru
*** 1852,1857 ****
--- 1858,1965 ----
  	return qry;
  }
  
+ /*
+  * transformCreateTrigStmt -
+  *	  transform a CREATE TRIGGER statement. Most of the work we do is
+  *	  transforming the statement's WHEN clause, if any.
+  */
+ static Query *
+ transformCreateTrigStmt(ParseState *pstate, CreateTrigStmt *stmt)
+ {
+ 	Query	   *qry;
+ 	Relation	rel;
+ 	RangeTblEntry *oldrte;
+ 	RangeTblEntry *newrte;
+ 	int			i;
+ 
+ 	qry = makeNode(Query);
+ 	qry->commandType = CMD_UTILITY;
+ 	qry->utilityStmt = (Node *) stmt;
+ 
+ 	/* If there's no WHEN clause, we're done. */
+ 	if (!stmt->when)
+ 		return qry;
+ 
+ 	/*
+ 	 * Note that we acquire and keep an exclusive lock on the target table
+ 	 * only if there's a WHEN clause; if there's no WHEN, we acquire the same
+ 	 * lock in CreateTrigger(), so the effect should be the same.
+ 	 */
+ 	rel = heap_openrv(stmt->relation, AccessExclusiveLock);
+ 
+ 	/*
+ 	 * Setup RTEs for the NEW and OLD relations in the main pstate, for use in
+ 	 * parsing the trigger qualification. We initially add "OLD" with RT index
+ 	 * 1, and "NEW" with RT index 2, and then change them to use the correct
+ 	 * varnos below.
+ 	 */
+ 	Assert(pstate->p_rtable == NIL);
+ 	oldrte = addRangeTableEntryForRelation(pstate, rel,
+ 										   makeAlias("*OLD*", NIL),
+ 										   false, false);
+ 	newrte = addRangeTableEntryForRelation(pstate, rel,
+ 										   makeAlias("*NEW*", NIL),
+ 										   false, false);
+ 
+ 	for (i = 0; stmt->actions[i] != '\0'; i++)
+ 	{
+ 		if (stmt->actions[i] == 'd' || stmt->actions[i] == 'u')
+ 		{
+ 			addRTEtoQuery(pstate, oldrte, false, true, true);
+ 			break;
+ 		}
+ 	}
+ 
+ 	for (i = 0; stmt->actions[i] != '\0'; i++)
+ 	{
+ 		if (stmt->actions[i] == 'i' || stmt->actions[i] == 'u')
+ 		{
+ 			addRTEtoQuery(pstate, newrte, false, true, true);
+ 			break;
+ 		}
+ 	}
+ 
+ 	/* process WHEN clause as though it was a WHERE clause */
+ 	stmt->when = transformWhereClause(pstate, stmt->when, "WHEN");
+ 
+ 	if (list_length(pstate->p_rtable) != 2)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ 				 errmsg("trigger WHEN condition may not contain "
+ 						"references to other relations")));
+ 
+ 	stmt->rtable = list_copy(pstate->p_rtable);
+ 
+ 	/* aggregates not allowed */
+ 	if (pstate->p_hasAggs)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_GROUPING_ERROR),
+ 		errmsg("trigger WHEN condition may not contain aggregate functions")));
+ 
+ 	/* subselects are not allowed either, at least for now */
+ 	if (pstate->p_hasSubLinks)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ 				 errmsg("trigger WHEN condition may not contain subqueries")));
+ 
+ 	/*
+ 	 * Rewrite the WHEN expression to give the right varno to the NEW and OLD
+ 	 * relations, so that these relations can be treated specially by the
+ 	 * executor.
+ 	 *
+ 	 * We could avoid doing this by having the executor code do it for us when
+ 	 * we initialise the expressions out of pg_trigger. This seems like a 
+ 	 * better place to do it, especially if you consider very complex
+ 	 * expressions.
+ 	 */
+ 	ChangeVarNodes(stmt->when, 1, TRIG_OLD_VARNO, 0);
+ 	ChangeVarNodes(stmt->when, 2, TRIG_NEW_VARNO, 0);
+ 
+ 	/* Close relation, but keep the exclusive lock */
+ 	heap_close(rel, NoLock);
+ 
+ 	return qry;
+ }
  
  /*
   * transformSelectStmt -
Index: src/backend/parser/gram.y
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/backend/parser/gram.y,v
retrieving revision 2.551
diff -c -p -r2.551 gram.y
*** src/backend/parser/gram.y	3 Jul 2006 22:45:39 -0000	2.551
--- src/backend/parser/gram.y	7 Jul 2006 20:20:40 -0000
***************
*** 79,85 ****
  
  extern List *parsetree;			/* final parse result is delivered here */
  
! static bool QueryIsRule = FALSE;
  
  /*
   * If you need access to certain yacc-generated variables and find that
--- 79,85 ----
  
  extern List *parsetree;			/* final parse result is delivered here */
  
! static bool QueryIsRuleOrTrigger = FALSE;
  
  /*
   * If you need access to certain yacc-generated variables and find that
*************** static void doNegateFloat(Value *v);
*** 169,175 ****
  		UnlistenStmt UpdateStmt VacuumStmt
  		VariableResetStmt VariableSetStmt VariableShowStmt
  		ViewStmt CheckPointStmt CreateConversionStmt
! 		DeallocateStmt PrepareStmt ExecuteStmt
  		DropOwnedStmt ReassignOwnedStmt
  
  %type <node>	select_no_parens select_with_parens select_clause
--- 169,175 ----
  		UnlistenStmt UpdateStmt VacuumStmt
  		VariableResetStmt VariableSetStmt VariableShowStmt
  		ViewStmt CheckPointStmt CreateConversionStmt
! 		DeallocateStmt PrepareStmt ExecuteStmt TriggerWhen
  		DropOwnedStmt ReassignOwnedStmt
  
  %type <node>	select_no_parens select_with_parens select_clause
*************** DropTableSpaceStmt: DROP TABLESPACE name
*** 2519,2540 ****
   *****************************************************************************/
  
  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;
! 					memcpy(n->actions, $5, 4);
  					n->isconstraint  = FALSE;
  					n->deferrable	 = FALSE;
  					n->initdeferred  = FALSE;
  					n->constrrel = NULL;
  					$$ = (Node *)n;
  				}
  			| CREATE CONSTRAINT TRIGGER name AFTER TriggerEvents ON
--- 2519,2545 ----
   *****************************************************************************/
  
  CreateTrigStmt:
! 			CREATE TRIGGER name 
! 			{ QueryIsRuleOrTrigger=TRUE; }
! 			TriggerActionTime TriggerEvents ON
! 			qualified_name TriggerForSpec TriggerWhen EXECUTE PROCEDURE
  			func_name '(' TriggerFuncArgs ')'
  				{
  					CreateTrigStmt *n = makeNode(CreateTrigStmt);
  					n->trigname = $3;
! 					n->relation = $8;
! 					n->funcname = $13;
! 					n->args = $15;
! 					n->before = $5;
! 					n->row = $9;
! 					memcpy(n->actions, $6, 4);
  					n->isconstraint  = FALSE;
  					n->deferrable	 = FALSE;
  					n->initdeferred  = FALSE;
  					n->constrrel = NULL;
+ 					n->when = $10;
+ 					n->rtable = NIL;
+ 					QueryIsRuleOrTrigger = FALSE;
  					$$ = (Node *)n;
  				}
  			| CREATE CONSTRAINT TRIGGER name AFTER TriggerEvents ON
*************** CreateTrigStmt:
*** 2554,2560 ****
  					n->isconstraint  = TRUE;
  					n->deferrable = ($10 & 1) != 0;
  					n->initdeferred = ($10 & 2) != 0;
! 
  					n->constrrel = $9;
  					$$ = (Node *)n;
  				}
--- 2559,2566 ----
  					n->isconstraint  = TRUE;
  					n->deferrable = ($10 & 1) != 0;
  					n->initdeferred = ($10 & 2) != 0;
! 					n->when = NULL;
! 					n->rtable = NIL;
  					n->constrrel = $9;
  					$$ = (Node *)n;
  				}
*************** TriggerForType:
*** 2617,2622 ****
--- 2623,2633 ----
  			| STATEMENT								{ $$ = FALSE; }
  		;
  
+ TriggerWhen:
+ 			WHEN '(' a_expr ')' 					{ $$ = $3; }
+ 			| /*EMPTY*/								{ $$ = NULL; }
+ 		;
+ 
  TriggerFuncArgs:
  			TriggerFuncArg							{ $$ = list_make1($1); }
  			| TriggerFuncArgs ',' TriggerFuncArg	{ $$ = lappend($1, $3); }
*************** AlterOwnerStmt: ALTER AGGREGATE func_nam
*** 4480,4486 ****
   *****************************************************************************/
  
  RuleStmt:	CREATE opt_or_replace RULE name AS
! 			{ QueryIsRule=TRUE; }
  			ON event TO qualified_name where_clause
  			DO opt_instead RuleActionList
  				{
--- 4491,4497 ----
   *****************************************************************************/
  
  RuleStmt:	CREATE opt_or_replace RULE name AS
! 			{ QueryIsRuleOrTrigger=TRUE; }
  			ON event TO qualified_name where_clause
  			DO opt_instead RuleActionList
  				{
*************** RuleStmt:	CREATE opt_or_replace RULE nam
*** 4493,4499 ****
  					n->instead = $13;
  					n->actions = $14;
  					$$ = (Node *)n;
! 					QueryIsRule=FALSE;
  				}
  		;
  
--- 4504,4510 ----
  					n->instead = $13;
  					n->actions = $14;
  					$$ = (Node *)n;
! 					QueryIsRuleOrTrigger=FALSE;
  				}
  		;
  
*************** reserved_keyword:
*** 8851,8857 ****
  SpecialRuleRelation:
  			OLD
  				{
! 					if (QueryIsRule)
  						$$ = "*OLD*";
  					else
  						ereport(ERROR,
--- 8862,8868 ----
  SpecialRuleRelation:
  			OLD
  				{
! 					if (QueryIsRuleOrTrigger)
  						$$ = "*OLD*";
  					else
  						ereport(ERROR,
*************** SpecialRuleRelation:
*** 8860,8866 ****
  				}
  			| NEW
  				{
! 					if (QueryIsRule)
  						$$ = "*NEW*";
  					else
  						ereport(ERROR,
--- 8871,8877 ----
  				}
  			| NEW
  				{
! 					if (QueryIsRuleOrTrigger)
  						$$ = "*NEW*";
  					else
  						ereport(ERROR,
*************** SystemTypeName(char *name)
*** 9238,9244 ****
  void
  parser_init(void)
  {
! 	QueryIsRule = FALSE;
  }
  
  /* exprIsNullConstant()
--- 9249,9255 ----
  void
  parser_init(void)
  {
! 	QueryIsRuleOrTrigger = FALSE;
  }
  
  /* exprIsNullConstant()
Index: src/backend/utils/adt/ruleutils.c
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v
retrieving revision 1.227
diff -c -p -r1.227 ruleutils.c
*** src/backend/utils/adt/ruleutils.c	4 Jul 2006 04:35:49 -0000	1.227
--- src/backend/utils/adt/ruleutils.c	7 Jul 2006 13:04:13 -0000
***************
*** 38,43 ****
--- 38,44 ----
  #include "parser/parse_oper.h"
  #include "parser/parse_type.h"
  #include "parser/parsetree.h"
+ #include "parser/parse_relation.h"
  #include "rewrite/rewriteHandler.h"
  #include "rewrite/rewriteManip.h"
  #include "rewrite/rewriteSupport.h"
*************** pg_get_triggerdef(PG_FUNCTION_ARGS)
*** 435,440 ****
--- 436,445 ----
  	SysScanDesc tgscan;
  	int			findx = 0;
  	char	   *tgname;
+ 	Datum		when_text;
+ 	char	   *when_str;
+ 	Node	   *node;
+ 	bool		isnull;
  
  	/*
  	 * Fetch the pg_trigger tuple by the Oid of the trigger
*************** pg_get_triggerdef(PG_FUNCTION_ARGS)
*** 514,519 ****
--- 519,570 ----
  	else
  		appendStringInfo(&buf, "FOR EACH STATEMENT ");
  
+ 	/* handle WHEN clause */
+ 	when_text = heap_getattr(ht_trig, Anum_pg_trigger_tgqual,
+ 							 RelationGetDescr(tgrel), &isnull);
+ 	Assert(!isnull);
+ 
+ 	when_str = DatumGetCString(DirectFunctionCall1(textout, when_text));
+ 	node = (Node *) stringToNode(when_str);
+ 
+ 	if (node)
+ 	{
+ 		Relation rel;
+ 		RangeTblEntry *oldrte;
+ 		RangeTblEntry *newrte;
+ 		deparse_context context;
+ 		deparse_namespace dpns;
+ 
+ 		rel = heap_open(trigrec->tgrelid, AccessShareLock);
+ 
+ 		appendStringInfo(&buf, "WHEN ");
+ 
+ 		oldrte = addRangeTableEntryForRelation(NULL, rel,
+ 											   makeAlias("*OLD*", NIL),
+ 											   false, false);
+ 
+ 		newrte = addRangeTableEntryForRelation(NULL, rel,
+ 											   makeAlias("*NEW*", NIL),
+ 											   false, false);
+ 
+ 		ChangeVarNodes(node, TRIG_OLD_VARNO, 1, 0);
+ 		ChangeVarNodes(node, TRIG_NEW_VARNO, 2, 0);
+ 
+ 		context.buf = &buf;
+ 		context.namespaces = list_make1(&dpns);
+ 		context.varprefix = true; 
+ 		context.prettyFlags = 0;
+ 		context.indentLevel = PRETTYINDENT_STD;
+ 
+ 		dpns.rtable = list_make2(oldrte, newrte);
+ 		dpns.outer_varno = dpns.inner_varno = 0;
+ 		dpns.outer_rte = dpns.inner_rte = NULL;
+ 
+ 		get_rule_expr(node, &context, false);
+ 		appendStringInfo(&buf, " ");
+ 		heap_close(rel, AccessShareLock);
+ 	}
+ 
  	appendStringInfo(&buf, "EXECUTE PROCEDURE %s(",
  					 generate_function_name(trigrec->tgfoid, 0, NULL));
  
Index: src/bin/pg_dump/pg_dump.c
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/bin/pg_dump/pg_dump.c,v
retrieving revision 1.439
diff -c -p -r1.439 pg_dump.c
*** src/bin/pg_dump/pg_dump.c	2 Jul 2006 02:23:21 -0000	1.439
--- src/bin/pg_dump/pg_dump.c	7 Jul 2006 13:04:21 -0000
*************** dumpSequence(Archive *fout, TableInfo *t
*** 8048,8077 ****
  }
  
  static void
! dumpTrigger(Archive *fout, TriggerInfo *tginfo)
  {
  	TableInfo  *tbinfo = tginfo->tgtable;
- 	PQExpBuffer query;
- 	PQExpBuffer delqry;
  	const char *p;
  	int			findx;
  
- 	if (dataOnly)
- 		return;
- 
- 	query = createPQExpBuffer();
- 	delqry = createPQExpBuffer();
- 
- 	/*
- 	 * DROP must be fully qualified in case same name appears in pg_catalog
- 	 */
- 	appendPQExpBuffer(delqry, "DROP TRIGGER %s ",
- 					  fmtId(tginfo->dobj.name));
- 	appendPQExpBuffer(delqry, "ON %s.",
- 					  fmtId(tbinfo->dobj.namespace->dobj.name));
- 	appendPQExpBuffer(delqry, "%s;\n",
- 					  fmtId(tbinfo->dobj.name));
- 
  	if (tginfo->tgisconstraint)
  	{
  		appendPQExpBuffer(query, "CREATE CONSTRAINT TRIGGER ");
--- 8048,8059 ----
  }
  
  static void
! dumpTriggerManually(Archive *fout, TriggerInfo *tginfo, PQExpBuffer query)
  {
  	TableInfo  *tbinfo = tginfo->tgtable;
  	const char *p;
  	int			findx;
  
  	if (tginfo->tgisconstraint)
  	{
  		appendPQExpBuffer(query, "CREATE CONSTRAINT TRIGGER ");
*************** dumpTrigger(Archive *fout, TriggerInfo *
*** 8194,8199 ****
--- 8176,8234 ----
  		p = p + 4;
  	}
  	appendPQExpBuffer(query, ");\n");
+ }
+ 
+ static void
+ dumpTrigger(Archive *fout, TriggerInfo *tginfo)
+ {
+ 	TableInfo  *tbinfo = tginfo->tgtable;
+ 	PQExpBuffer query;
+ 	PQExpBuffer delqry;
+ 
+ 	if (dataOnly)
+ 		return;
+ 
+ 	query = createPQExpBuffer();
+ 	delqry = createPQExpBuffer();
+ 
+ 	/*
+ 	 * DROP must be fully qualified in case same name appears in pg_catalog
+ 	 */
+ 	appendPQExpBuffer(delqry, "DROP TRIGGER %s ",
+ 					  fmtId(tginfo->dobj.name));
+ 	appendPQExpBuffer(delqry, "ON %s.",
+ 					  fmtId(tbinfo->dobj.namespace->dobj.name));
+ 	appendPQExpBuffer(delqry, "%s;\n",
+ 					  fmtId(tbinfo->dobj.name));
+ 
+ 	/*
+ 	 * If pg_get_triggerdef() is available, use it. Otherwise, reconstruct the
+ 	 * CREATE TRIGGER command manually.
+ 	 */
+ 	if (g_fout->remoteVersion >= 70300)
+ 	{
+ 		PQExpBuffer 	 def_query;
+ 		PGresult 		*res;
+ 
+ 		def_query = createPQExpBuffer();
+ 		appendPQExpBuffer(def_query,
+ 						  "SELECT pg_catalog.pg_get_triggerdef('%u'::pg_catalog.oid) AS definition",
+ 						  tginfo->dobj.catId.oid);
+ 		res = PQexec(g_conn, def_query->data);
+ 		check_sql_result(res, g_conn, def_query->data, PGRES_TUPLES_OK);
+ 
+ 		if (PQntuples(res) != 1)
+ 		{
+ 			write_msg(NULL, "query to get trigger \"%s\" for table \"%s\" failed: wrong number of rows returned",
+ 					  tginfo->dobj.name, tbinfo->dobj.name);
+ 			exit_nicely();
+ 		}
+ 
+ 		appendPQExpBuffer(query, "%s;\n", PQgetvalue(res, 0, 0));
+ 		destroyPQExpBuffer(def_query);
+ 	}
+ 	else
+ 		dumpTriggerManually(fout, tginfo, query);
  
  	if (!tginfo->tgenabled)
  	{
Index: src/include/catalog/pg_trigger.h
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/include/catalog/pg_trigger.h,v
retrieving revision 1.25
diff -c -p -r1.25 pg_trigger.h
*** src/include/catalog/pg_trigger.h	11 Mar 2006 04:38:38 -0000	1.25
--- src/include/catalog/pg_trigger.h	5 Jul 2006 17:37:22 -0000
*************** CATALOG(pg_trigger,2620)
*** 48,53 ****
--- 48,54 ----
  	/* VARIABLE LENGTH FIELDS: */
  	int2vector	tgattr;			/* reserved for column-specific triggers */
  	bytea		tgargs;			/* first\000second\000tgnargs\000 */
+ 	text		tgqual;			/* string form of qualification clause */
  } FormData_pg_trigger;
  
  /* ----------------
*************** typedef FormData_pg_trigger *Form_pg_tri
*** 61,67 ****
   *		compiler constants for pg_trigger
   * ----------------
   */
! #define Natts_pg_trigger				13
  #define Anum_pg_trigger_tgrelid			1
  #define Anum_pg_trigger_tgname			2
  #define Anum_pg_trigger_tgfoid			3
--- 62,68 ----
   *		compiler constants for pg_trigger
   * ----------------
   */
! #define Natts_pg_trigger				14
  #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
*** 75,80 ****
--- 76,82 ----
  #define Anum_pg_trigger_tgnargs			11
  #define Anum_pg_trigger_tgattr			12
  #define Anum_pg_trigger_tgargs			13
+ #define Anum_pg_trigger_tgqual			14
  
  #define TRIGGER_TYPE_ROW				(1 << 0)
  #define TRIGGER_TYPE_BEFORE				(1 << 1)
Index: src/include/nodes/execnodes.h
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/include/nodes/execnodes.h,v
retrieving revision 1.152
diff -c -p -r1.152 execnodes.h
*** src/include/nodes/execnodes.h	28 Jun 2006 19:40:52 -0000	1.152
--- src/include/nodes/execnodes.h	7 Jul 2006 19:03:02 -0000
*************** typedef struct ExprContext
*** 100,105 ****
--- 100,107 ----
  	TupleTableSlot *ecxt_scantuple;
  	TupleTableSlot *ecxt_innertuple;
  	TupleTableSlot *ecxt_outertuple;
+ 	TupleTableSlot *ecxt_oldtuple; /* OLD tuple in trigger context */
+ 	TupleTableSlot *ecxt_newtuple; /* NEW tuple in trigger context */
  
  	/* Memory contexts for expression evaluation --- see notes above */
  	MemoryContext ecxt_per_query_memory;
*************** typedef struct JunkFilter
*** 247,252 ****
--- 249,269 ----
  	TupleTableSlot *jf_resultSlot;
  } JunkFilter;
  
+ /*
+  * State information for trigger WHEN clauses. Since ExecPrepareExpr() could
+  * be expensive, we want to initialise each expression once and
+  * reuse it again and again
+  *
+  * "quals" is an array of lists, with one array element for each trigger (in
+  * the same order as the array of Triggers kept in the TriggerDesc). Each
+  * List represents an Expr, in "ANDs implicit" format.
+  */
+ typedef struct TrigQualState
+ {
+ 	List 				**quals;
+ } TrigQualState;
+ 
+ 
  /* ----------------
   *	  ResultRelInfo information
   *
*************** typedef struct JunkFilter
*** 262,267 ****
--- 279,285 ----
   *		IndexRelationInfo		array of key/attr info for indices
   *		TrigDesc				triggers to be fired, if any
   *		TrigFunctions			cached lookup info for trigger functions
+  *		TrigQuals				state for trigger WHEN clauses
   *		TrigInstrument			optional runtime measurements for triggers
   *		ConstraintExprs			array of constraint-checking expr states
   *		junkFilter				for removing junk attributes from tuples
*************** typedef struct ResultRelInfo
*** 277,282 ****
--- 295,301 ----
  	IndexInfo **ri_IndexRelationInfo;
  	TriggerDesc *ri_TrigDesc;
  	FmgrInfo   *ri_TrigFunctions;
+ 	TrigQualState	  *ri_TrigQuals;
  	struct Instrumentation *ri_TrigInstrument;
  	List	  **ri_ConstraintExprs;
  	JunkFilter *ri_junkFilter;
Index: src/include/nodes/parsenodes.h
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/include/nodes/parsenodes.h,v
retrieving revision 1.315
diff -c -p -r1.315 parsenodes.h
*** src/include/nodes/parsenodes.h	3 Jul 2006 22:45:40 -0000	1.315
--- src/include/nodes/parsenodes.h	7 Jul 2006 13:04:27 -0000
*************** typedef struct CreateTrigStmt
*** 1162,1167 ****
--- 1162,1169 ----
  	bool		deferrable;		/* [NOT] DEFERRABLE */
  	bool		initdeferred;	/* INITIALLY {DEFERRED|IMMEDIATE} */
  	RangeVar   *constrrel;		/* opposite relation */
+ 	Node	   *when;			/* WHEN clause qual */
+ 	List	   *rtable;			/* range table for interpreting WHEN expr */
  } CreateTrigStmt;
  
  /* ----------------------
Index: src/include/nodes/primnodes.h
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/include/nodes/primnodes.h,v
retrieving revision 1.113
diff -c -p -r1.113 primnodes.h
*** src/include/nodes/primnodes.h	22 Apr 2006 01:26:01 -0000	1.113
--- src/include/nodes/primnodes.h	7 Jul 2006 19:05:51 -0000
*************** typedef struct Expr
*** 101,108 ****
   * The code doesn't really need varnoold/varoattno, but they are very useful
   * for debugging and interpreting completed plans, so we keep them around.
   */
! #define    INNER		65000
! #define    OUTER		65001
  
  #define    PRS2_OLD_VARNO			1
  #define    PRS2_NEW_VARNO			2
--- 101,110 ----
   * The code doesn't really need varnoold/varoattno, but they are very useful
   * for debugging and interpreting completed plans, so we keep them around.
   */
! #define    INNER					65000
! #define    OUTER					65001
! #define    TRIG_OLD_VARNO			65002
! #define    TRIG_NEW_VARNO			65003
  
  #define    PRS2_OLD_VARNO			1
  #define    PRS2_NEW_VARNO			2
Index: src/include/utils/rel.h
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/include/utils/rel.h,v
retrieving revision 1.91
diff -c -p -r1.91 rel.h
*** src/include/utils/rel.h	3 Jul 2006 22:45:41 -0000	1.91
--- src/include/utils/rel.h	7 Jul 2006 13:04:30 -0000
*************** typedef struct Trigger
*** 62,67 ****
--- 62,68 ----
  	int16		tgnattr;
  	int16	   *tgattr;
  	char	  **tgargs;
+ 	Node	   *when;
  } Trigger;
  
  typedef struct TriggerDesc
Index: src/test/regress/expected/triggers.out
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/test/regress/expected/triggers.out,v
retrieving revision 1.23
diff -c -p -r1.23 triggers.out
*** src/test/regress/expected/triggers.out	26 Jun 2006 17:24:41 -0000	1.23
--- src/test/regress/expected/triggers.out	7 Jul 2006 20:48:15 -0000
*************** NOTICE:  row 1 not changed
*** 537,539 ****
--- 537,562 ----
  NOTICE:  row 2 not changed
  DROP TABLE trigger_test;
  DROP FUNCTION mytrigger();
+ -- test the WHEN clause for trigger definitions
+ CREATE OR REPLACE FUNCTION notify_trig() RETURNS trigger LANGUAGE plpgsql AS $$
+ begin
+     raise notice '%s invoked: new.a = %, new.b = %', tg_name, new.a, new.b;
+     return new;
+ end$$;
+ CREATE TABLE when_test (a int, b int);
+ CREATE TRIGGER t1 BEFORE INSERT ON when_test FOR EACH ROW
+ WHEN (new.a > 5)
+ EXECUTE PROCEDURE notify_trig();
+ INSERT INTO when_test VALUES (NULL, 0);    -- shouldn't fire
+ INSERT INTO when_test VALUES (10, 100);    -- should fire
+ NOTICE:  t1s invoked: new.a = 10, new.b = 100
+ CREATE TRIGGER t2 BEFORE UPDATE ON when_test FOR EACH ROW
+ WHEN (new.b > 50)
+ EXECUTE PROCEDURE notify_trig();
+ UPDATE when_test SET b = b + 50;     -- should fire once
+ NOTICE:  t2s invoked: new.a = 10, new.b = 150
+ UPDATE when_test SET b = b + 1;     -- should fire twice
+ NOTICE:  t2s invoked: new.a = <NULL>, new.b = 51
+ NOTICE:  t2s invoked: new.a = 10, new.b = 151
+ DROP TABLE when_test;
+ DROP FUNCTION notify_trig();
Index: src/test/regress/sql/triggers.sql
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/test/regress/sql/triggers.sql,v
retrieving revision 1.13
diff -c -p -r1.13 triggers.sql
*** src/test/regress/sql/triggers.sql	26 Jun 2006 17:24:41 -0000	1.13
--- src/test/regress/sql/triggers.sql	7 Jul 2006 13:15:22 -0000
*************** UPDATE trigger_test SET f3 = NULL;
*** 415,417 ****
--- 415,444 ----
  DROP TABLE trigger_test;
  
  DROP FUNCTION mytrigger();
+ 
+ -- test the WHEN clause for trigger definitions
+ CREATE OR REPLACE FUNCTION notify_trig() RETURNS trigger LANGUAGE plpgsql AS $$
+ begin
+     raise notice '%s invoked: new.a = %, new.b = %', tg_name, new.a, new.b;
+     return new;
+ end$$;
+ 
+ CREATE TABLE when_test (a int, b int);
+ 
+ CREATE TRIGGER t1 BEFORE INSERT ON when_test FOR EACH ROW
+ WHEN (new.a > 5)
+ EXECUTE PROCEDURE notify_trig();
+ 
+ INSERT INTO when_test VALUES (NULL, 0);    -- shouldn't fire
+ INSERT INTO when_test VALUES (10, 100);    -- should fire
+ 
+ CREATE TRIGGER t2 BEFORE UPDATE ON when_test FOR EACH ROW
+ WHEN (new.b > 50)
+ EXECUTE PROCEDURE notify_trig();
+ 
+ UPDATE when_test SET b = b + 50;     -- should fire once
+ UPDATE when_test SET b = b + 1;     -- should fire twice
+ 
+ DROP TABLE when_test;
+ DROP FUNCTION notify_trig();
+ 
