diff --git a/doc/src/sgml/trigger.sgml b/doc/src/sgml/trigger.sgml
index 686c9580edf8f2421645b90e32b4c78b5dfa929e..bb88ccc4ded0052251bc3a6873883dd00844343a 100644
--- a/doc/src/sgml/trigger.sgml
+++ b/doc/src/sgml/trigger.sgml
@@ -366,7 +366,6 @@ typedef struct Trigger
     Oid         tgoid;
     char       *tgname;
     Oid         tgfoid;
-    FmgrInfo    tgfunc;
     int16       tgtype;
     bool        tgenabled;
     bool        tgisconstraint;
diff --git a/src/backend/access/common/scankey.c b/src/backend/access/common/scankey.c
index dc2d36b52f47bbe57d3b7bf1c32c54bae25f2845..eb66d41bf26e43e31a1cef92b0141cca0791d34a 100644
--- a/src/backend/access/common/scankey.c
+++ b/src/backend/access/common/scankey.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/access/common/scankey.c,v 1.18 2001/01/24 19:42:47 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/access/common/scankey.c,v 1.19 2001/06/01 02:41:35 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -40,11 +40,13 @@ ScanKeyEntrySetIllegal(ScanKey entry)
 	entry->sk_flags = 0;		/* just in case... */
 	entry->sk_attno = InvalidAttrNumber;
 	entry->sk_procedure = 0;	/* should be InvalidRegProcedure */
+	entry->sk_func.fn_oid = InvalidOid;
+	entry->sk_argument = (Datum) 0;
 }
 
 /*
  * ScanKeyEntryInitialize
- *		Initializes an scan key entry.
+ *		Initializes a scan key entry.
  *
  * Note:
  *		Assumes the scan key entry is valid.
@@ -64,7 +66,6 @@ ScanKeyEntryInitialize(ScanKey entry,
 	entry->sk_procedure = procedure;
 	entry->sk_argument = argument;
 	fmgr_info(procedure, &entry->sk_func);
-	entry->sk_nargs = entry->sk_func.fn_nargs;
 
 	Assert(ScanKeyEntryIsLegal(entry));
 }
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index 3cea6895f318722f79f976f80d6e185975afe832..5b12930114873ce73567dd01b158cb25d6f21e66 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/access/index/indexam.c,v 1.49 2001/05/31 18:16:54 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/access/index/indexam.c,v 1.50 2001/06/01 02:41:35 tgl Exp $
  *
  * INTERFACE ROUTINES
  *		index_open		- open an index relation by relationId
@@ -232,7 +232,7 @@ index_beginscan(Relation relation,
 				uint16 numberOfKeys,
 				ScanKey key)
 {
-	IndexScanDesc scandesc;
+	IndexScanDesc scan;
 	RegProcedure procedure;
 
 	RELATION_CHECKS;
@@ -249,14 +249,22 @@ index_beginscan(Relation relation,
 	 */
 	LockRelation(relation, AccessShareLock);
 
-	scandesc = (IndexScanDesc)
+	scan = (IndexScanDesc)
 		DatumGetPointer(OidFunctionCall4(procedure,
 										 PointerGetDatum(relation),
 										 BoolGetDatum(scanFromEnd),
 										 UInt16GetDatum(numberOfKeys),
 										 PointerGetDatum(key)));
 
-	return scandesc;
+	/*
+	 * We want to look up the amgettuple procedure just once per scan,
+	 * not once per index_getnext call.  So do it here and save
+	 * the fmgr info result in the scan descriptor.
+	 */
+	GET_SCAN_PROCEDURE(beginscan, amgettuple);
+	fmgr_info(procedure, &scan->fn_getnext);
+
+	return scan;
 }
 
 /* ----------------
@@ -345,19 +353,9 @@ index_getnext(IndexScanDesc scan,
 
 	SCAN_CHECKS;
 
-	/*
-	 * Look up the access procedure only once per scan.
-	 */
-	if (scan->fn_getnext.fn_oid == InvalidOid)
-	{
-		RegProcedure procedure;
-
-		GET_SCAN_PROCEDURE(getnext, amgettuple);
-		fmgr_info(procedure, &scan->fn_getnext);
-	}
-
 	/*
 	 * have the am's gettuple proc do all the work.
+	 * index_beginscan already set up fn_getnext.
 	 */
 	result = (RetrieveIndexResult)
 		DatumGetPointer(FunctionCall2(&scan->fn_getnext,
diff --git a/src/backend/access/index/istrat.c b/src/backend/access/index/istrat.c
index 8cd1284972ea538ffc8b458d30d56a3c347bf0cb..188f69b57194e3e67e3f7b3f5967e5a02da94675 100644
--- a/src/backend/access/index/istrat.c
+++ b/src/backend/access/index/istrat.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/access/index/Attic/istrat.c,v 1.50 2001/05/30 19:53:40 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/access/index/Attic/istrat.c,v 1.51 2001/06/01 02:41:35 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -458,6 +458,8 @@ RelationInvokeStrategy(Relation relation,
 
 /* ----------------
  *		OperatorRelationFillScanKeyEntry
+ *
+ * Initialize a ScanKey entry given already-opened pg_operator relation.
  * ----------------
  */
 static void
@@ -498,6 +500,7 @@ OperatorRelationFillScanKeyEntry(Relation operatorRelation,
 			 operatorObjectId);
 	}
 
+	MemSet(entry, 0, sizeof(*entry));
 	entry->sk_flags = 0;
 	entry->sk_procedure = ((Form_pg_operator) GETSTRUCT(tuple))->oprcode;
 
@@ -511,14 +514,29 @@ OperatorRelationFillScanKeyEntry(Relation operatorRelation,
 		"OperatorRelationFillScanKeyEntry: no procedure for operator %u",
 			 operatorObjectId);
 
-	fmgr_info(entry->sk_procedure, &entry->sk_func);
-	entry->sk_nargs = entry->sk_func.fn_nargs;
+	/*
+	 * Formerly we initialized entry->sk_func here, but that's a waste of
+	 * time because ScanKey entries in strategy maps are never actually
+	 * used to invoke the operator.  Furthermore, to do that we'd have to
+	 * worry about setting the proper memory context (the map is probably
+	 * not allocated in the current memory context!)
+	 */
 }
 
 
 /*
  * IndexSupportInitialize
  *		Initializes an index strategy and associated support procedures.
+ *
+ * Data is returned into *indexStrategy, *indexSupport, and *isUnique,
+ * all of which are objects allocated by the caller.
+ *
+ * The primary input keys are indexObjectId and accessMethodObjectId.
+ * The caller also passes maxStrategyNumber, maxSupportNumber, and
+ * maxAttributeNumber, since these indicate the size of the indexStrategy
+ * and indexSupport arrays it has allocated --- but in practice these
+ * numbers must always match those obtainable from the system catalog
+ * entries for the index and access method.
  */
 void
 IndexSupportInitialize(IndexStrategy indexStrategy,
@@ -578,7 +596,7 @@ IndexSupportInitialize(IndexStrategy indexStrategy,
 		if (!OidIsValid(iform->indkey[attIndex]))
 		{
 			if (attIndex == InvalidAttrNumber)
-				elog(ERROR, "IndexSupportInitialize: no pg_index tuple");
+				elog(ERROR, "IndexSupportInitialize: bogus pg_index tuple");
 			break;
 		}
 
@@ -637,6 +655,7 @@ IndexSupportInitialize(IndexStrategy indexStrategy,
 		heap_close(relation, AccessShareLock);
 	}
 
+	/* Now load the strategy information for the index operators */
 	ScanKeyEntryInitialize(&entry[0], 0,
 						   Anum_pg_amop_amopid,
 						   F_OIDEQ,
@@ -644,7 +663,8 @@ IndexSupportInitialize(IndexStrategy indexStrategy,
 
 	ScanKeyEntryInitialize(&entry[1], 0,
 						   Anum_pg_amop_amopclaid,
-						   F_OIDEQ, 0);
+						   F_OIDEQ,
+						   0);	/* will fill below */
 
 	relation = heap_openr(AccessMethodOperatorRelationName, AccessShareLock);
 	operatorRelation = heap_openr(OperatorRelationName, AccessShareLock);
@@ -670,9 +690,11 @@ IndexSupportInitialize(IndexStrategy indexStrategy,
 			Form_pg_amop aform;
 
 			aform = (Form_pg_amop) GETSTRUCT(tuple);
+			strategy = aform->amopstrategy;
+			Assert(strategy > 0 && strategy <= maxStrategyNumber);
 			OperatorRelationFillScanKeyEntry(operatorRelation,
 											 aform->amopopr,
-				   StrategyMapGetScanKeyEntry(map, aform->amopstrategy));
+				   StrategyMapGetScanKeyEntry(map, strategy));
 		}
 
 		heap_endscan(scan);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index b6420a10e9a465307666e8b035f4856de8381689..9b98bd75e9ee1585581620257f09a00967370bed 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/catalog/index.c,v 1.152 2001/05/30 20:52:32 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/catalog/index.c,v 1.153 2001/06/01 02:41:35 tgl Exp $
  *
  *
  * INTERFACE ROUTINES
@@ -722,6 +722,9 @@ UpdateIndexPredicate(Oid indexoid, Node *oldPred, Node *predicate)
 
 /* ----------------------------------------------------------------
  *		InitIndexStrategy
+ *
+ * XXX this is essentially the same as relcache.c's
+ * IndexedAccessMethodInitialize(), and probably ought to be merged with it.
  * ----------------------------------------------------------------
  */
 void
@@ -733,18 +736,16 @@ InitIndexStrategy(int numatts,
 	RegProcedure *support;
 	uint16		amstrategies;
 	uint16		amsupport;
-	Oid			attrelid;
 	Size		strsize;
 
 	/*
 	 * get information from the index relation descriptor
 	 */
-	attrelid = indexRelation->rd_att->attrs[0]->attrelid;
 	amstrategies = indexRelation->rd_am->amstrategies;
 	amsupport = indexRelation->rd_am->amsupport;
 
 	/*
-	 * get the size of the strategy
+	 * compute the size of the strategy array
 	 */
 	strsize = AttributeNumberGetIndexStrategySize(numatts, amstrategies);
 
@@ -779,7 +780,8 @@ InitIndexStrategy(int numatts,
 
 	IndexSupportInitialize(strategy, support,
 						   &indexRelation->rd_uniqueindex,
-						   attrelid, accessMethodObjectId,
+						   RelationGetRelid(indexRelation),
+						   accessMethodObjectId,
 						   amstrategies, amsupport, numatts);
 
 	/*
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index db54ceede2f5a0e136577d598aaedfd56fb1aa5c..40ee84c0186687d0463921546de4f431e26a5996 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/catalog/pg_operator.c,v 1.58 2001/05/20 20:28:17 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/catalog/pg_operator.c,v 1.59 2001/06/01 02:41:35 tgl Exp $
  *
  * NOTES
  *	  these routines moved here from commands/define.c and somewhat cleaned up.
@@ -71,12 +71,13 @@ static void OperatorUpd(Oid baseId, Oid commId, Oid negId);
  *
  *		performs a scan on pg_operator for an operator tuple
  *		with given name and left/right type oids.
- * ----------------------------------------------------------------
+ *
  *	  pg_operator_desc	-- reldesc for pg_operator
  *	  operatorName		-- name of operator to fetch
  *	  leftObjectId		-- left data type oid of operator to fetch
  *	  rightObjectId		-- right data type oid of operator to fetch
  *	  defined			-- set TRUE if defined (not a shell)
+ * ----------------------------------------------------------------
  */
 static Oid
 OperatorGetWithOpenRelation(Relation pg_operator_desc,
@@ -88,26 +89,23 @@ OperatorGetWithOpenRelation(Relation pg_operator_desc,
 	HeapScanDesc pg_operator_scan;
 	Oid			operatorObjectId;
 	HeapTuple	tup;
-
-	static ScanKeyData opKey[3] = {
-		{0, Anum_pg_operator_oprname, F_NAMEEQ},
-		{0, Anum_pg_operator_oprleft, F_OIDEQ},
-		{0, Anum_pg_operator_oprright, F_OIDEQ},
-	};
-
-	fmgr_info(F_NAMEEQ, &opKey[0].sk_func);
-	fmgr_info(F_OIDEQ, &opKey[1].sk_func);
-	fmgr_info(F_OIDEQ, &opKey[2].sk_func);
-	opKey[0].sk_nargs = opKey[0].sk_func.fn_nargs;
-	opKey[1].sk_nargs = opKey[1].sk_func.fn_nargs;
-	opKey[2].sk_nargs = opKey[2].sk_func.fn_nargs;
+	ScanKeyData	opKey[3];
 
 	/*
 	 * form scan key
 	 */
-	opKey[0].sk_argument = PointerGetDatum(operatorName);
-	opKey[1].sk_argument = ObjectIdGetDatum(leftObjectId);
-	opKey[2].sk_argument = ObjectIdGetDatum(rightObjectId);
+	ScanKeyEntryInitialize(&opKey[0], 0x0,
+						   Anum_pg_operator_oprname,
+						   F_NAMEEQ,
+						   PointerGetDatum(operatorName));
+	ScanKeyEntryInitialize(&opKey[1], 0x0,
+						   Anum_pg_operator_oprleft,
+						   F_OIDEQ,
+						   ObjectIdGetDatum(leftObjectId));
+	ScanKeyEntryInitialize(&opKey[2], 0x0,
+						   Anum_pg_operator_oprright,
+						   F_OIDEQ,
+						   ObjectIdGetDatum(rightObjectId));
 
 	/*
 	 * begin the scan
@@ -451,7 +449,6 @@ OperatorDef(char *operatorName,
 	int			i,
 				j;
 	Relation	pg_operator_desc;
-
 	HeapScanDesc pg_operator_scan;
 	HeapTuple	tup;
 	char		nulls[Natts_pg_operator];
@@ -471,19 +468,7 @@ OperatorDef(char *operatorName,
 	int			nargs;
 	NameData	oname;
 	TupleDesc	tupDesc;
-
-	static ScanKeyData opKey[3] = {
-		{0, Anum_pg_operator_oprname, F_NAMEEQ},
-		{0, Anum_pg_operator_oprleft, F_OIDEQ},
-		{0, Anum_pg_operator_oprright, F_OIDEQ},
-	};
-
-	fmgr_info(F_NAMEEQ, &opKey[0].sk_func);
-	fmgr_info(F_OIDEQ, &opKey[1].sk_func);
-	fmgr_info(F_OIDEQ, &opKey[2].sk_func);
-	opKey[0].sk_nargs = opKey[0].sk_func.fn_nargs;
-	opKey[1].sk_nargs = opKey[1].sk_func.fn_nargs;
-	opKey[2].sk_nargs = opKey[2].sk_func.fn_nargs;
+	ScanKeyData	opKey[3];
 
 	operatorObjectId = OperatorGet(operatorName,
 								   leftTypeName,
@@ -753,13 +738,22 @@ OperatorDef(char *operatorName,
 	 */
 	if (operatorObjectId)
 	{
-		opKey[0].sk_argument = PointerGetDatum(operatorName);
-		opKey[1].sk_argument = ObjectIdGetDatum(leftTypeId);
-		opKey[2].sk_argument = ObjectIdGetDatum(rightTypeId);
-
 		/* Make sure we can see the shell even if it is new in current cmd */
 		CommandCounterIncrement();
 
+		ScanKeyEntryInitialize(&opKey[0], 0x0,
+							   Anum_pg_operator_oprname,
+							   F_NAMEEQ,
+							   PointerGetDatum(operatorName));
+		ScanKeyEntryInitialize(&opKey[1], 0x0,
+							   Anum_pg_operator_oprleft,
+							   F_OIDEQ,
+							   ObjectIdGetDatum(leftTypeId));
+		ScanKeyEntryInitialize(&opKey[2], 0x0,
+							   Anum_pg_operator_oprright,
+							   F_OIDEQ,
+							   ObjectIdGetDatum(rightTypeId));
+
 		pg_operator_scan = heap_beginscan(pg_operator_desc,
 										  0,
 										  SnapshotSelf, /* no cache? */
@@ -789,7 +783,6 @@ OperatorDef(char *operatorName,
 
 		heap_insert(pg_operator_desc, tup);
 		operatorObjectId = tup->t_data->t_oid;
-
 	}
 
 	if (RelationGetForm(pg_operator_desc)->relhasindex)
@@ -841,17 +834,11 @@ OperatorUpd(Oid baseId, Oid commId, Oid negId)
 	char		nulls[Natts_pg_operator];
 	char		replaces[Natts_pg_operator];
 	Datum		values[Natts_pg_operator];
-
-	static ScanKeyData opKey[1] = {
-		{0, ObjectIdAttributeNumber, F_OIDEQ},
-	};
-
-	fmgr_info(F_OIDEQ, &opKey[0].sk_func);
-	opKey[0].sk_nargs = opKey[0].sk_func.fn_nargs;
+	ScanKeyData	opKey[1];
 
 	for (i = 0; i < Natts_pg_operator; ++i)
 	{
-		values[i] = (Datum) NULL;
+		values[i] = (Datum) 0;
 		replaces[i] = ' ';
 		nulls[i] = ' ';
 	}
@@ -865,7 +852,10 @@ OperatorUpd(Oid baseId, Oid commId, Oid negId)
 	 */
 	CommandCounterIncrement();
 
-	opKey[0].sk_argument = ObjectIdGetDatum(commId);
+	ScanKeyEntryInitialize(&opKey[0], 0x0,
+						   ObjectIdAttributeNumber,
+						   F_OIDEQ,
+						   ObjectIdGetDatum(commId));
 
 	pg_operator_scan = heap_beginscan(pg_operator_desc,
 									  0,
@@ -993,7 +983,6 @@ OperatorUpd(Oid baseId, Oid commId, Oid negId)
 
 	heap_endscan(pg_operator_scan);
 
-
 	heap_close(pg_operator_desc, RowExclusiveLock);
 }
 
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index fbbade103310b8a2ee261e984758c3871dae11cd..a19f1a303bb0280e158c1699a70a5d719d8f99d7 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.137 2001/05/27 09:59:29 petere Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.138 2001/06/01 02:41:35 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -636,6 +636,7 @@ CopyFrom(Relation rel, bool binary, bool oids, FILE *fp,
 	resultRelInfo = makeNode(ResultRelInfo);
 	resultRelInfo->ri_RangeTableIndex = 1;		/* dummy */
 	resultRelInfo->ri_RelationDesc = rel;
+	resultRelInfo->ri_TrigDesc = rel->trigdesc;
 
 	ExecOpenIndices(resultRelInfo);
 
@@ -868,12 +869,12 @@ CopyFrom(Relation rel, bool binary, bool oids, FILE *fp,
 		skip_tuple = false;
 
 		/* BEFORE ROW INSERT Triggers */
-		if (rel->trigdesc &&
-			rel->trigdesc->n_before_row[TRIGGER_EVENT_INSERT] > 0)
+		if (resultRelInfo->ri_TrigDesc &&
+			resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_INSERT] > 0)
 		{
 			HeapTuple	newtuple;
 
-			newtuple = ExecBRInsertTriggers(estate, rel, tuple);
+			newtuple = ExecBRInsertTriggers(estate, resultRelInfo, tuple);
 
 			if (newtuple == NULL)		/* "do nothing" */
 				skip_tuple = true;
@@ -903,8 +904,8 @@ CopyFrom(Relation rel, bool binary, bool oids, FILE *fp,
 				ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
 
 			/* AFTER ROW INSERT Triggers */
-			if (rel->trigdesc)
-				ExecARInsertTriggers(estate, rel, tuple);
+			if (resultRelInfo->ri_TrigDesc)
+				ExecARInsertTriggers(estate, resultRelInfo, tuple);
 		}
 
 		for (i = 0; i < attr_count; i++)
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 70c146530f145a0362efa9121688b4130d5786b6..013221860a2a289cf38e09b9289e99c4247f1974 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
- *	  $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.91 2001/05/27 09:59:29 petere Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.92 2001/06/01 02:41:35 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -32,14 +32,19 @@
 #include "utils/syscache.h"
 
 
-static void DescribeTrigger(TriggerDesc *trigdesc, Trigger *trigger);
-static HeapTuple GetTupleForTrigger(EState *estate, ItemPointer tid,
-				   TupleTableSlot **newSlot);
-static HeapTuple ExecCallTriggerFunc(Trigger *trigger,
-					TriggerData *trigdata,
-					MemoryContext per_tuple_context);
-static void DeferredTriggerSaveEvent(Relation rel, int event,
-						 HeapTuple oldtup, HeapTuple newtup);
+static void InsertTrigger(TriggerDesc *trigdesc, Trigger *trigger, int indx);
+static HeapTuple GetTupleForTrigger(EState *estate,
+									ResultRelInfo *relinfo,
+									ItemPointer tid,
+									TupleTableSlot **newSlot);
+static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata,
+									 FmgrInfo *finfo,
+									 MemoryContext per_tuple_context);
+static void DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
+									 HeapTuple oldtup, HeapTuple newtup);
+static void DeferredTriggerExecute(DeferredTriggerEvent event, int itemno,
+								   Relation rel, FmgrInfo *finfo,
+								   MemoryContext per_tuple_context);
 
 
 void
@@ -577,8 +582,6 @@ RelationBuildTriggers(Relation relation)
 							 DatumGetCString(DirectFunctionCall1(nameout,
 									NameGetDatum(&pg_trigger->tgname))));
 		build->tgfoid = pg_trigger->tgfoid;
-		build->tgfunc.fn_oid = InvalidOid;		/* mark FmgrInfo as
-												 * uninitialized */
 		build->tgtype = pg_trigger->tgtype;
 		build->tgenabled = pg_trigger->tgenabled;
 		build->tgisconstraint = pg_trigger->tgisconstraint;
@@ -641,21 +644,22 @@ RelationBuildTriggers(Relation relation)
 	trigdesc->triggers = triggers;
 	trigdesc->numtriggers = ntrigs;
 	for (found = 0; found < ntrigs; found++)
-		DescribeTrigger(trigdesc, &(triggers[found]));
+		InsertTrigger(trigdesc, &(triggers[found]), found);
 
 	relation->trigdesc = trigdesc;
 }
 
+/* Insert the given trigger into the appropriate index list(s) for it */
 static void
-DescribeTrigger(TriggerDesc *trigdesc, Trigger *trigger)
+InsertTrigger(TriggerDesc *trigdesc, Trigger *trigger, int indx)
 {
 	uint16	   *n;
-	Trigger  ***t,
-			 ***tp;
+	int		  **t,
+			  **tp;
 
-	if (TRIGGER_FOR_ROW(trigger->tgtype))		/* Is ROW/STATEMENT
-												 * trigger */
+	if (TRIGGER_FOR_ROW(trigger->tgtype))
 	{
+		/* ROW trigger */
 		if (TRIGGER_FOR_BEFORE(trigger->tgtype))
 		{
 			n = trigdesc->n_before_row;
@@ -668,8 +672,8 @@ DescribeTrigger(TriggerDesc *trigdesc, Trigger *trigger)
 		}
 	}
 	else
-/* STATEMENT (NI) */
 	{
+		/* STATEMENT trigger */
 		if (TRIGGER_FOR_BEFORE(trigger->tgtype))
 		{
 			n = trigdesc->n_before_statement;
@@ -686,12 +690,12 @@ DescribeTrigger(TriggerDesc *trigdesc, Trigger *trigger)
 	{
 		tp = &(t[TRIGGER_EVENT_INSERT]);
 		if (*tp == NULL)
-			*tp = (Trigger **) MemoryContextAlloc(CacheMemoryContext,
-												  sizeof(Trigger *));
+			*tp = (int *) MemoryContextAlloc(CacheMemoryContext,
+											 sizeof(int));
 		else
-			*tp = (Trigger **) repalloc(*tp, (n[TRIGGER_EVENT_INSERT] + 1) *
-										sizeof(Trigger *));
-		(*tp)[n[TRIGGER_EVENT_INSERT]] = trigger;
+			*tp = (int *) repalloc(*tp, (n[TRIGGER_EVENT_INSERT] + 1) *
+								   sizeof(int));
+		(*tp)[n[TRIGGER_EVENT_INSERT]] = indx;
 		(n[TRIGGER_EVENT_INSERT])++;
 	}
 
@@ -699,12 +703,12 @@ DescribeTrigger(TriggerDesc *trigdesc, Trigger *trigger)
 	{
 		tp = &(t[TRIGGER_EVENT_DELETE]);
 		if (*tp == NULL)
-			*tp = (Trigger **) MemoryContextAlloc(CacheMemoryContext,
-												  sizeof(Trigger *));
+			*tp = (int *) MemoryContextAlloc(CacheMemoryContext,
+											 sizeof(int));
 		else
-			*tp = (Trigger **) repalloc(*tp, (n[TRIGGER_EVENT_DELETE] + 1) *
-										sizeof(Trigger *));
-		(*tp)[n[TRIGGER_EVENT_DELETE]] = trigger;
+			*tp = (int *) repalloc(*tp, (n[TRIGGER_EVENT_DELETE] + 1) *
+								   sizeof(int));
+		(*tp)[n[TRIGGER_EVENT_DELETE]] = indx;
 		(n[TRIGGER_EVENT_DELETE])++;
 	}
 
@@ -712,21 +716,20 @@ DescribeTrigger(TriggerDesc *trigdesc, Trigger *trigger)
 	{
 		tp = &(t[TRIGGER_EVENT_UPDATE]);
 		if (*tp == NULL)
-			*tp = (Trigger **) MemoryContextAlloc(CacheMemoryContext,
-												  sizeof(Trigger *));
+			*tp = (int *) MemoryContextAlloc(CacheMemoryContext,
+											 sizeof(int));
 		else
-			*tp = (Trigger **) repalloc(*tp, (n[TRIGGER_EVENT_UPDATE] + 1) *
-										sizeof(Trigger *));
-		(*tp)[n[TRIGGER_EVENT_UPDATE]] = trigger;
+			*tp = (int *) repalloc(*tp, (n[TRIGGER_EVENT_UPDATE] + 1) *
+								   sizeof(int));
+		(*tp)[n[TRIGGER_EVENT_UPDATE]] = indx;
 		(n[TRIGGER_EVENT_UPDATE])++;
 	}
-
 }
 
 void
 FreeTriggerDesc(TriggerDesc *trigdesc)
 {
-	Trigger  ***t;
+	int		  **t;
 	Trigger    *trigger;
 	int			i;
 
@@ -734,19 +737,19 @@ FreeTriggerDesc(TriggerDesc *trigdesc)
 		return;
 
 	t = trigdesc->tg_before_statement;
-	for (i = 0; i < 4; i++)
+	for (i = 0; i < TRIGGER_NUM_EVENT_CLASSES; i++)
 		if (t[i] != NULL)
 			pfree(t[i]);
 	t = trigdesc->tg_before_row;
-	for (i = 0; i < 4; i++)
+	for (i = 0; i < TRIGGER_NUM_EVENT_CLASSES; i++)
 		if (t[i] != NULL)
 			pfree(t[i]);
 	t = trigdesc->tg_after_row;
-	for (i = 0; i < 4; i++)
+	for (i = 0; i < TRIGGER_NUM_EVENT_CLASSES; i++)
 		if (t[i] != NULL)
 			pfree(t[i]);
 	t = trigdesc->tg_after_statement;
-	for (i = 0; i < 4; i++)
+	for (i = 0; i < TRIGGER_NUM_EVENT_CLASSES; i++)
 		if (t[i] != NULL)
 			pfree(t[i]);
 
@@ -806,7 +809,6 @@ equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2)
 				return false;
 			if (trig1->tgfoid != trig2->tgfoid)
 				return false;
-			/* need not examine tgfunc, if tgfoid matches */
 			if (trig1->tgtype != trig2->tgtype)
 				return false;
 			if (trig1->tgenabled != trig2->tgenabled)
@@ -832,9 +834,18 @@ equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2)
 	return true;
 }
 
+/*
+ * Call a trigger function.
+ *
+ *		trigdata: trigger descriptor.
+ *		finfo: possibly-cached call info for the function.
+ *		per_tuple_context: memory context to execute the function in.
+ *
+ * Returns the tuple (or NULL) as returned by the function.
+ */
 static HeapTuple
-ExecCallTriggerFunc(Trigger *trigger,
-					TriggerData *trigdata,
+ExecCallTriggerFunc(TriggerData *trigdata,
+					FmgrInfo *finfo,
 					MemoryContext per_tuple_context)
 {
 	FunctionCallInfoData fcinfo;
@@ -842,11 +853,13 @@ ExecCallTriggerFunc(Trigger *trigger,
 	MemoryContext oldContext;
 
 	/*
-	 * Fmgr lookup info is cached in the Trigger structure, so that we
-	 * need not repeat the lookup on every call.
+	 * We cache fmgr lookup info, to avoid making the lookup
+	 * again on each call.
 	 */
-	if (trigger->tgfunc.fn_oid == InvalidOid)
-		fmgr_info(trigger->tgfoid, &trigger->tgfunc);
+	if (finfo->fn_oid == InvalidOid)
+		fmgr_info(trigdata->tg_trigger->tgfoid, finfo);
+
+	Assert(finfo->fn_oid == trigdata->tg_trigger->tgfoid);
 
 	/*
 	 * Do the function evaluation in the per-tuple memory context, so that
@@ -861,7 +874,7 @@ ExecCallTriggerFunc(Trigger *trigger,
 	 */
 	MemSet(&fcinfo, 0, sizeof(fcinfo));
 
-	fcinfo.flinfo = &trigger->tgfunc;
+	fcinfo.flinfo = finfo;
 	fcinfo.context = (Node *) trigdata;
 
 	result = FunctionCallInvoke(&fcinfo);
@@ -880,26 +893,40 @@ ExecCallTriggerFunc(Trigger *trigger,
 }
 
 HeapTuple
-ExecBRInsertTriggers(EState *estate, Relation rel, HeapTuple trigtuple)
+ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
+					 HeapTuple trigtuple)
 {
-	int			ntrigs = rel->trigdesc->n_before_row[TRIGGER_EVENT_INSERT];
-	Trigger   **trigger = rel->trigdesc->tg_before_row[TRIGGER_EVENT_INSERT];
+	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+	int			ntrigs = trigdesc->n_before_row[TRIGGER_EVENT_INSERT];
+	int		   *tgindx = trigdesc->tg_before_row[TRIGGER_EVENT_INSERT];
 	HeapTuple	newtuple = trigtuple;
 	HeapTuple	oldtuple;
 	TriggerData LocTriggerData;
 	int			i;
 
+	/* Allocate cache space for fmgr lookup info, if not done yet */
+	if (relinfo->ri_TrigFunctions == NULL)
+	{
+		relinfo->ri_TrigFunctions = (FmgrInfo *)
+			palloc(trigdesc->numtriggers * sizeof(FmgrInfo));
+		MemSet(relinfo->ri_TrigFunctions, 0,
+			   trigdesc->numtriggers * sizeof(FmgrInfo));
+	}
+
 	LocTriggerData.type = T_TriggerData;
 	LocTriggerData.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW | TRIGGER_EVENT_BEFORE;
-	LocTriggerData.tg_relation = rel;
+	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
 	LocTriggerData.tg_newtuple = NULL;
 	for (i = 0; i < ntrigs; i++)
 	{
-		if (!trigger[i]->tgenabled)
+		Trigger *trigger = &trigdesc->triggers[tgindx[i]];
+
+		if (!trigger->tgenabled)
 			continue;
 		LocTriggerData.tg_trigtuple = oldtuple = newtuple;
-		LocTriggerData.tg_trigger = trigger[i];
-		newtuple = ExecCallTriggerFunc(trigger[i], &LocTriggerData,
+		LocTriggerData.tg_trigger = trigger;
+		newtuple = ExecCallTriggerFunc(&LocTriggerData,
+									   relinfo->ri_TrigFunctions + tgindx[i],
 									   GetPerTupleMemoryContext(estate));
 		if (oldtuple != newtuple && oldtuple != trigtuple)
 			heap_freetuple(oldtuple);
@@ -910,42 +937,59 @@ ExecBRInsertTriggers(EState *estate, Relation rel, HeapTuple trigtuple)
 }
 
 void
-ExecARInsertTriggers(EState *estate, Relation rel, HeapTuple trigtuple)
+ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
+					 HeapTuple trigtuple)
 {
+	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+
 	/* Must save info if there are any deferred triggers on this rel */
-	if (rel->trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0 ||
-		rel->trigdesc->n_after_row[TRIGGER_EVENT_UPDATE] > 0 ||
-		rel->trigdesc->n_after_row[TRIGGER_EVENT_DELETE] > 0)
-		DeferredTriggerSaveEvent(rel, TRIGGER_EVENT_INSERT, NULL, trigtuple);
+	if (trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0 ||
+		trigdesc->n_after_row[TRIGGER_EVENT_UPDATE] > 0 ||
+		trigdesc->n_after_row[TRIGGER_EVENT_DELETE] > 0)
+		DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
+								 NULL, trigtuple);
 }
 
 bool
-ExecBRDeleteTriggers(EState *estate, ItemPointer tupleid)
+ExecBRDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
+					 ItemPointer tupleid)
 {
-	Relation	rel = estate->es_result_relation_info->ri_RelationDesc;
-	int			ntrigs = rel->trigdesc->n_before_row[TRIGGER_EVENT_DELETE];
-	Trigger   **trigger = rel->trigdesc->tg_before_row[TRIGGER_EVENT_DELETE];
+	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+	int			ntrigs = trigdesc->n_before_row[TRIGGER_EVENT_DELETE];
+	int		   *tgindx = trigdesc->tg_before_row[TRIGGER_EVENT_DELETE];
 	TriggerData LocTriggerData;
 	HeapTuple	trigtuple;
 	HeapTuple	newtuple = NULL;
 	TupleTableSlot *newSlot;
 	int			i;
 
-	trigtuple = GetTupleForTrigger(estate, tupleid, &newSlot);
+	trigtuple = GetTupleForTrigger(estate, relinfo, tupleid, &newSlot);
 	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 *)
+			palloc(trigdesc->numtriggers * sizeof(FmgrInfo));
+		MemSet(relinfo->ri_TrigFunctions, 0,
+			   trigdesc->numtriggers * sizeof(FmgrInfo));
+	}
+
 	LocTriggerData.type = T_TriggerData;
 	LocTriggerData.tg_event = TRIGGER_EVENT_DELETE | TRIGGER_EVENT_ROW | TRIGGER_EVENT_BEFORE;
-	LocTriggerData.tg_relation = rel;
+	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
 	LocTriggerData.tg_newtuple = NULL;
 	for (i = 0; i < ntrigs; i++)
 	{
-		if (!trigger[i]->tgenabled)
+		Trigger *trigger = &trigdesc->triggers[tgindx[i]];
+
+		if (!trigger->tgenabled)
 			continue;
 		LocTriggerData.tg_trigtuple = trigtuple;
-		LocTriggerData.tg_trigger = trigger[i];
-		newtuple = ExecCallTriggerFunc(trigger[i], &LocTriggerData,
+		LocTriggerData.tg_trigger = trigger;
+		newtuple = ExecCallTriggerFunc(&LocTriggerData,
+									   relinfo->ri_TrigFunctions + tgindx[i],
 									   GetPerTupleMemoryContext(estate));
 		if (newtuple == NULL)
 			break;
@@ -958,27 +1002,31 @@ ExecBRDeleteTriggers(EState *estate, ItemPointer tupleid)
 }
 
 void
-ExecARDeleteTriggers(EState *estate, ItemPointer tupleid)
+ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
+					 ItemPointer tupleid)
 {
-	Relation	rel = estate->es_result_relation_info->ri_RelationDesc;
+	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 
 	/* Must save info if there are upd/del deferred triggers on this rel */
-	if (rel->trigdesc->n_after_row[TRIGGER_EVENT_UPDATE] > 0 ||
-		rel->trigdesc->n_after_row[TRIGGER_EVENT_DELETE] > 0)
+	if (trigdesc->n_after_row[TRIGGER_EVENT_UPDATE] > 0 ||
+		trigdesc->n_after_row[TRIGGER_EVENT_DELETE] > 0)
 	{
-		HeapTuple	trigtuple = GetTupleForTrigger(estate, tupleid, NULL);
+		HeapTuple	trigtuple = GetTupleForTrigger(estate, relinfo,
+												   tupleid, NULL);
 
-		DeferredTriggerSaveEvent(rel, TRIGGER_EVENT_DELETE, trigtuple, NULL);
+		DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
+								 trigtuple, NULL);
 		heap_freetuple(trigtuple);
 	}
 }
 
 HeapTuple
-ExecBRUpdateTriggers(EState *estate, ItemPointer tupleid, HeapTuple newtuple)
+ExecBRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
+					 ItemPointer tupleid, HeapTuple newtuple)
 {
-	Relation	rel = estate->es_result_relation_info->ri_RelationDesc;
-	int			ntrigs = rel->trigdesc->n_before_row[TRIGGER_EVENT_UPDATE];
-	Trigger   **trigger = rel->trigdesc->tg_before_row[TRIGGER_EVENT_UPDATE];
+	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+	int			ntrigs = trigdesc->n_before_row[TRIGGER_EVENT_UPDATE];
+	int		   *tgindx = trigdesc->tg_before_row[TRIGGER_EVENT_UPDATE];
 	TriggerData LocTriggerData;
 	HeapTuple	trigtuple;
 	HeapTuple	oldtuple;
@@ -986,7 +1034,7 @@ ExecBRUpdateTriggers(EState *estate, ItemPointer tupleid, HeapTuple newtuple)
 	TupleTableSlot *newSlot;
 	int			i;
 
-	trigtuple = GetTupleForTrigger(estate, tupleid, &newSlot);
+	trigtuple = GetTupleForTrigger(estate, relinfo, tupleid, &newSlot);
 	if (trigtuple == NULL)
 		return NULL;
 
@@ -997,17 +1045,29 @@ ExecBRUpdateTriggers(EState *estate, ItemPointer tupleid, HeapTuple newtuple)
 	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 *)
+			palloc(trigdesc->numtriggers * sizeof(FmgrInfo));
+		MemSet(relinfo->ri_TrigFunctions, 0,
+			   trigdesc->numtriggers * sizeof(FmgrInfo));
+	}
+
 	LocTriggerData.type = T_TriggerData;
 	LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE | TRIGGER_EVENT_ROW | TRIGGER_EVENT_BEFORE;
-	LocTriggerData.tg_relation = rel;
+	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
 	for (i = 0; i < ntrigs; i++)
 	{
-		if (!trigger[i]->tgenabled)
+		Trigger *trigger = &trigdesc->triggers[tgindx[i]];
+
+		if (!trigger->tgenabled)
 			continue;
 		LocTriggerData.tg_trigtuple = trigtuple;
 		LocTriggerData.tg_newtuple = oldtuple = newtuple;
-		LocTriggerData.tg_trigger = trigger[i];
-		newtuple = ExecCallTriggerFunc(trigger[i], &LocTriggerData,
+		LocTriggerData.tg_trigger = trigger;
+		newtuple = ExecCallTriggerFunc(&LocTriggerData,
+									   relinfo->ri_TrigFunctions + tgindx[i],
 									   GetPerTupleMemoryContext(estate));
 		if (oldtuple != newtuple && oldtuple != intuple)
 			heap_freetuple(oldtuple);
@@ -1019,26 +1079,30 @@ ExecBRUpdateTriggers(EState *estate, ItemPointer tupleid, HeapTuple newtuple)
 }
 
 void
-ExecARUpdateTriggers(EState *estate, ItemPointer tupleid, HeapTuple newtuple)
+ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
+					 ItemPointer tupleid, HeapTuple newtuple)
 {
-	Relation	rel = estate->es_result_relation_info->ri_RelationDesc;
+	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 
 	/* Must save info if there are upd/del deferred triggers on this rel */
-	if (rel->trigdesc->n_after_row[TRIGGER_EVENT_UPDATE] > 0 ||
-		rel->trigdesc->n_after_row[TRIGGER_EVENT_DELETE] > 0)
+	if (trigdesc->n_after_row[TRIGGER_EVENT_UPDATE] > 0 ||
+		trigdesc->n_after_row[TRIGGER_EVENT_DELETE] > 0)
 	{
-		HeapTuple	trigtuple = GetTupleForTrigger(estate, tupleid, NULL);
+		HeapTuple	trigtuple = GetTupleForTrigger(estate, relinfo,
+												   tupleid, NULL);
 
-		DeferredTriggerSaveEvent(rel, TRIGGER_EVENT_UPDATE, trigtuple, newtuple);
+		DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
+								 trigtuple, newtuple);
 		heap_freetuple(trigtuple);
 	}
 }
 
 
 static HeapTuple
-GetTupleForTrigger(EState *estate, ItemPointer tid, TupleTableSlot **newSlot)
+GetTupleForTrigger(EState *estate, ResultRelInfo *relinfo,
+				   ItemPointer tid, TupleTableSlot **newSlot)
 {
-	Relation	relation = estate->es_result_relation_info->ri_RelationDesc;
+	Relation	relation = relinfo->ri_RelationDesc;
 	HeapTupleData tuple;
 	HeapTuple	result;
 	Buffer		buffer;
@@ -1070,8 +1134,8 @@ ltrmark:;
 				else if (!(ItemPointerEquals(&(tuple.t_self), tid)))
 				{
 					TupleTableSlot *epqslot = EvalPlanQual(estate,
-					 estate->es_result_relation_info->ri_RangeTableIndex,
-														&(tuple.t_self));
+														   relinfo->ri_RangeTableIndex,
+														   &(tuple.t_self));
 
 					if (!(TupIsNull(epqslot)))
 					{
@@ -1293,35 +1357,46 @@ deferredTriggerGetPreviousEvent(Oid relid, ItemPointer ctid)
 
 
 /* ----------
- * deferredTriggerExecute()
+ * DeferredTriggerExecute()
  *
  *	Fetch the required tuples back from the heap and fire one
  *	single trigger function.
+ *
+ *	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.
+ *
+ *	event: event currently being fired.
+ *	itemno: item within event currently being fired.
+ *	rel: open relation for event.
+ *	finfo: array of fmgr lookup cache entries (one per trigger of relation).
+ *	per_tuple_context: memory context to call trigger function in.
  * ----------
  */
 static void
-deferredTriggerExecute(DeferredTriggerEvent event, int itemno,
+DeferredTriggerExecute(DeferredTriggerEvent event, int itemno,
+					   Relation rel, FmgrInfo *finfo,
 					   MemoryContext per_tuple_context)
 {
-	Relation	rel;
+	Oid			tgoid = event->dte_item[itemno].dti_tgoid;
+	TriggerDesc *trigdesc = rel->trigdesc;
 	TriggerData LocTriggerData;
 	HeapTupleData oldtuple;
 	HeapTupleData newtuple;
 	HeapTuple	rettuple;
 	Buffer		oldbuffer;
 	Buffer		newbuffer;
+	int			tgindx;
 
 	/*
-	 * Open the heap and fetch the required OLD and NEW tuples.
+	 * Fetch the required OLD and NEW tuples.
 	 */
-	rel = heap_open(event->dte_relid, NoLock);
-
 	if (ItemPointerIsValid(&(event->dte_oldctid)))
 	{
 		ItemPointerCopy(&(event->dte_oldctid), &(oldtuple.t_self));
 		heap_fetch(rel, SnapshotAny, &oldtuple, &oldbuffer);
 		if (!oldtuple.t_data)
-			elog(ERROR, "deferredTriggerExecute: failed to fetch old tuple");
+			elog(ERROR, "DeferredTriggerExecute: failed to fetch old tuple");
 	}
 
 	if (ItemPointerIsValid(&(event->dte_newctid)))
@@ -1329,7 +1404,7 @@ deferredTriggerExecute(DeferredTriggerEvent event, int itemno,
 		ItemPointerCopy(&(event->dte_newctid), &(newtuple.t_self));
 		heap_fetch(rel, SnapshotAny, &newtuple, &newbuffer);
 		if (!newtuple.t_data)
-			elog(ERROR, "deferredTriggerExecute: failed to fetch new tuple");
+			elog(ERROR, "DeferredTriggerExecute: failed to fetch new tuple");
 	}
 
 	/*
@@ -1340,27 +1415,33 @@ deferredTriggerExecute(DeferredTriggerEvent event, int itemno,
 		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, "DeferredTriggerExecute: can't find trigger %u", tgoid);
+
 	switch (event->dte_event & TRIGGER_EVENT_OPMASK)
 	{
 		case TRIGGER_EVENT_INSERT:
 			LocTriggerData.tg_trigtuple = &newtuple;
 			LocTriggerData.tg_newtuple = NULL;
-			LocTriggerData.tg_trigger =
-				rel->trigdesc->tg_after_row[TRIGGER_EVENT_INSERT][itemno];
 			break;
 
 		case TRIGGER_EVENT_UPDATE:
 			LocTriggerData.tg_trigtuple = &oldtuple;
 			LocTriggerData.tg_newtuple = &newtuple;
-			LocTriggerData.tg_trigger =
-				rel->trigdesc->tg_after_row[TRIGGER_EVENT_UPDATE][itemno];
 			break;
 
 		case TRIGGER_EVENT_DELETE:
 			LocTriggerData.tg_trigtuple = &oldtuple;
 			LocTriggerData.tg_newtuple = NULL;
-			LocTriggerData.tg_trigger =
-				rel->trigdesc->tg_after_row[TRIGGER_EVENT_DELETE][itemno];
 			break;
 	}
 
@@ -1368,8 +1449,8 @@ deferredTriggerExecute(DeferredTriggerEvent event, int itemno,
 	 * Call the trigger and throw away an eventually returned updated
 	 * tuple.
 	 */
-	rettuple = ExecCallTriggerFunc(LocTriggerData.tg_trigger,
-								   &LocTriggerData,
+	rettuple = ExecCallTriggerFunc(&LocTriggerData,
+								   finfo + tgindx,
 								   per_tuple_context);
 	if (rettuple != NULL && rettuple != &oldtuple && rettuple != &newtuple)
 		heap_freetuple(rettuple);
@@ -1381,14 +1462,12 @@ deferredTriggerExecute(DeferredTriggerEvent event, int itemno,
 	ReferentialIntegritySnapshotOverride = false;
 
 	/*
-	 * Release buffers and close the relation
+	 * Release buffers
 	 */
 	if (ItemPointerIsValid(&(event->dte_oldctid)))
 		ReleaseBuffer(oldbuffer);
 	if (ItemPointerIsValid(&(event->dte_newctid)))
 		ReleaseBuffer(newbuffer);
-
-	heap_close(rel, NoLock);
 }
 
 
@@ -1403,9 +1482,9 @@ static void
 deferredTriggerInvokeEvents(bool immediate_only)
 {
 	DeferredTriggerEvent event;
-	int			still_deferred_ones;
-	int			i;
 	MemoryContext per_tuple_context;
+	Relation	rel = NULL;
+	FmgrInfo   *finfo = NULL;
 
 	/*
 	 * For now we process all events - to speedup transaction blocks we
@@ -1426,6 +1505,8 @@ deferredTriggerInvokeEvents(bool immediate_only)
 
 	for (event = deftrig_events; event != NULL; event = event->dte_next)
 	{
+		bool		still_deferred_ones;
+		int			i;
 
 		/*
 		 * Check if event is completely done.
@@ -1458,9 +1539,32 @@ deferredTriggerInvokeEvents(bool immediate_only)
 			}
 
 			/*
-			 * So let's fire it...
+			 * So let's fire it... but first, open the correct relation
+			 * if this is not the same relation as before.
 			 */
-			deferredTriggerExecute(event, i, per_tuple_context);
+			if (rel == NULL || rel->rd_id != event->dte_relid)
+			{
+				if (rel)
+					heap_close(rel, NoLock);
+				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->dte_relid, NoLock);
+				/*
+				 * Allocate space to cache fmgr lookup info for triggers
+				 * of this relation.
+				 */
+				finfo = (FmgrInfo *)
+					palloc(rel->trigdesc->numtriggers * sizeof(FmgrInfo));
+				MemSet(finfo, 0,
+					   rel->trigdesc->numtriggers * sizeof(FmgrInfo));
+			}
+
+			DeferredTriggerExecute(event, i, rel, finfo, per_tuple_context);
+
 			event->dte_item[i].dti_state |= TRIGGER_DEFERRED_DONE;
 		}
 
@@ -1471,6 +1575,10 @@ deferredTriggerInvokeEvents(bool immediate_only)
 			event->dte_event |= TRIGGER_DEFERRED_DONE;
 	}
 
+	if (rel)
+		heap_close(rel, NoLock);
+	if (finfo)
+		pfree(finfo);
 	MemoryContextDelete(per_tuple_context);
 }
 
@@ -1892,16 +2000,18 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
  * ----------
  */
 static void
-DeferredTriggerSaveEvent(Relation rel, int event,
+DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
 						 HeapTuple oldtup, HeapTuple newtup)
 {
+	Relation	rel = relinfo->ri_RelationDesc;
+	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 	MemoryContext oldcxt;
 	DeferredTriggerEvent new_event;
 	DeferredTriggerEvent prev_event;
 	int			new_size;
 	int			i;
 	int			ntriggers;
-	Trigger   **triggers;
+	int		   *tgindx;
 	ItemPointerData oldctid;
 	ItemPointerData newctid;
 	TriggerData LocTriggerData;
@@ -1927,8 +2037,8 @@ DeferredTriggerSaveEvent(Relation rel, int event,
 	 */
 	oldcxt = MemoryContextSwitchTo(deftrig_cxt);
 
-	ntriggers = rel->trigdesc->n_after_row[event];
-	triggers = rel->trigdesc->tg_after_row[event];
+	ntriggers = trigdesc->n_after_row[event];
+	tgindx = trigdesc->tg_after_row[event];
 	new_size = offsetof(DeferredTriggerEventData, dte_item[0]) +
 		ntriggers * sizeof(DeferredTriggerEventItem);
 
@@ -1941,13 +2051,15 @@ DeferredTriggerSaveEvent(Relation rel, int event,
 	new_event->dte_n_items = ntriggers;
 	for (i = 0; i < ntriggers; i++)
 	{
-		new_event->dte_item[i].dti_tgoid = triggers[i]->tgoid;
+		Trigger *trigger = &trigdesc->triggers[tgindx[i]];
+
+		new_event->dte_item[i].dti_tgoid = trigger->tgoid;
 		new_event->dte_item[i].dti_state =
-			((triggers[i]->tgdeferrable) ?
+			((trigger->tgdeferrable) ?
 			 TRIGGER_DEFERRED_DEFERRABLE : 0) |
-			((triggers[i]->tginitdeferred) ?
+			((trigger->tginitdeferred) ?
 			 TRIGGER_DEFERRED_INITDEFERRED : 0) |
-			((rel->trigdesc->n_before_row[event] > 0) ?
+			((trigdesc->n_before_row[event] > 0) ?
 			 TRIGGER_DEFERRED_HAS_BEFORE : 0);
 	}
 
@@ -1978,13 +2090,14 @@ DeferredTriggerSaveEvent(Relation rel, int event,
 			 */
 			for (i = 0; i < ntriggers; i++)
 			{
+				Trigger	   *trigger = &trigdesc->triggers[tgindx[i]];
 				bool		is_ri_trigger;
 				bool		key_unchanged;
 
 				/*
 				 * We are interested in RI_FKEY triggers only.
 				 */
-				switch (triggers[i]->tgfoid)
+				switch (trigger->tgfoid)
 				{
 					case F_RI_FKEY_NOACTION_UPD:
 					case F_RI_FKEY_CASCADE_UPD:
@@ -2006,7 +2119,7 @@ DeferredTriggerSaveEvent(Relation rel, int event,
 				LocTriggerData.tg_relation = rel;
 				LocTriggerData.tg_trigtuple = oldtup;
 				LocTriggerData.tg_newtuple = newtup;
-				LocTriggerData.tg_trigger = triggers[i];
+				LocTriggerData.tg_trigger = trigger;
 
 				key_unchanged = RI_FKey_keyequal_upd(&LocTriggerData);
 
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 3ba9b9136f5607371954e62053fa9558bdfef525..38c34f03be72c952fa24e7e541be2735da6b2a9a 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -27,7 +27,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.142 2001/05/27 20:48:51 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.143 2001/06/01 02:41:35 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -830,6 +830,8 @@ initResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_NumIndices = 0;
 	resultRelInfo->ri_IndexRelationDescs = NULL;
 	resultRelInfo->ri_IndexRelationInfo = NULL;
+	resultRelInfo->ri_TrigDesc = resultRelationDesc->trigdesc;
+	resultRelInfo->ri_TrigFunctions = NULL;
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 
@@ -1232,12 +1234,12 @@ ExecAppend(TupleTableSlot *slot,
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/* BEFORE ROW INSERT Triggers */
-	if (resultRelationDesc->trigdesc &&
-	resultRelationDesc->trigdesc->n_before_row[TRIGGER_EVENT_INSERT] > 0)
+	if (resultRelInfo->ri_TrigDesc &&
+		resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_INSERT] > 0)
 	{
 		HeapTuple	newtuple;
 
-		newtuple = ExecBRInsertTriggers(estate, resultRelationDesc, tuple);
+		newtuple = ExecBRInsertTriggers(estate, resultRelInfo, tuple);
 
 		if (newtuple == NULL)	/* "do nothing" */
 			return;
@@ -1283,8 +1285,8 @@ ExecAppend(TupleTableSlot *slot,
 		ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
 
 	/* AFTER ROW INSERT Triggers */
-	if (resultRelationDesc->trigdesc)
-		ExecARInsertTriggers(estate, resultRelationDesc, tuple);
+	if (resultRelInfo->ri_TrigDesc)
+		ExecARInsertTriggers(estate, resultRelInfo, tuple);
 }
 
 /* ----------------------------------------------------------------
@@ -1311,12 +1313,12 @@ ExecDelete(TupleTableSlot *slot,
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/* BEFORE ROW DELETE Triggers */
-	if (resultRelationDesc->trigdesc &&
-	resultRelationDesc->trigdesc->n_before_row[TRIGGER_EVENT_DELETE] > 0)
+	if (resultRelInfo->ri_TrigDesc &&
+		resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_DELETE] > 0)
 	{
 		bool		dodelete;
 
-		dodelete = ExecBRDeleteTriggers(estate, tupleid);
+		dodelete = ExecBRDeleteTriggers(estate, resultRelInfo, tupleid);
 
 		if (!dodelete)			/* "do nothing" */
 			return;
@@ -1370,8 +1372,8 @@ ldelete:;
 	 */
 
 	/* AFTER ROW DELETE Triggers */
-	if (resultRelationDesc->trigdesc)
-		ExecARDeleteTriggers(estate, tupleid);
+	if (resultRelInfo->ri_TrigDesc)
+		ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
 }
 
 /* ----------------------------------------------------------------
@@ -1418,12 +1420,13 @@ ExecReplace(TupleTableSlot *slot,
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/* BEFORE ROW UPDATE Triggers */
-	if (resultRelationDesc->trigdesc &&
-	resultRelationDesc->trigdesc->n_before_row[TRIGGER_EVENT_UPDATE] > 0)
+	if (resultRelInfo->ri_TrigDesc &&
+		resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_UPDATE] > 0)
 	{
 		HeapTuple	newtuple;
 
-		newtuple = ExecBRUpdateTriggers(estate, tupleid, tuple);
+		newtuple = ExecBRUpdateTriggers(estate, resultRelInfo,
+										tupleid, tuple);
 
 		if (newtuple == NULL)	/* "do nothing" */
 			return;
@@ -1519,8 +1522,8 @@ lreplace:;
 		ExecInsertIndexTuples(slot, &(tuple->t_self), estate, true);
 
 	/* AFTER ROW UPDATE Triggers */
-	if (resultRelationDesc->trigdesc)
-		ExecARUpdateTriggers(estate, tupleid, tuple);
+	if (resultRelInfo->ri_TrigDesc)
+		ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple);
 }
 
 static char *
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index f843f2bb166d0fd7acaa6027e53d99b5ba9662d9..cd54ff766570ef0541caf5b55791a7a9a8ef76d7 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/utils/cache/catcache.c,v 1.77 2001/03/22 03:59:55 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/cache/catcache.c,v 1.78 2001/06/01 02:41:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -205,7 +205,6 @@ CatalogCacheInitializeCache(CatCache *cache)
 	/*
 	 * switch to the cache context so our allocations do not vanish at the
 	 * end of a transaction
-	 *
 	 */
 	if (!CacheMemoryContext)
 		CreateCacheMemoryContext();
@@ -214,13 +213,11 @@ CatalogCacheInitializeCache(CatCache *cache)
 
 	/*
 	 * copy the relcache's tuple descriptor to permanent cache storage
-	 *
 	 */
 	tupdesc = CreateTupleDescCopyConstr(RelationGetDescr(relation));
 
 	/*
 	 * return to the caller's memory context and close the rel
-	 *
 	 */
 	MemoryContextSwitchTo(oldcxt);
 
@@ -231,7 +228,6 @@ CatalogCacheInitializeCache(CatCache *cache)
 
 	/*
 	 * initialize cache's key information
-	 *
 	 */
 	for (i = 0; i < cache->cc_nkeys; ++i)
 	{
@@ -255,9 +251,23 @@ CatalogCacheInitializeCache(CatCache *cache)
 		 */
 		cache->cc_skey[i].sk_procedure = EQPROC(keytype);
 
+		/*
+		 * Note: to avoid any possible leakage of scan temporary data into
+		 * the cache context, we do not switch into CacheMemoryContext while
+		 * calling fmgr_info here.  Instead set fn_mcxt on return.  This
+		 * would fail to work correctly if fmgr_info allocated any subsidiary
+		 * data structures to attach to the FmgrInfo record; but it doesn't
+		 * do so for built-in functions, and all the comparator functions
+		 * for system caches should most assuredly be built-in functions.
+		 * Currently there's no real need to fix fn_mcxt either, but let's do
+		 * that anyway just to make sure it's not pointing to a dead context
+		 * later on.
+		 */
+
 		fmgr_info(cache->cc_skey[i].sk_procedure,
 				  &cache->cc_skey[i].sk_func);
-		cache->cc_skey[i].sk_nargs = cache->cc_skey[i].sk_func.fn_nargs;
+
+		cache->cc_skey[i].sk_func.fn_mcxt = CacheMemoryContext;
 
 		/* Initialize sk_attno suitably for HeapKeyTest() and heap scans */
 		cache->cc_skey[i].sk_attno = cache->cc_key[i];
@@ -270,7 +280,6 @@ CatalogCacheInitializeCache(CatCache *cache)
 
 	/*
 	 * mark this cache fully initialized
-	 *
 	 */
 	cache->cc_tupdesc = tupdesc;
 }
@@ -705,7 +714,6 @@ InitCatCache(int id,
  *		certain system indexes that support critical syscaches.
  *		We can't use an indexscan to fetch these, else we'll get into
  *		infinite recursion.  A plain heap scan will work, however.
- *
  */
 static bool
 IndexScanOK(CatCache *cache, ScanKey cur_skey)
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index d04e0cd55920a7d37e7c83360caf7a84bea549ba..6431bd9382e7bd6480e0af733dd0dde57fc97838 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/utils/cache/relcache.c,v 1.135 2001/05/30 14:15:26 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/cache/relcache.c,v 1.136 2001/06/01 02:41:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1161,19 +1161,20 @@ IndexedAccessMethodInitialize(Relation relation)
 	int			natts;
 	Size		stratSize;
 	Size		supportSize;
-	uint16		relamstrategies;
-	uint16		relamsupport;
+	uint16		amstrategies;
+	uint16		amsupport;
 
 	natts = relation->rd_rel->relnatts;
-	relamstrategies = relation->rd_am->amstrategies;
-	stratSize = AttributeNumberGetIndexStrategySize(natts, relamstrategies);
+	amstrategies = relation->rd_am->amstrategies;
+	amsupport = relation->rd_am->amsupport;
+
+	stratSize = AttributeNumberGetIndexStrategySize(natts, amstrategies);
 	strategy = (IndexStrategy) MemoryContextAlloc(CacheMemoryContext,
 												  stratSize);
 
-	relamsupport = relation->rd_am->amsupport;
-	if (relamsupport > 0)
+	if (amsupport > 0)
 	{
-		supportSize = natts * (relamsupport * sizeof(RegProcedure));
+		supportSize = natts * (amsupport * sizeof(RegProcedure));
 		support = (RegProcedure *) MemoryContextAlloc(CacheMemoryContext,
 													  supportSize);
 	}
@@ -1182,9 +1183,9 @@ IndexedAccessMethodInitialize(Relation relation)
 
 	IndexSupportInitialize(strategy, support,
 						   &relation->rd_uniqueindex,
-						   relation->rd_att->attrs[0]->attrelid,
+						   RelationGetRelid(relation),
 						   relation->rd_rel->relam,
-						   relamstrategies, relamsupport, natts);
+						   amstrategies, amsupport, natts);
 
 	RelationSetIndexSupport(relation, strategy, support);
 }
@@ -1212,26 +1213,22 @@ formrdesc(char *relationName,
 
 	/*
 	 * allocate new relation desc
-	 *
 	 */
 	relation = (Relation) palloc(sizeof(RelationData));
 	MemSet((char *) relation, 0, sizeof(RelationData));
 
 	/*
 	 * don't open the unix file yet..
-	 *
 	 */
 	relation->rd_fd = -1;
 
 	/*
 	 * initialize reference count
-	 *
 	 */
 	RelationSetReferenceCount(relation, 1);
 
 	/*
 	 * all entries built with this routine are nailed-in-cache
-	 *
 	 */
 	relation->rd_isnailed = true;
 
@@ -1241,7 +1238,6 @@ formrdesc(char *relationName,
 	 * The data we insert here is pretty incomplete/bogus, but it'll serve to
 	 * get us launched.  RelationCacheInitializePhase2() will read the
 	 * real data from pg_class and replace what we've done here.
-	 *
 	 */
 	relation->rd_rel = (Form_pg_class) palloc(CLASS_TUPLE_SIZE);
 	MemSet(relation->rd_rel, 0, CLASS_TUPLE_SIZE);
@@ -1266,13 +1262,11 @@ formrdesc(char *relationName,
 
 	/*
 	 * initialize attribute tuple form
-	 *
 	 */
 	relation->rd_att = CreateTemplateTupleDesc(natts);
 
 	/*
 	 * initialize tuple desc info
-	 *
 	 */
 	for (i = 0; i < natts; i++)
 	{
@@ -1283,14 +1277,12 @@ formrdesc(char *relationName,
 	}
 
 	/*
-	 * initialize relation id
-	 *
+	 * initialize relation id from info in att array (my, this is ugly)
 	 */
 	RelationGetRelid(relation) = relation->rd_att->attrs[0]->attrelid;
 
 	/*
 	 * initialize the relation's lock manager and RelFileNode information
-	 *
 	 */
 	RelationInitLockInfo(relation);		/* see lmgr.c */
 
@@ -1303,7 +1295,6 @@ formrdesc(char *relationName,
 
 	/*
 	 * initialize the rel-has-index flag, using hardwired knowledge
-	 *
 	 */
 	relation->rd_rel->relhasindex = false;
 
@@ -1322,7 +1313,6 @@ formrdesc(char *relationName,
 
 	/*
 	 * add new reldesc to relcache
-	 *
 	 */
 	RelationCacheInsert(relation);
 }
@@ -2755,10 +2745,8 @@ init_irels(void)
 		{
 			fmgr_info(SMD(i).sk_procedure,
 					  &(SMD(i).sk_func));
-			SMD(i).sk_nargs = SMD(i).sk_func.fn_nargs;
 		}
 
-
 		/*
 		 * use a real field called rd_istrat instead of the bogosity of
 		 * hanging invisible fields off the end of a structure - jolly
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 090807129f50c49c26398b65b77d36f3b17b1dab..544dc840c196fb069100c06726056175cf204c92 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/utils/fmgr/fmgr.c,v 1.52 2001/05/19 09:28:08 petere Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/fmgr/fmgr.c,v 1.53 2001/06/01 02:41:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -114,6 +114,15 @@ fmgr_lookupByName(const char *name)
 /*
  * This routine fills a FmgrInfo struct, given the OID
  * of the function to be called.
+ *
+ * The caller's CurrentMemoryContext is used as the fn_mcxt of the info
+ * struct; this means that any subsidiary data attached to the info struct
+ * (either by fmgr_info itself, or later on by a function call handler)
+ * will be allocated in that context.  The caller must ensure that this
+ * context is at least as long-lived as the info struct itself.  This is
+ * not a problem in typical cases where the info struct is on the stack or
+ * in freshly-palloc'd space, but one must take extra care when the info
+ * struct is in a long-lived table.
  */
 void
 fmgr_info(Oid functionId, FmgrInfo *finfo)
@@ -124,8 +133,9 @@ fmgr_info(Oid functionId, FmgrInfo *finfo)
 	char	   *prosrc;
 
 	/*
-	 * fn_oid *must* be filled in last.  Code may assume that is fn_oid is valid,
-	 * the whole struct is valid.  Some FmgrInfo struct's do survive elogs.
+	 * fn_oid *must* be filled in last.  Some code assumes that if fn_oid is
+	 * valid, the whole struct is valid.  Some FmgrInfo struct's do survive
+	 * elogs.
 	 */
 	finfo->fn_oid = InvalidOid;
 	finfo->fn_extra = NULL;
@@ -133,10 +143,8 @@ fmgr_info(Oid functionId, FmgrInfo *finfo)
 
 	if ((fbp = fmgr_isbuiltin(functionId)) != NULL)
 	{
-
 		/*
-		 * Fast path for builtin functions: don't bother consulting
-		 * pg_proc
+		 * Fast path for builtin functions: don't bother consulting pg_proc
 		 */
 		finfo->fn_nargs = fbp->nargs;
 		finfo->fn_strict = fbp->strict;
@@ -171,7 +179,6 @@ fmgr_info(Oid functionId, FmgrInfo *finfo)
 	switch (procedureStruct->prolang)
 	{
 		case INTERNALlanguageId:
-
 			/*
 			 * For an ordinary builtin function, we should never get here
 			 * because the isbuiltin() search above will have succeeded.
diff --git a/src/include/access/skey.h b/src/include/access/skey.h
index 0cb9dc5d68b3de0dda8acdcd93565d517f36d5ae..2a00e26744e4caf611835f435fbe7abe2f7134b5 100644
--- a/src/include/access/skey.h
+++ b/src/include/access/skey.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: skey.h,v 1.14 2001/01/24 19:43:19 momjian Exp $
+ * $Id: skey.h,v 1.15 2001/06/01 02:41:36 tgl Exp $
  *
  *
  * Note:
@@ -25,18 +25,18 @@ typedef struct ScanKeyData
 	bits16		sk_flags;		/* flags */
 	AttrNumber	sk_attno;		/* domain number */
 	RegProcedure sk_procedure;	/* procedure OID */
-	FmgrInfo	sk_func;
-	int32		sk_nargs;
+	FmgrInfo	sk_func;		/* fmgr call info for procedure */
 	Datum		sk_argument;	/* data to compare */
 } ScanKeyData;
 
 typedef ScanKeyData *ScanKey;
 
+/* ScanKeyData flags */
+#define SK_ISNULL		0x1		/* sk_argument is NULL */
+#define SK_UNARY		0x2		/* unary function (currently unsupported) */
+#define SK_NEGATE		0x4		/* negate function result */
+#define SK_COMMUTE		0x8		/* commute function (not fully supported) */
 
-#define SK_ISNULL		0x1
-#define SK_UNARY		0x2
-#define SK_NEGATE		0x4
-#define SK_COMMUTE		0x8
 
 #define ScanUnmarked			0x01
 #define ScanUncheckedPrevious	0x02
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index 46c4a497646d3e087863839bc5c089a0d0c98fdb..ddb9a327db0942b6d9e77c71131a377990ee0e9e 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: trigger.h,v 1.26 2001/03/22 04:00:43 momjian Exp $
+ * $Id: trigger.h,v 1.27 2001/06/01 02:41:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -90,15 +90,25 @@ extern void FreeTriggerDesc(TriggerDesc *trigdesc);
 extern bool equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2);
 
 extern HeapTuple ExecBRInsertTriggers(EState *estate,
-					 Relation rel, HeapTuple tuple);
+									  ResultRelInfo *relinfo,
+									  HeapTuple trigtuple);
 extern void ExecARInsertTriggers(EState *estate,
-					 Relation rel, HeapTuple tuple);
-extern bool ExecBRDeleteTriggers(EState *estate, ItemPointer tupleid);
-extern void ExecARDeleteTriggers(EState *estate, ItemPointer tupleid);
-extern HeapTuple ExecBRUpdateTriggers(EState *estate, ItemPointer tupleid,
-					 HeapTuple tuple);
-extern void ExecARUpdateTriggers(EState *estate, ItemPointer tupleid,
-					 HeapTuple tuple);
+								 ResultRelInfo *relinfo,
+								 HeapTuple trigtuple);
+extern bool ExecBRDeleteTriggers(EState *estate,
+								 ResultRelInfo *relinfo,
+								 ItemPointer tupleid);
+extern void ExecARDeleteTriggers(EState *estate,
+								 ResultRelInfo *relinfo,
+								 ItemPointer tupleid);
+extern HeapTuple ExecBRUpdateTriggers(EState *estate,
+									  ResultRelInfo *relinfo,
+									  ItemPointer tupleid,
+									  HeapTuple newtuple);
+extern void ExecARUpdateTriggers(EState *estate,
+								 ResultRelInfo *relinfo,
+								 ItemPointer tupleid,
+								 HeapTuple newtuple);
 
 
 /* ----------
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 18903369c1756236dd2fe29e1872e91d29fed4e4..1dc68c192fee13882693a02bdac43ee545237c5b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: execnodes.h,v 1.60 2001/05/27 20:48:51 tgl Exp $
+ * $Id: execnodes.h,v 1.61 2001/06/01 02:41:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -198,16 +198,18 @@ typedef struct JunkFilter
 /* ----------------
  *	  ResultRelInfo information
  *
- *		whenever we update an existing relation, we have to
- *		update indices on the relation.  The ResultRelInfo class
- *		is used to hold all the information on result relations,
- *		including indices.. -cim 10/15/89
+ *		Whenever we update an existing relation, we have to
+ *		update indices on the relation, and perhaps also fire triggers.
+ *		The ResultRelInfo class is used to hold all the information needed
+ *		about a result relation, including indices.. -cim 10/15/89
  *
  *		RangeTableIndex			result relation's range table index
  *		RelationDesc			relation descriptor for result relation
  *		NumIndices				# of indices existing on result relation
  *		IndexRelationDescs		array of relation descriptors for indices
  *		IndexRelationInfo		array of key/attr info for indices
+ *		TrigDesc				triggers to be fired, if any
+ *		TrigFunctions			cached lookup info for trigger functions
  *		ConstraintExprs			array of constraint-checking expressions
  *		junkFilter				for removing junk attributes from tuples
  * ----------------
@@ -220,6 +222,8 @@ typedef struct ResultRelInfo
 	int			ri_NumIndices;
 	RelationPtr ri_IndexRelationDescs;
 	IndexInfo **ri_IndexRelationInfo;
+	TriggerDesc *ri_TrigDesc;
+	FmgrInfo   *ri_TrigFunctions;
 	List	  **ri_ConstraintExprs;
 	JunkFilter *ri_junkFilter;
 } ResultRelInfo;
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 5853be716d18d6fb4354f6784e2dc0a54598a047..f1574b3ffd535cfadf1c13024ae656192888e702 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: rel.h,v 1.45 2001/03/22 04:01:14 momjian Exp $
+ * $Id: rel.h,v 1.46 2001/06/01 02:41:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -47,13 +47,11 @@ typedef LockInfoData *LockInfo;
  * Likewise, this struct really belongs to trigger.h, but for convenience
  * we put it here.
  */
-
 typedef struct Trigger
 {
 	Oid			tgoid;
 	char	   *tgname;
 	Oid			tgfoid;
-	FmgrInfo	tgfunc;
 	int16		tgtype;
 	bool		tgenabled;
 	bool		tgisconstraint;
@@ -66,16 +64,23 @@ typedef struct Trigger
 
 typedef struct TriggerDesc
 {
-	/* index data to identify which triggers are which */
-	uint16		n_before_statement[4];
-	uint16		n_before_row[4];
-	uint16		n_after_row[4];
-	uint16		n_after_statement[4];
-	Trigger   **tg_before_statement[4];
-	Trigger   **tg_before_row[4];
-	Trigger   **tg_after_row[4];
-	Trigger   **tg_after_statement[4];
-	/* the actual array of triggers is here */
+	/*
+	 * Index data to identify which triggers are which.  Since each trigger
+	 * can appear in more than one class, for each class we provide a list
+	 * of integer indexes into the triggers array.
+	 */
+#define TRIGGER_NUM_EVENT_CLASSES  4
+
+	uint16		n_before_statement[TRIGGER_NUM_EVENT_CLASSES];
+	uint16		n_before_row[TRIGGER_NUM_EVENT_CLASSES];
+	uint16		n_after_row[TRIGGER_NUM_EVENT_CLASSES];
+	uint16		n_after_statement[TRIGGER_NUM_EVENT_CLASSES];
+	int		   *tg_before_statement[TRIGGER_NUM_EVENT_CLASSES];
+	int		   *tg_before_row[TRIGGER_NUM_EVENT_CLASSES];
+	int		   *tg_after_row[TRIGGER_NUM_EVENT_CLASSES];
+	int		   *tg_after_statement[TRIGGER_NUM_EVENT_CLASSES];
+
+	/* The actual array of triggers is here */
 	Trigger    *triggers;
 	int			numtriggers;
 } TriggerDesc;