diff --git a/doc/src/sgml/perform.sgml b/doc/src/sgml/perform.sgml
index 2316df99a5cc411e91a67c3e0ea29cd4535af0de..ff93eaf8315bc891ed04ccf46b15b57ed9f37260 100644
--- a/doc/src/sgml/perform.sgml
+++ b/doc/src/sgml/perform.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/perform.sgml,v 1.50 2005/02/03 07:12:37 neilc Exp $
+$PostgreSQL: pgsql/doc/src/sgml/perform.sgml,v 1.51 2005/03/25 21:57:57 tgl Exp $
 -->
 
  <chapter id="performance-tips">
@@ -320,16 +320,19 @@ EXPLAIN ANALYZE SELECT * FROM tenk1 t1, tenk2 t2 WHERE t1.unique1 &lt; 50 AND t1
    </para>
 
    <para>
-    The <literal>Total runtime</literal> shown by <command>EXPLAIN ANALYZE</command> includes
-    executor start-up and shut-down time, as well as time spent processing
-    the result rows.  It does not include parsing, rewriting, or planning
-    time.  For a <command>SELECT</> query, the total run time will normally be just a
-    little larger than the total time reported for the top-level plan node.
-    For <command>INSERT</>, <command>UPDATE</>, and <command>DELETE</> commands, the total run time may be
-    considerably larger, because it includes the time spent processing the
-    result rows.  In these commands, the time for the top plan node
-    essentially is the time spent computing the new rows and/or locating
-    the old ones, but it doesn't include the time spent making the changes.
+    The <literal>Total runtime</literal> shown by <command>EXPLAIN
+    ANALYZE</command> includes executor start-up and shut-down time, as well
+    as time spent processing the result rows.  It does not include parsing,
+    rewriting, or planning time.  For a <command>SELECT</> query, the total
+    run time will normally be just a little larger than the total time
+    reported for the top-level plan node.  For <command>INSERT</>,
+    <command>UPDATE</>, and <command>DELETE</> commands, the total run time
+    may be considerably larger, because it includes the time spent processing
+    the result rows.  In these commands, the time for the top plan node
+    essentially is the time spent computing the new rows and/or locating the
+    old ones, but it doesn't include the time spent making the changes.
+    Time spent firing triggers, if any, is also outside the top plan node,
+    and is shown separately for each trigger.
    </para>
 
    <para>
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 552fd391fe0f6b27b926b17c39253eadf0e40a3c..d76c956d14ab22febcd3647c36369e99a443b905 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/catalog/pg_constraint.c,v 1.22 2004/12/31 21:59:38 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/catalog/pg_constraint.c,v 1.23 2005/03/25 21:57:57 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -21,12 +21,14 @@
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/pg_constraint.h"
+#include "catalog/pg_depend.h"
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
 #include "miscadmin.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
+#include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
 
@@ -510,3 +512,95 @@ RemoveConstraintById(Oid conId)
 	systable_endscan(conscan);
 	heap_close(conDesc, RowExclusiveLock);
 }
+
+/*
+ * GetConstraintNameForTrigger
+ *		Get the name of the constraint owning a trigger, if any
+ *
+ * Returns a palloc'd string, or NULL if no constraint can be found
+ */
+char *
+GetConstraintNameForTrigger(Oid triggerId)
+{
+	char	   *result;
+	Oid			constraintId = InvalidOid;
+	Oid			pg_trigger_id;
+	Oid			pg_constraint_id;
+	Relation	depRel;
+	Relation	conRel;
+	ScanKeyData key[2];
+	SysScanDesc scan;
+	HeapTuple	tup;
+
+	pg_trigger_id = get_system_catalog_relid(TriggerRelationName);
+	pg_constraint_id = get_system_catalog_relid(ConstraintRelationName);
+
+	/*
+	 * We must grovel through pg_depend to find the owning constraint.
+	 * Perhaps pg_trigger should have a column for the owning constraint ...
+	 * but right now this is not performance-critical code.
+	 */
+	depRel = heap_openr(DependRelationName, AccessShareLock);
+
+	ScanKeyInit(&key[0],
+				Anum_pg_depend_classid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(pg_trigger_id));
+	ScanKeyInit(&key[1],
+				Anum_pg_depend_objid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(triggerId));
+	/* assume we can ignore objsubid for a trigger */
+
+	scan = systable_beginscan(depRel, DependDependerIndex, true,
+							  SnapshotNow, 2, key);
+
+	while (HeapTupleIsValid(tup = systable_getnext(scan)))
+	{
+		Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(tup);
+
+		if (foundDep->refclassid == pg_constraint_id &&
+			foundDep->deptype == DEPENDENCY_INTERNAL)
+		{
+			constraintId = foundDep->refobjid;
+			break;
+		}
+	}
+
+	systable_endscan(scan);
+
+	heap_close(depRel, AccessShareLock);
+
+	if (!OidIsValid(constraintId))
+		return NULL;				/* no owning constraint found */
+
+	conRel = heap_openr(ConstraintRelationName, AccessShareLock);
+
+	ScanKeyInit(&key[0],
+				ObjectIdAttributeNumber,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(constraintId));
+
+	scan = systable_beginscan(conRel, ConstraintOidIndex, true,
+							  SnapshotNow, 1, key);
+
+	tup = systable_getnext(scan);
+
+	if (HeapTupleIsValid(tup))
+	{
+		Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tup);
+
+		result = pstrdup(NameStr(con->conname));
+	}
+	else
+	{
+		/* This arguably should be an error, but we'll just return NULL */
+		result = NULL;
+	}
+
+	systable_endscan(scan);
+
+	heap_close(conRel, AccessShareLock);
+
+	return result;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index d193d1dd3118a3a62f445304d898cdf86b232dc4..3713a039b4fd3e54f3300ea398c5f4b4f12846ec 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.238 2005/03/16 21:38:05 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.239 2005/03/25 21:57:57 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1510,6 +1510,10 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
 	resultRelInfo->ri_RangeTableIndex = 1;		/* dummy */
 	resultRelInfo->ri_RelationDesc = rel;
 	resultRelInfo->ri_TrigDesc = CopyTriggerDesc(rel->trigdesc);
+	if (resultRelInfo->ri_TrigDesc)
+		resultRelInfo->ri_TrigFunctions = (FmgrInfo *)
+			palloc0(resultRelInfo->ri_TrigDesc->numtriggers * sizeof(FmgrInfo));
+	resultRelInfo->ri_TrigInstrument = NULL;
 
 	ExecOpenIndices(resultRelInfo);
 
@@ -1974,7 +1978,7 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
 	/*
 	 * Handle queued AFTER triggers
 	 */
-	AfterTriggerEndQuery();
+	AfterTriggerEndQuery(estate);
 
 	pfree(values);
 	pfree(nulls);
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 02c9534cab8b562b7b93ab54fb3181596d88aff8..02ceedd39598f1de2a6ac8aa07e2793cb786d4c4 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994-5, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.130 2005/03/20 22:27:51 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.131 2005/03/25 21:57:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -15,6 +15,7 @@
 
 #include "access/genam.h"
 #include "access/heapam.h"
+#include "catalog/pg_constraint.h"
 #include "catalog/pg_type.h"
 #include "commands/explain.h"
 #include "commands/prepare.h"
@@ -272,16 +273,76 @@ ExplainOnePlan(QueryDesc *queryDesc, ExplainStmt *stmt,
 	}
 
 	/*
-	 * Close down the query and free resources; also run any queued
-	 * AFTER triggers.  Include time for this in the total runtime.
+	 * If we ran the command, run any AFTER triggers it queued.  (Note this
+	 * will not include DEFERRED triggers; since those don't run until end of
+	 * transaction, we can't measure them.)  Include into total runtime.
+	 */
+	if (stmt->analyze)
+	{
+		INSTR_TIME_SET_CURRENT(starttime);
+		AfterTriggerEndQuery(queryDesc->estate);
+		totaltime += elapsed_time(&starttime);
+	}
+
+	/* Print info about runtime of triggers */
+	if (es->printAnalyze)
+	{
+		ResultRelInfo *rInfo;
+		int		numrels = queryDesc->estate->es_num_result_relations;
+		int		nr;
+
+		rInfo = queryDesc->estate->es_result_relations;
+		for (nr = 0; nr < numrels; rInfo++, nr++)
+		{
+			int		nt;
+
+			if (!rInfo->ri_TrigDesc)
+				continue;
+			for (nt = 0; nt < rInfo->ri_TrigDesc->numtriggers; nt++)
+			{
+				Trigger *trig = rInfo->ri_TrigDesc->triggers + nt;
+				Instrumentation *instr = rInfo->ri_TrigInstrument + nt;
+				char   *conname;
+
+				/* Must clean up instrumentation state */
+				InstrEndLoop(instr);
+
+				/*
+				 * We ignore triggers that were never invoked; they likely
+				 * aren't relevant to the current query type.
+				 */
+				if (instr->ntuples == 0)
+					continue;
+
+				if (trig->tgisconstraint &&
+					(conname = GetConstraintNameForTrigger(trig->tgoid)) != NULL)
+				{
+					appendStringInfo(str, "Trigger for constraint %s",
+									 conname);
+					pfree(conname);
+				}
+				else
+					appendStringInfo(str, "Trigger %s", trig->tgname);
+
+				if (numrels > 1)
+					appendStringInfo(str, " on %s",
+									 RelationGetRelationName(rInfo->ri_RelationDesc));
+
+				appendStringInfo(str, ": time=%.3f calls=%.0f\n",
+								 1000.0 * instr->total,
+								 instr->ntuples);
+			}
+		}
+	}
+
+	/*
+	 * Close down the query and free resources.  Include time for this
+	 * in the total runtime (although it should be pretty minimal).
 	 */
 	INSTR_TIME_SET_CURRENT(starttime);
 
 	ExecutorEnd(queryDesc);
 
-	if (stmt->analyze)
-		AfterTriggerEndQuery();
-
 	FreeQueryDesc(queryDesc);
 
 	/* We need a CCI just in case query expanded to multiple plans */
diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c
index ca72503d04ac3fda2f35a680345fc704a4b46721..3d5133f8254a89a6aa52fef4b19d195454b90e83 100644
--- a/src/backend/commands/portalcmds.c
+++ b/src/backend/commands/portalcmds.c
@@ -14,7 +14,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.38 2004/12/31 21:59:41 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.39 2005/03/25 21:57:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -281,8 +281,8 @@ PortalCleanup(Portal portal)
 			PG_TRY();
 			{
 				CurrentResourceOwner = portal->resowner;
-				ExecutorEnd(queryDesc);
 				/* we do not need AfterTriggerEndQuery() here */
+				ExecutorEnd(queryDesc);
 			}
 			PG_CATCH();
 			{
@@ -382,8 +382,8 @@ PersistHoldablePortal(Portal portal)
 		 * Now shut down the inner executor.
 		 */
 		portal->queryDesc = NULL;		/* prevent double shutdown */
-		ExecutorEnd(queryDesc);
 		/* we do not need AfterTriggerEndQuery() here */
+		ExecutorEnd(queryDesc);
 
 		/*
 		 * Reset the position in the result set: ideally, this could be
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 54e0eac26888654503413084e02af933345c30df..277d87444887148c053e51d1e8eda8197f8bb75e 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.180 2005/03/24 00:03:26 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.181 2005/03/25 21:57:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -28,6 +28,7 @@
 #include "commands/defrem.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
+#include "executor/instrument.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_func.h"
@@ -46,7 +47,9 @@ static HeapTuple GetTupleForTrigger(EState *estate,
 				   CommandId cid,
 				   TupleTableSlot **newSlot);
 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);
@@ -1107,20 +1110,26 @@ equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2)
  * Call a trigger function.
  *
  *		trigdata: trigger descriptor.
- *		finfo: possibly-cached call info for the function.
+ *		tgindx: trigger's index in finfo and instr arrays.
+ *		finfo: array of cached trigger function call information.
+ *		instr: optional array of EXPLAIN ANALYZE instrumentation state.
  *		per_tuple_context: memory context to execute the function in.
  *
  * Returns the tuple (or NULL) as returned by the function.
  */
 static HeapTuple
 ExecCallTriggerFunc(TriggerData *trigdata,
+					int tgindx,
 					FmgrInfo *finfo,
+					Instrumentation *instr,
 					MemoryContext per_tuple_context)
 {
 	FunctionCallInfoData fcinfo;
 	Datum		result;
 	MemoryContext oldContext;
 
+	finfo += tgindx;
+
 	/*
 	 * We cache fmgr lookup info, to avoid making the lookup again on each
 	 * call.
@@ -1130,6 +1139,12 @@ ExecCallTriggerFunc(TriggerData *trigdata,
 
 	Assert(finfo->fn_oid == trigdata->tg_trigger->tgfoid);
 
+	/*
+	 * If doing EXPLAIN ANALYZE, start charging time to this trigger.
+	 */
+	if (instr)
+		InstrStartNode(instr + tgindx);
+
 	/*
 	 * Do the function evaluation in the per-tuple memory context, so that
 	 * leaked memory will be reclaimed once per tuple. Note in particular
@@ -1160,6 +1175,13 @@ ExecCallTriggerFunc(TriggerData *trigdata,
 				 errmsg("trigger function %u returned null value",
 						fcinfo.flinfo->fn_oid)));
 
+	/*
+	 * If doing EXPLAIN ANALYZE, stop charging time to this trigger,
+	 * and count one "tuple returned" (really the number of firings).
+	 */
+	if (instr)
+		InstrStopNode(instr + tgindx, true);
+
 	return (HeapTuple) DatumGetPointer(result);
 }
 
@@ -1183,11 +1205,6 @@ ExecBSInsertTriggers(EState *estate, ResultRelInfo *relinfo)
 	if (ntrigs == 0)
 		return;
 
-	/* Allocate cache space for fmgr lookup info, if not done yet */
-	if (relinfo->ri_TrigFunctions == NULL)
-		relinfo->ri_TrigFunctions = (FmgrInfo *)
-			palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
-
 	LocTriggerData.type = T_TriggerData;
 	LocTriggerData.tg_event = TRIGGER_EVENT_INSERT |
 		TRIGGER_EVENT_BEFORE;
@@ -1205,7 +1222,9 @@ ExecBSInsertTriggers(EState *estate, ResultRelInfo *relinfo)
 			continue;
 		LocTriggerData.tg_trigger = trigger;
 		newtuple = ExecCallTriggerFunc(&LocTriggerData,
-								   relinfo->ri_TrigFunctions + tgindx[i],
+									   tgindx[i],
+									   relinfo->ri_TrigFunctions,
+									   relinfo->ri_TrigInstrument,
 									   GetPerTupleMemoryContext(estate));
 
 		if (newtuple)
@@ -1237,11 +1256,6 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 	TriggerData LocTriggerData;
 	int			i;
 
-	/* Allocate cache space for fmgr lookup info, if not done yet */
-	if (relinfo->ri_TrigFunctions == NULL)
-		relinfo->ri_TrigFunctions = (FmgrInfo *)
-			palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
-
 	LocTriggerData.type = T_TriggerData;
 	LocTriggerData.tg_event = TRIGGER_EVENT_INSERT |
 		TRIGGER_EVENT_ROW |
@@ -1259,7 +1273,9 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 		LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
 		LocTriggerData.tg_trigger = trigger;
 		newtuple = ExecCallTriggerFunc(&LocTriggerData,
-								   relinfo->ri_TrigFunctions + tgindx[i],
+									   tgindx[i],
+									   relinfo->ri_TrigFunctions,
+									   relinfo->ri_TrigInstrument,
 									   GetPerTupleMemoryContext(estate));
 		if (oldtuple != newtuple && oldtuple != trigtuple)
 			heap_freetuple(oldtuple);
@@ -1300,11 +1316,6 @@ ExecBSDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
 	if (ntrigs == 0)
 		return;
 
-	/* Allocate cache space for fmgr lookup info, if not done yet */
-	if (relinfo->ri_TrigFunctions == NULL)
-		relinfo->ri_TrigFunctions = (FmgrInfo *)
-			palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
-
 	LocTriggerData.type = T_TriggerData;
 	LocTriggerData.tg_event = TRIGGER_EVENT_DELETE |
 		TRIGGER_EVENT_BEFORE;
@@ -1322,7 +1333,9 @@ ExecBSDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
 			continue;
 		LocTriggerData.tg_trigger = trigger;
 		newtuple = ExecCallTriggerFunc(&LocTriggerData,
-								   relinfo->ri_TrigFunctions + tgindx[i],
+									   tgindx[i],
+									   relinfo->ri_TrigFunctions,
+									   relinfo->ri_TrigInstrument,
 									   GetPerTupleMemoryContext(estate));
 
 		if (newtuple)
@@ -1360,11 +1373,6 @@ ExecBRDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
 	if (trigtuple == NULL)
 		return false;
 
-	/* Allocate cache space for fmgr lookup info, if not done yet */
-	if (relinfo->ri_TrigFunctions == NULL)
-		relinfo->ri_TrigFunctions = (FmgrInfo *)
-			palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
-
 	LocTriggerData.type = T_TriggerData;
 	LocTriggerData.tg_event = TRIGGER_EVENT_DELETE |
 		TRIGGER_EVENT_ROW |
@@ -1382,7 +1390,9 @@ ExecBRDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
 		LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
 		LocTriggerData.tg_trigger = trigger;
 		newtuple = ExecCallTriggerFunc(&LocTriggerData,
-								   relinfo->ri_TrigFunctions + tgindx[i],
+									   tgindx[i],
+									   relinfo->ri_TrigFunctions,
+									   relinfo->ri_TrigInstrument,
 									   GetPerTupleMemoryContext(estate));
 		if (newtuple == NULL)
 			break;
@@ -1433,11 +1443,6 @@ ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
 	if (ntrigs == 0)
 		return;
 
-	/* Allocate cache space for fmgr lookup info, if not done yet */
-	if (relinfo->ri_TrigFunctions == NULL)
-		relinfo->ri_TrigFunctions = (FmgrInfo *)
-			palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
-
 	LocTriggerData.type = T_TriggerData;
 	LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
 		TRIGGER_EVENT_BEFORE;
@@ -1455,7 +1460,9 @@ ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
 			continue;
 		LocTriggerData.tg_trigger = trigger;
 		newtuple = ExecCallTriggerFunc(&LocTriggerData,
-								   relinfo->ri_TrigFunctions + tgindx[i],
+									   tgindx[i],
+									   relinfo->ri_TrigFunctions,
+									   relinfo->ri_TrigInstrument,
 									   GetPerTupleMemoryContext(estate));
 
 		if (newtuple)
@@ -1501,11 +1508,6 @@ ExecBRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 	if (newSlot != NULL)
 		intuple = newtuple = ExecRemoveJunk(estate->es_junkFilter, newSlot);
 
-	/* Allocate cache space for fmgr lookup info, if not done yet */
-	if (relinfo->ri_TrigFunctions == NULL)
-		relinfo->ri_TrigFunctions = (FmgrInfo *)
-			palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
-
 	LocTriggerData.type = T_TriggerData;
 	LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
 		TRIGGER_EVENT_ROW |
@@ -1523,7 +1525,9 @@ ExecBRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 		LocTriggerData.tg_newtuplebuf = InvalidBuffer;
 		LocTriggerData.tg_trigger = trigger;
 		newtuple = ExecCallTriggerFunc(&LocTriggerData,
-								   relinfo->ri_TrigFunctions + tgindx[i],
+									   tgindx[i],
+									   relinfo->ri_TrigFunctions,
+									   relinfo->ri_TrigInstrument,
 									   GetPerTupleMemoryContext(estate));
 		if (oldtuple != newtuple && oldtuple != intuple)
 			heap_freetuple(oldtuple);
@@ -1798,6 +1802,7 @@ static AfterTriggers afterTriggers;
 static void AfterTriggerExecute(AfterTriggerEvent event,
 								Relation rel, TriggerDesc *trigdesc,
 								FmgrInfo *finfo,
+								Instrumentation *instr,
 								MemoryContext per_tuple_context);
 static SetConstraintState SetConstraintStateCreate(int numalloc);
 static SetConstraintState SetConstraintStateCopy(SetConstraintState state);
@@ -1886,18 +1891,22 @@ afterTriggerAddEvent(AfterTriggerEvent event)
  *
  *	Frequently, this will be fired many times in a row for triggers of
  *	a single relation.	Therefore, we cache the open relation and provide
- *	fmgr lookup cache space at the caller level.
+ *	fmgr lookup cache space at the caller level.  (For triggers fired at
+ *	the end of a query, we can even piggyback on the executor's state.)
  *
  *	event: event currently being fired.
  *	rel: open relation for event.
  *	trigdesc: working copy of rel's trigger info.
  *	finfo: array of fmgr lookup cache entries (one per trigger in trigdesc).
+ *	instr: array of EXPLAIN ANALYZE instrumentation nodes (one per trigger),
+ *		or NULL if no instrumentation is wanted.
  *	per_tuple_context: memory context to call trigger function in.
  * ----------
  */
 static void
 AfterTriggerExecute(AfterTriggerEvent event,
-					Relation rel, TriggerDesc *trigdesc, FmgrInfo *finfo,
+					Relation rel, TriggerDesc *trigdesc,
+					FmgrInfo *finfo, Instrumentation *instr,
 					MemoryContext per_tuple_context)
 {
 	Oid			tgoid = event->ate_tgoid;
@@ -1909,6 +1918,28 @@ AfterTriggerExecute(AfterTriggerEvent event,
 	Buffer		newbuffer = InvalidBuffer;
 	int			tgindx;
 
+	/*
+	 * Locate trigger in trigdesc.
+	 */
+	LocTriggerData.tg_trigger = NULL;
+	for (tgindx = 0; tgindx < trigdesc->numtriggers; tgindx++)
+	{
+		if (trigdesc->triggers[tgindx].tgoid == tgoid)
+		{
+			LocTriggerData.tg_trigger = &(trigdesc->triggers[tgindx]);
+			break;
+		}
+	}
+	if (LocTriggerData.tg_trigger == NULL)
+		elog(ERROR, "could not find trigger %u", tgoid);
+
+	/*
+	 * If doing EXPLAIN ANALYZE, start charging time to this trigger.
+	 * We want to include time spent re-fetching tuples in the trigger cost.
+	 */
+	if (instr)
+		InstrStartNode(instr + tgindx);
+
 	/*
 	 * Fetch the required OLD and NEW tuples.
 	 */
@@ -1934,18 +1965,6 @@ AfterTriggerExecute(AfterTriggerEvent event,
 		event->ate_event & (TRIGGER_EVENT_OPMASK | TRIGGER_EVENT_ROW);
 	LocTriggerData.tg_relation = rel;
 
-	LocTriggerData.tg_trigger = NULL;
-	for (tgindx = 0; tgindx < trigdesc->numtriggers; tgindx++)
-	{
-		if (trigdesc->triggers[tgindx].tgoid == tgoid)
-		{
-			LocTriggerData.tg_trigger = &(trigdesc->triggers[tgindx]);
-			break;
-		}
-	}
-	if (LocTriggerData.tg_trigger == NULL)
-		elog(ERROR, "could not find trigger %u", tgoid);
-
 	switch (event->ate_event & TRIGGER_EVENT_OPMASK)
 	{
 		case TRIGGER_EVENT_INSERT:
@@ -1973,11 +1992,13 @@ AfterTriggerExecute(AfterTriggerEvent event,
 	MemoryContextReset(per_tuple_context);
 
 	/*
-	 * Call the trigger and throw away any eventually returned updated
-	 * tuple.
+	 * Call the trigger and throw away any possibly returned updated
+	 * tuple.  (Don't let ExecCallTriggerFunc measure EXPLAIN time.)
 	 */
 	rettuple = ExecCallTriggerFunc(&LocTriggerData,
-								   finfo + tgindx,
+								   tgindx,
+								   finfo,
+								   NULL,
 								   per_tuple_context);
 	if (rettuple != NULL && rettuple != &oldtuple && rettuple != &newtuple)
 		heap_freetuple(rettuple);
@@ -1989,6 +2010,13 @@ AfterTriggerExecute(AfterTriggerEvent event,
 		ReleaseBuffer(oldbuffer);
 	if (newbuffer != InvalidBuffer)
 		ReleaseBuffer(newbuffer);
+
+	/*
+	 * If doing EXPLAIN ANALYZE, stop charging time to this trigger,
+	 * and count one "tuple returned" (really the number of firings).
+	 */
+	if (instr)
+		InstrStopNode(instr + tgindx, true);
 }
 
 
@@ -2093,6 +2121,12 @@ afterTriggerMarkEvents(AfterTriggerEventList *events,
  *	Scan the given event list for events that are marked as to be fired
  *	in the current firing cycle, and fire them.
  *
+ *	If estate isn't NULL, then we expect that all the firable events are
+ *	for triggers of the relations included in the estate's result relation
+ *	array.  This allows us to re-use the estate's open relations and
+ *	trigger cache info.  When estate is NULL, we have to find the relations
+ *	the hard way.
+ *
  *	When delete_ok is TRUE, it's okay to delete fully-processed events.
  *	The events list pointers are updated.
  * ----------
@@ -2100,6 +2134,7 @@ afterTriggerMarkEvents(AfterTriggerEventList *events,
 static void
 afterTriggerInvokeEvents(AfterTriggerEventList *events,
 						 CommandId firing_id,
+						 EState *estate,
 						 bool delete_ok)
 {
 	AfterTriggerEvent event,
@@ -2108,6 +2143,7 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
 	Relation	rel = NULL;
 	TriggerDesc *trigdesc = NULL;
 	FmgrInfo   *finfo = NULL;
+	Instrumentation *instr = NULL;
 
 	/* Make a per-tuple memory context for trigger function calls */
 	per_tuple_context =
@@ -2136,35 +2172,65 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
 			 */
 			if (rel == NULL || rel->rd_id != event->ate_relid)
 			{
-				if (rel)
-					heap_close(rel, NoLock);
-				if (trigdesc)
-					FreeTriggerDesc(trigdesc);
-				if (finfo)
-					pfree(finfo);
-
-				/*
-				 * We assume that an appropriate lock is still held by
-				 * the executor, so grab no new lock here.
-				 */
-				rel = heap_open(event->ate_relid, NoLock);
-
-				/*
-				 * Copy relation's trigger info so that we have a
-				 * stable copy no matter what the called triggers do.
-				 */
-				trigdesc = CopyTriggerDesc(rel->trigdesc);
-
-				if (trigdesc == NULL)		/* should not happen */
-					elog(ERROR, "relation %u has no triggers",
-						 event->ate_relid);
+				if (estate)
+				{
+					/* Find target relation among estate's result rels */
+					ResultRelInfo *rInfo;
+					int		nr;
 
-				/*
-				 * Allocate space to cache fmgr lookup info for
-				 * triggers.
-				 */
-				finfo = (FmgrInfo *)
-					palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
+					rInfo = estate->es_result_relations;
+					nr = estate->es_num_result_relations;
+					while (nr > 0)
+					{
+						if (rInfo->ri_RelationDesc->rd_id == event->ate_relid)
+							break;
+						rInfo++;
+						nr--;
+					}
+					if (nr <= 0)				/* should not happen */
+						elog(ERROR, "could not find relation %u among query result relations",
+							 event->ate_relid);
+					rel = rInfo->ri_RelationDesc;
+					trigdesc = rInfo->ri_TrigDesc;
+					finfo = rInfo->ri_TrigFunctions;
+					instr = rInfo->ri_TrigInstrument;
+				}
+				else
+				{
+					/* Hard way: we manage the resources for ourselves */
+					if (rel)
+						heap_close(rel, NoLock);
+					if (trigdesc)
+						FreeTriggerDesc(trigdesc);
+					if (finfo)
+						pfree(finfo);
+					Assert(instr == NULL);	/* never used in this case */
+
+					/*
+					 * We assume that an appropriate lock is still held by
+					 * the executor, so grab no new lock here.
+					 */
+					rel = heap_open(event->ate_relid, NoLock);
+
+					/*
+					 * Copy relation's trigger info so that we have a
+					 * stable copy no matter what the called triggers do.
+					 */
+					trigdesc = CopyTriggerDesc(rel->trigdesc);
+
+					if (trigdesc == NULL)		/* should not happen */
+						elog(ERROR, "relation %u has no triggers",
+							 event->ate_relid);
+
+					/*
+					 * Allocate space to cache fmgr lookup info for
+					 * triggers.
+					 */
+					finfo = (FmgrInfo *)
+						palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
+
+					/* Never any EXPLAIN info in this case */
+				}
 			}
 
 			/*
@@ -2172,7 +2238,7 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
 			 * set, so recursive examinations of the event list won't try
 			 * to re-fire it.
 			 */
-			AfterTriggerExecute(event, rel, trigdesc, finfo,
+			AfterTriggerExecute(event, rel, trigdesc, finfo, instr,
 								per_tuple_context);
 
 			/*
@@ -2214,12 +2280,16 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
 	events->tail = prev_event;
 
 	/* Release working resources */
-	if (rel)
-		heap_close(rel, NoLock);
-	if (trigdesc)
-		FreeTriggerDesc(trigdesc);
-	if (finfo)
-		pfree(finfo);
+	if (!estate)
+	{
+		if (rel)
+			heap_close(rel, NoLock);
+		if (trigdesc)
+			FreeTriggerDesc(trigdesc);
+		if (finfo)
+			pfree(finfo);
+		Assert(instr == NULL);	/* never used in this case */
+	}
 	MemoryContextDelete(per_tuple_context);
 }
 
@@ -2308,10 +2378,14 @@ AfterTriggerBeginQuery(void)
  *	Called after one query has been completely processed. At this time
  *	we invoke all AFTER IMMEDIATE trigger events queued by the query, and
  *	transfer deferred trigger events to the global deferred-trigger list.
+ *
+ *	Note that this should be called just BEFORE closing down the executor
+ *	with ExecutorEnd, because we make use of the EState's info about
+ *	target relations.
  * ----------
  */
 void
-AfterTriggerEndQuery(void)
+AfterTriggerEndQuery(EState *estate)
 {
 	AfterTriggerEventList *events;
 
@@ -2339,7 +2413,7 @@ AfterTriggerEndQuery(void)
 		CommandId		firing_id = afterTriggers->firing_counter++;
 
 		/* OK to delete the immediate events after processing them */
-		afterTriggerInvokeEvents(events, firing_id, true);
+		afterTriggerInvokeEvents(events, firing_id, estate, true);
 	}
 
 	afterTriggers->query_depth--;
@@ -2381,7 +2455,7 @@ AfterTriggerEndXact(void)
 	{
 		CommandId		firing_id = afterTriggers->firing_counter++;
 
-		afterTriggerInvokeEvents(events, firing_id, true);
+		afterTriggerInvokeEvents(events, firing_id, NULL, true);
 	}
 
 	/*
@@ -2838,7 +2912,7 @@ AfterTriggerSetState(ConstraintsSetStmt *stmt)
 			 * level, but we'd better not if inside a subtransaction, since
 			 * the subtransaction could later get rolled back.
 			 */
-			afterTriggerInvokeEvents(events, firing_id,
+			afterTriggerInvokeEvents(events, firing_id, NULL,
 									 !IsSubTransaction());
 		}
 	}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ad3ff3e5b909cbf8aee91e38bfca7bc263598e51..304f1ba6b10924bd1d712e754eb28a8421c7b02f 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -26,7 +26,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.243 2005/03/20 23:40:25 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.244 2005/03/25 21:57:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -39,6 +39,7 @@
 #include "commands/trigger.h"
 #include "executor/execdebug.h"
 #include "executor/execdefs.h"
+#include "executor/instrument.h"
 #include "miscadmin.h"
 #include "optimizer/clauses.h"
 #include "optimizer/var.h"
@@ -69,7 +70,8 @@ static void InitPlan(QueryDesc *queryDesc, bool explainOnly);
 static void initResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Index resultRelationIndex,
 				  List *rangeTable,
-				  CmdType operation);
+				  CmdType operation,
+				  bool doInstrument);
 static TupleTableSlot *ExecutePlan(EState *estate, PlanState *planstate,
 			CmdType operation,
 			long numberTuples,
@@ -508,7 +510,8 @@ InitPlan(QueryDesc *queryDesc, bool explainOnly)
 				initResultRelInfo(resultRelInfo,
 								  lfirst_int(l),
 								  rangeTable,
-								  operation);
+								  operation,
+								  estate->es_instrument);
 				resultRelInfo++;
 			}
 		}
@@ -523,7 +526,8 @@ InitPlan(QueryDesc *queryDesc, bool explainOnly)
 			initResultRelInfo(resultRelInfos,
 							  parseTree->resultRelation,
 							  rangeTable,
-							  operation);
+							  operation,
+							  estate->es_instrument);
 		}
 
 		estate->es_result_relations = resultRelInfos;
@@ -798,7 +802,8 @@ static void
 initResultRelInfo(ResultRelInfo *resultRelInfo,
 				  Index resultRelationIndex,
 				  List *rangeTable,
-				  CmdType operation)
+				  CmdType operation,
+				  bool doInstrument)
 {
 	Oid			resultRelationOid;
 	Relation	resultRelationDesc;
@@ -837,7 +842,22 @@ initResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_IndexRelationInfo = NULL;
 	/* make a copy so as not to depend on relcache info not changing... */
 	resultRelInfo->ri_TrigDesc = CopyTriggerDesc(resultRelationDesc->trigdesc);
-	resultRelInfo->ri_TrigFunctions = NULL;
+	if (resultRelInfo->ri_TrigDesc)
+	{
+		int		n = resultRelInfo->ri_TrigDesc->numtriggers;
+
+		resultRelInfo->ri_TrigFunctions = (FmgrInfo *)
+			palloc0(n * sizeof(FmgrInfo));
+		if (doInstrument)
+			resultRelInfo->ri_TrigInstrument = InstrAlloc(n);
+		else
+			resultRelInfo->ri_TrigInstrument = NULL;
+	}
+	else
+	{
+		resultRelInfo->ri_TrigFunctions = NULL;
+		resultRelInfo->ri_TrigInstrument = NULL;
+	}
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index 2bd4b7e6fc02f68b77cfa6ba93043f9745729951..71b274530cfac23389926b6ed787b53b3565250e 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -12,7 +12,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/execProcnode.c,v 1.46 2004/12/31 21:59:45 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/execProcnode.c,v 1.47 2005/03/25 21:57:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -252,7 +252,7 @@ ExecInitNode(Plan *node, EState *estate)
 
 	/* Set up instrumentation for this node if requested */
 	if (estate->es_instrument)
-		result->instrument = InstrAlloc();
+		result->instrument = InstrAlloc(1);
 
 	return result;
 }
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index df54b56ff575e3acff53aef749d12b9c0609e87a..fdbd6107a0fedf22ca2d89098196db0de2ad5477 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.92 2005/03/16 21:38:07 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.93 2005/03/25 21:57:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -399,8 +399,8 @@ postquel_end(execution_state *es, SQLFunctionCachePtr fcache)
 		{
 			ActiveSnapshot = es->qd->snapshot;
 
+			AfterTriggerEndQuery(es->qd->estate);
 			ExecutorEnd(es->qd);
-			AfterTriggerEndQuery();
 		}
 		PG_CATCH();
 		{
diff --git a/src/backend/executor/instrument.c b/src/backend/executor/instrument.c
index 8b9b6abb935f8ed33346506d433149899a97305b..52d9de4f4ac2898f3483c6eeca766d381138d715 100644
--- a/src/backend/executor/instrument.c
+++ b/src/backend/executor/instrument.c
@@ -7,7 +7,7 @@
  * Copyright (c) 2001-2005, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/instrument.c,v 1.10 2005/03/20 22:27:51 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/instrument.c,v 1.11 2005/03/25 21:57:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -18,13 +18,13 @@
 #include "executor/instrument.h"
 
 
-/* Allocate new instrumentation structure */
+/* Allocate new instrumentation structure(s) */
 Instrumentation *
-InstrAlloc(void)
+InstrAlloc(int n)
 {
-	Instrumentation *instr = palloc(sizeof(Instrumentation));
+	Instrumentation *instr = palloc0(n * sizeof(Instrumentation));
 
-	memset(instr, 0, sizeof(Instrumentation));
+	/* we don't need to do any initialization except zero 'em */
 
 	return instr;
 }
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index d26e3068509020ce36ea05a79d2c1f1ecf899652..de119ec7762eac9150b605e1bd23f62858d89440 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.135 2005/03/16 21:38:08 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.136 2005/03/25 21:57:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1546,10 +1546,10 @@ _SPI_pquery(QueryDesc *queryDesc, int tcount)
 			elog(ERROR, "consistency check on SPI tuple count failed");
 	}
 
-	ExecutorEnd(queryDesc);
-
 	/* Take care of any queued AFTER triggers */
-	AfterTriggerEndQuery();
+	AfterTriggerEndQuery(queryDesc->estate);
+
+	ExecutorEnd(queryDesc);
 
 	if (queryDesc->dest->mydest == SPI)
 	{
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 1f66fda2b7291707b07e1b9d65bd5d2952593a56..51f3df8e56b2aaa1c2a136742edbaef5bebd7b3e 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.92 2005/03/16 21:38:08 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.93 2005/03/25 21:57:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -206,14 +206,14 @@ ProcessQuery(Query *parsetree,
 		}
 	}
 
+	/* Now take care of any queued AFTER triggers */
+	AfterTriggerEndQuery(queryDesc->estate);
+
 	/*
 	 * Now, we close down all the scans and free allocated resources.
 	 */
 	ExecutorEnd(queryDesc);
 
-	/* And take care of any queued AFTER triggers */
-	AfterTriggerEndQuery();
-
 	FreeQueryDesc(queryDesc);
 
 	FreeSnapshot(ActiveSnapshot);
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index 3231f4b778830eccae8407d3ef583f17b2298bf7..fdc8c0676da6f70cac6f7812282dac4f7bcf2dc7 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_constraint.h,v 1.14 2004/12/31 22:03:24 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_constraint.h,v 1.15 2005/03/25 21:57:59 tgl Exp $
  *
  * NOTES
  *	  the genbki.sh script reads this file and generates .bki
@@ -181,4 +181,6 @@ extern char *ChooseConstraintName(const char *name1, const char *name2,
 					 const char *label, Oid namespace,
 					 List *others);
 
+extern char *GetConstraintNameForTrigger(Oid triggerId);
+
 #endif   /* PG_CONSTRAINT_H */
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index 1f84b0eaa9a8d0f0bcf28e566b869fd732bf7bde..1aacccc8110221f4a73fde6fb8a8381816117a08 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.51 2004/12/31 22:03:28 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.52 2005/03/25 21:57:59 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -156,7 +156,7 @@ extern void ExecARUpdateTriggers(EState *estate,
 
 extern void AfterTriggerBeginXact(void);
 extern void AfterTriggerBeginQuery(void);
-extern void AfterTriggerEndQuery(void);
+extern void AfterTriggerEndQuery(EState *estate);
 extern void AfterTriggerEndXact(void);
 extern void AfterTriggerAbortXact(void);
 extern void AfterTriggerBeginSubXact(void);
diff --git a/src/include/executor/instrument.h b/src/include/executor/instrument.h
index 8936c5cce23833b9494816815d8f58b845f71c7b..0540fd0da71cab07596e9575968bc7625c5426ff 100644
--- a/src/include/executor/instrument.h
+++ b/src/include/executor/instrument.h
@@ -6,7 +6,7 @@
  *
  * Copyright (c) 2001-2005, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/include/executor/instrument.h,v 1.9 2005/03/20 22:27:52 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/executor/instrument.h,v 1.10 2005/03/25 21:57:59 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -70,7 +70,7 @@ typedef struct Instrumentation
 	double		nloops;			/* # of run cycles for this node */
 } Instrumentation;
 
-extern Instrumentation *InstrAlloc(void);
+extern Instrumentation *InstrAlloc(int n);
 extern void InstrStartNode(Instrumentation *instr);
 extern void InstrStopNode(Instrumentation *instr, bool returnedTuple);
 extern void InstrEndLoop(Instrumentation *instr);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 8e5404b50785278f40c2ecb034ca7469013013bd..da82daaac9832083162d2a482b51cf0709b414cf 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.124 2005/03/16 21:38:10 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.125 2005/03/25 21:58:00 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -261,6 +261,7 @@ typedef struct JunkFilter
  *		IndexRelationInfo		array of key/attr info for indices
  *		TrigDesc				triggers to be fired, if any
  *		TrigFunctions			cached lookup info for trigger functions
+ *		TrigInstrument			optional runtime measurements for triggers
  *		ConstraintExprs			array of constraint-checking expr states
  *		junkFilter				for removing junk attributes from tuples
  * ----------------
@@ -275,6 +276,7 @@ typedef struct ResultRelInfo
 	IndexInfo **ri_IndexRelationInfo;
 	TriggerDesc *ri_TrigDesc;
 	FmgrInfo   *ri_TrigFunctions;
+	struct Instrumentation *ri_TrigInstrument;
 	List	  **ri_ConstraintExprs;
 	JunkFilter *ri_junkFilter;
 } ResultRelInfo;