diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index daa5b8e7175ae2f363dfc716a21d33a25e2188a6..b0196c90deeb45db1b23a2cee8dee722e6c1e641 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -1054,20 +1054,6 @@ deferredTriggerGetPreviousEvent(Oid relid, ItemPointer ctid)
 }
 
 
-/* ----------
- * deferredTriggerCancelEvent()
- *
- *	Mark an event in the eventlist as cancelled because it isn't
- *	required anymore (replaced by anoter event).
- * ----------
- */
-static void
-deferredTriggerCancelEvent(DeferredTriggerEvent event)
-{
-	event->dte_event |= TRIGGER_DEFERRED_CANCELED;
-}
-
-
 /* ----------
  * deferredTriggerExecute()
  *
@@ -1112,10 +1098,11 @@ deferredTriggerExecute(DeferredTriggerEvent event, int itemno)
 	 * Setup the trigger information
 	 * ----------
 	 */
-	SaveTriggerData.tg_event    = event->dte_event | TRIGGER_EVENT_ROW;
+	SaveTriggerData.tg_event    = (event->dte_event & TRIGGER_EVENT_OPMASK) |
+														TRIGGER_EVENT_ROW;
 	SaveTriggerData.tg_relation = rel;
 
-	switch (event->dte_event)
+	switch (event->dte_event & TRIGGER_EVENT_OPMASK)
 	{
 		case TRIGGER_EVENT_INSERT:
 			SaveTriggerData.tg_trigtuple = &newtuple;
@@ -1656,13 +1643,13 @@ DeferredTriggerSaveEvent(Relation rel, int event,
 	MemoryContext			oldcxt;
 	DeferredTriggerEvent	new_event;
 	DeferredTriggerEvent	prev_event;
-	bool					prev_done = false;
 	int						new_size;
 	int						i;
 	int						ntriggers;
 	Trigger				  **triggers;
 	ItemPointerData			oldctid;
 	ItemPointerData			newctid;
+	TriggerData				SaveTriggerData;
 
 	if (deftrig_cxt == NULL)
 		elog(ERROR, 
@@ -1694,203 +1681,212 @@ DeferredTriggerSaveEvent(Relation rel, int event,
 		ItemPointerSetInvalid(&(newctid));
 
 	/* ----------
-	 * Eventually modify the event and do some general RI violation checks
+	 * Create a new event 
 	 * ----------
 	 */
-	switch (event)
+	oldcxt = MemoryContextSwitchTo((MemoryContext) deftrig_cxt);
+
+	ntriggers = rel->trigdesc->n_after_row[event];
+	triggers  = rel->trigdesc->tg_after_row[event];
+
+	new_size  = sizeof(DeferredTriggerEventData) +
+				ntriggers * sizeof(DeferredTriggerEventItem);
+				
+	new_event = (DeferredTriggerEvent) palloc(new_size);
+	new_event->dte_event	= event & TRIGGER_EVENT_OPMASK;
+	new_event->dte_relid	= rel->rd_id;
+	ItemPointerCopy(&oldctid, &(new_event->dte_oldctid));
+	ItemPointerCopy(&newctid, &(new_event->dte_newctid));
+	new_event->dte_n_items = ntriggers;
+	new_event->dte_item[ntriggers].dti_state = new_size;
+	for (i = 0; i < ntriggers; i++)
+	{
+		new_event->dte_item[i].dti_tgoid = triggers[i]->tgoid;
+		new_event->dte_item[i].dti_state = 
+			((triggers[i]->tgdeferrable) ? 
+						TRIGGER_DEFERRED_DEFERRABLE : 0)	|
+			((triggers[i]->tginitdeferred) ? 
+						TRIGGER_DEFERRED_INITDEFERRED : 0)	|
+			((rel->trigdesc->n_before_row[event] > 0) ?
+						TRIGGER_DEFERRED_HAS_BEFORE : 0);
+	}
+	MemoryContextSwitchTo(oldcxt);
+
+	switch (event & TRIGGER_EVENT_OPMASK)
 	{
 		case TRIGGER_EVENT_INSERT:
-			/* ----------
-			 * Don't know how to (surely) check if another tuple with
-			 * this meaning (from all FK's point of view) got deleted
-			 * in the same transaction. Thus not handled yet.
-			 * ----------
-			 */
+			new_event->dte_event |= TRIGGER_DEFERRED_ROW_INSERTED;
+			new_event->dte_event |= TRIGGER_DEFERRED_KEY_CHANGED;
 			break;
 
 		case TRIGGER_EVENT_UPDATE:
 			/* ----------
-			 * On UPDATE check if the tuple updated is a result
-			 * of the same transaction.
+			 * On UPDATE check if the tuple updated has been inserted
+			 * or a foreign referenced key value that's changing now
+			 * has been updated once before in this transaction.
 			 * ----------
 			 */
 			if (oldtup->t_data->t_xmin != GetCurrentTransactionId())
-				break;
+				prev_event = NULL;
+			else
+				prev_event = 
+					deferredTriggerGetPreviousEvent(rel->rd_id, &oldctid);
 
 			/* ----------
-			 * Look at the previous event to the same tuple if
-			 * any of its triggers has already been executed.
-			 * Such a situation would potentially violate RI
-			 * so we abort the transaction.
+			 * Now check if one of the referenced keys is changed.
 			 * ----------
 			 */
-			prev_event = deferredTriggerGetPreviousEvent(rel->rd_id, &oldctid);
-			if (prev_event->dte_event & TRIGGER_DEFERRED_HAS_BEFORE	||
-					(prev_event->dte_n_items != 0 &&
-					 prev_event->dte_event & TRIGGER_DEFERRED_DONE))
-				prev_done = true;
-			else
-				for (i = 0; i < prev_event->dte_n_items; i++)
+			for (i = 0; i < ntriggers; i++)
+			{
+				bool	is_ri_trigger;
+				bool	key_unchanged;
+
+				/* ----------
+				 * We are interested in RI_FKEY triggers only.
+				 * ----------
+				 */
+				switch (triggers[i]->tgfoid)
 				{
-					if (prev_event->dte_item[i].dti_state &
-									TRIGGER_DEFERRED_DONE)
-					{
-						prev_done = true;
+					case F_RI_FKEY_NOACTION_UPD:
+						is_ri_trigger = true;
+						new_event->dte_item[i].dti_state |=
+											TRIGGER_DEFERRED_DONE;
 						break;
-					}
-				}
 
-			if (prev_done)
-			{
-				elog(NOTICE, "UPDATE of row inserted/updated in same "
-						"transaction violates");
-				elog(NOTICE, "referential integrity semantics. Other "
-						"triggers or IMMEDIATE ");
-				elog(ERROR, " constraints have already been executed.");
-			}
+					case F_RI_FKEY_CASCADE_UPD:
+					case F_RI_FKEY_RESTRICT_UPD:
+					case F_RI_FKEY_SETNULL_UPD:
+					case F_RI_FKEY_SETDEFAULT_UPD:
+						is_ri_trigger = true;
+						break;
+					
+					default:
+						is_ri_trigger = false;
+						break;
+				}
+				if (!is_ri_trigger)
+					continue;
+
+				SaveTriggerData.tg_event     = TRIGGER_EVENT_UPDATE;
+				SaveTriggerData.tg_relation  = rel;
+				SaveTriggerData.tg_trigtuple = oldtup;
+				SaveTriggerData.tg_newtuple  = newtup;
+				SaveTriggerData.tg_trigger   = triggers[i];
+
+				CurrentTriggerData = &SaveTriggerData;
+				key_unchanged = RI_FKey_keyequal_upd();
+				CurrentTriggerData = NULL;
+				
+				if (key_unchanged)
+				{
+					/* ----------
+					 * The key hasn't changed, so no need later to invoke
+					 * the trigger at all. But remember other states from
+					 * the possible earlier event.
+					 * ----------
+					 */
+					new_event->dte_item[i].dti_state |= TRIGGER_DEFERRED_DONE;
 
-			/* ----------
-			 * Anything's fine so far - i.e. none of the previous
-			 * events triggers has been executed up to now. Let's
-			 * the REAL event that happened so far.
-			 * ----------
-			 */
-			switch (prev_event->dte_event & TRIGGER_EVENT_OPMASK)
-			{
-				case TRIGGER_EVENT_INSERT:
+					if (prev_event)
+					{
+						if (prev_event->dte_event & 
+									TRIGGER_DEFERRED_ROW_INSERTED)
+						{
+							/* ----------
+							 * This is a row inserted during our transaction.
+							 * So any key value is considered changed.
+							 * ----------
+							 */
+							new_event->dte_event |= 
+											TRIGGER_DEFERRED_ROW_INSERTED;
+							new_event->dte_event |= 
+											TRIGGER_DEFERRED_KEY_CHANGED;
+							new_event->dte_item[i].dti_state |=
+											TRIGGER_DEFERRED_KEY_CHANGED;
+						}
+						else
+						{
+							/* ----------
+							 * This is a row, previously updated. So
+							 * if this key has been changed before, we
+							 * still remember that it happened.
+							 * ----------
+							 */
+							if (prev_event->dte_item[i].dti_state &
+											TRIGGER_DEFERRED_KEY_CHANGED)
+							{
+								new_event->dte_item[i].dti_state |=
+											TRIGGER_DEFERRED_KEY_CHANGED;
+								new_event->dte_event |=
+											TRIGGER_DEFERRED_KEY_CHANGED;
+							}
+						}
+					}
+				}
+				else
+				{
 					/* ----------
-					 * The previous operation was an insert.
-					 * So the REAL new event is an INSERT of
-					 * the new tuple.
+					 * Bomb out if this key has been changed before.
+					 * Otherwise remember that we do so.
 					 * ----------
 					 */
-					event = TRIGGER_EVENT_INSERT;
-					ItemPointerSetInvalid(&oldctid);
-					deferredTriggerCancelEvent(prev_event);
-					break;
+					if (prev_event)
+					{
+						if (prev_event->dte_event &
+											TRIGGER_DEFERRED_ROW_INSERTED)
+							elog(ERROR, "triggered data change violation "
+									"on relation \"%s\"",
+									nameout(&(rel->rd_rel->relname)));
+
+						if (prev_event->dte_item[i].dti_state &
+											TRIGGER_DEFERRED_KEY_CHANGED)
+							elog(ERROR, "triggered data change violation "
+									"on relation \"%s\"",
+									nameout(&(rel->rd_rel->relname)));
+					}
 
-				case TRIGGER_EVENT_UPDATE:
 					/* ----------
-					 * The previous operation was an UPDATE.
-					 * So the REAL new event is still an UPDATE
-					 * but from the original tuple to this new one.
+					 * This is the first change to this key, so let
+					 * it happen.
 					 * ----------
 					 */
-					event = TRIGGER_EVENT_UPDATE;
-					ItemPointerCopy(&(prev_event->dte_oldctid), &oldctid);
-					deferredTriggerCancelEvent(prev_event);
-					break;
+					new_event->dte_item[i].dti_state |=
+											TRIGGER_DEFERRED_KEY_CHANGED;
+					new_event->dte_event |= TRIGGER_DEFERRED_KEY_CHANGED;
+				}
 			}
 
 			break;
 
 		case TRIGGER_EVENT_DELETE:
 			/* ----------
-			 * On DELETE check if the tuple updated is a result
-			 * of the same transaction.
+			 * On DELETE check if the tuple deleted has been inserted
+			 * or a possibly referenced key value has changed in this
+			 * transaction.
 			 * ----------
 			 */
 			if (oldtup->t_data->t_xmin != GetCurrentTransactionId())
 				break;
 
 			/* ----------
-			 * Look at the previous event to the same tuple if
-			 * any of its triggers has already been executed.
-			 * Such a situation would potentially violate RI
-			 * so we abort the transaction.
+			 * Look at the previous event to the same tuple.
 			 * ----------
 			 */
 			prev_event = deferredTriggerGetPreviousEvent(rel->rd_id, &oldctid);
-			if (prev_event->dte_event & TRIGGER_DEFERRED_HAS_BEFORE	||
-					(prev_event->dte_n_items != 0 &&
-					 prev_event->dte_event & TRIGGER_DEFERRED_DONE))
-				prev_done = true;
-			else
-				for (i = 0; i < prev_event->dte_n_items; i++)
-				{
-					if (prev_event->dte_item[i].dti_state &
-									TRIGGER_DEFERRED_DONE)
-					{
-						prev_done = true;
-						break;
-					}
-				}
-
-			if (prev_done)
-			{
-				elog(NOTICE, "DELETE of row inserted/updated in same "
-						"transaction violates");
-				elog(NOTICE, "referential integrity semantics. Other "
-						"triggers or IMMEDIATE ");
-				elog(ERROR, " constraints have already been executed.");
-			}
-
-			/* ----------
-			 * Anything's fine so far - i.e. none of the previous
-			 * events triggers has been executed up to now. Let's
-			 * the REAL event that happened so far.
-			 * ----------
-			 */
-			switch (prev_event->dte_event & TRIGGER_EVENT_OPMASK)
-			{
-				case TRIGGER_EVENT_INSERT:
-					/* ----------
-					 * The previous operation was an insert.
-					 * So the REAL new event is nothing.
-					 * ----------
-					 */
-					deferredTriggerCancelEvent(prev_event);
-					return;
-
-				case TRIGGER_EVENT_UPDATE:
-					/* ----------
-					 * The previous operation was an UPDATE.
-					 * So the REAL new event is a DELETE
-					 * but from the original tuple.
-					 * ----------
-					 */
-					event = TRIGGER_EVENT_DELETE;
-					ItemPointerCopy(&(prev_event->dte_oldctid), &oldctid);
-					deferredTriggerCancelEvent(prev_event);
-					break;
-			}
+			if (prev_event->dte_event & TRIGGER_DEFERRED_KEY_CHANGED)
+				elog(ERROR, "triggered data change violation "
+						"on relation \"%s\"",
+						nameout(&(rel->rd_rel->relname)));
 
 			break;
 	}
-	
+
 	/* ----------
-	 * Create a new event and save it.
+	 * Anything's fine up to here. Add the new event to the queue.
 	 * ----------
 	 */
 	oldcxt = MemoryContextSwitchTo((MemoryContext) deftrig_cxt);
-
-	ntriggers = rel->trigdesc->n_after_row[event];
-	triggers  = rel->trigdesc->tg_after_row[event];
-
-	new_size  = sizeof(DeferredTriggerEventData) +
-				ntriggers * sizeof(DeferredTriggerEventItem);
-				
-	new_event = (DeferredTriggerEvent) palloc(new_size);
-	new_event->dte_event	= event;
-	new_event->dte_relid	= rel->rd_id;
-	ItemPointerCopy(&oldctid, &(new_event->dte_oldctid));
-	ItemPointerCopy(&newctid, &(new_event->dte_newctid));
-	new_event->dte_n_items = ntriggers;
-	new_event->dte_item[ntriggers].dti_state = new_size;
-	for (i = 0; i < ntriggers; i++)
-	{
-		new_event->dte_item[i].dti_tgoid = triggers[i]->tgoid;
-		new_event->dte_item[i].dti_state = 
-			((triggers[i]->tgdeferrable) ? 
-						TRIGGER_DEFERRED_DEFERRABLE : 0)	|
-			((triggers[i]->tginitdeferred) ? 
-						TRIGGER_DEFERRED_INITDEFERRED : 0)	|
-			((rel->trigdesc->n_before_row[event] > 0) ?
-						TRIGGER_DEFERRED_HAS_BEFORE : 0);
-	}
-
 	deferredTriggerAddEvent(new_event);
-
 	MemoryContextSwitchTo(oldcxt);
 
 	return;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 2162bbff315c77b8121e422b38567be9b54be411..afcbcce3ed919fd5f8c9e61e24d78925997edc49 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -5,7 +5,7 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- *	$Id: analyze.c,v 1.126 1999/12/10 07:37:35 tgl Exp $
+ *	$Id: analyze.c,v 1.127 2000/01/06 20:46:49 wieck Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1015,141 +1015,141 @@ transformCreateStmt(ParseState *pstate, CreateStmt *stmt)
 
 			extras_after = lappend(extras_after, (Node *)fk_trigger);
 
-			if ((fkconstraint->actions & FKCONSTR_ON_DELETE_MASK) != 0)
+			/*
+			 * Build a CREATE CONSTRAINT TRIGGER statement for the 
+			 * ON DELETE action fired on the PK table !!!
+			 *
+			 */
+			fk_trigger = (CreateTrigStmt *)makeNode(CreateTrigStmt);
+			fk_trigger->trigname		= fkconstraint->constr_name;
+			fk_trigger->relname			= fkconstraint->pktable_name;
+			switch ((fkconstraint->actions & FKCONSTR_ON_DELETE_MASK)
+							>> FKCONSTR_ON_DELETE_SHIFT)
 			{
-				/*
-				 * Build a CREATE CONSTRAINT TRIGGER statement for the 
-				 * ON DELETE action fired on the PK table !!!
-				 *
-				 */
-				fk_trigger = (CreateTrigStmt *)makeNode(CreateTrigStmt);
-				fk_trigger->trigname		= fkconstraint->constr_name;
-				fk_trigger->relname			= fkconstraint->pktable_name;
-				switch ((fkconstraint->actions & FKCONSTR_ON_DELETE_MASK)
-								>> FKCONSTR_ON_DELETE_SHIFT)
-				{
-					case FKCONSTR_ON_KEY_RESTRICT:
-						fk_trigger->funcname = "RI_FKey_restrict_del";
-						break;
-					case FKCONSTR_ON_KEY_CASCADE:
-						fk_trigger->funcname = "RI_FKey_cascade_del";
-						break;
-					case FKCONSTR_ON_KEY_SETNULL:
-						fk_trigger->funcname = "RI_FKey_setnull_del";
-						break;
-					case FKCONSTR_ON_KEY_SETDEFAULT:
-						fk_trigger->funcname = "RI_FKey_setdefault_del";
-						break;
-					default:
-						elog(ERROR, "Only one ON DELETE action can be specified for FOREIGN KEY constraint");
-						break;
-				}
-				fk_trigger->before			= false;
-				fk_trigger->row				= true;
-				fk_trigger->actions[0]		= 'd';
-				fk_trigger->actions[1]		= '\0';
-				fk_trigger->lang			= NULL;
-				fk_trigger->text			= NULL;
-				fk_trigger->attr			= NIL;
-				fk_trigger->when			= NULL;
-				fk_trigger->isconstraint	= true;
-				fk_trigger->deferrable		= fkconstraint->deferrable;
-				fk_trigger->initdeferred	= fkconstraint->initdeferred;
-				fk_trigger->constrrelname	= stmt->relname;
-
-				fk_trigger->args		= NIL;
-				fk_trigger->args = lappend(fk_trigger->args,
-											fkconstraint->constr_name);
-				fk_trigger->args = lappend(fk_trigger->args,
-											stmt->relname);
-				fk_trigger->args = lappend(fk_trigger->args,
-											fkconstraint->pktable_name);
-				fk_trigger->args = lappend(fk_trigger->args,
-											fkconstraint->match_type);
-				fk_attr = fkconstraint->fk_attrs;
-				pk_attr = fkconstraint->pk_attrs;
-				while (fk_attr != NIL)
-				{
-					id = (Ident *)lfirst(fk_attr);
-					fk_trigger->args = lappend(fk_trigger->args, id->name);
+				case FKCONSTR_ON_KEY_NOACTION:
+					fk_trigger->funcname = "RI_FKey_noaction_del";
+					break;
+				case FKCONSTR_ON_KEY_RESTRICT:
+					fk_trigger->funcname = "RI_FKey_restrict_del";
+					break;
+				case FKCONSTR_ON_KEY_CASCADE:
+					fk_trigger->funcname = "RI_FKey_cascade_del";
+					break;
+				case FKCONSTR_ON_KEY_SETNULL:
+					fk_trigger->funcname = "RI_FKey_setnull_del";
+					break;
+				case FKCONSTR_ON_KEY_SETDEFAULT:
+					fk_trigger->funcname = "RI_FKey_setdefault_del";
+					break;
+				default:
+					elog(ERROR, "Only one ON DELETE action can be specified for FOREIGN KEY constraint");
+					break;
+			}
+			fk_trigger->before			= false;
+			fk_trigger->row				= true;
+			fk_trigger->actions[0]		= 'd';
+			fk_trigger->actions[1]		= '\0';
+			fk_trigger->lang			= NULL;
+			fk_trigger->text			= NULL;
+			fk_trigger->attr			= NIL;
+			fk_trigger->when			= NULL;
+			fk_trigger->isconstraint	= true;
+			fk_trigger->deferrable		= fkconstraint->deferrable;
+			fk_trigger->initdeferred	= fkconstraint->initdeferred;
+			fk_trigger->constrrelname	= stmt->relname;
 
-					id = (Ident *)lfirst(pk_attr);
-					fk_trigger->args = lappend(fk_trigger->args, id->name);
+			fk_trigger->args		= NIL;
+			fk_trigger->args = lappend(fk_trigger->args,
+										fkconstraint->constr_name);
+			fk_trigger->args = lappend(fk_trigger->args,
+										stmt->relname);
+			fk_trigger->args = lappend(fk_trigger->args,
+										fkconstraint->pktable_name);
+			fk_trigger->args = lappend(fk_trigger->args,
+										fkconstraint->match_type);
+			fk_attr = fkconstraint->fk_attrs;
+			pk_attr = fkconstraint->pk_attrs;
+			while (fk_attr != NIL)
+			{
+				id = (Ident *)lfirst(fk_attr);
+				fk_trigger->args = lappend(fk_trigger->args, id->name);
 
-					fk_attr = lnext(fk_attr);
-					pk_attr = lnext(pk_attr);
-				}
+				id = (Ident *)lfirst(pk_attr);
+				fk_trigger->args = lappend(fk_trigger->args, id->name);
 
-				extras_after = lappend(extras_after, (Node *)fk_trigger);
+				fk_attr = lnext(fk_attr);
+				pk_attr = lnext(pk_attr);
 			}
 
-			if ((fkconstraint->actions & FKCONSTR_ON_UPDATE_MASK) != 0)
+			extras_after = lappend(extras_after, (Node *)fk_trigger);
+
+			/*
+			 * Build a CREATE CONSTRAINT TRIGGER statement for the 
+			 * ON UPDATE action fired on the PK table !!!
+			 *
+			 */
+			fk_trigger = (CreateTrigStmt *)makeNode(CreateTrigStmt);
+			fk_trigger->trigname		= fkconstraint->constr_name;
+			fk_trigger->relname			= fkconstraint->pktable_name;
+			switch ((fkconstraint->actions & FKCONSTR_ON_UPDATE_MASK)
+							>> FKCONSTR_ON_UPDATE_SHIFT)
 			{
-				/*
-				 * Build a CREATE CONSTRAINT TRIGGER statement for the 
-				 * ON UPDATE action fired on the PK table !!!
-				 *
-				 */
-				fk_trigger = (CreateTrigStmt *)makeNode(CreateTrigStmt);
-				fk_trigger->trigname		= fkconstraint->constr_name;
-				fk_trigger->relname			= fkconstraint->pktable_name;
-				switch ((fkconstraint->actions & FKCONSTR_ON_UPDATE_MASK)
-								>> FKCONSTR_ON_UPDATE_SHIFT)
-				{
-					case FKCONSTR_ON_KEY_RESTRICT:
-						fk_trigger->funcname = "RI_FKey_restrict_upd";
-						break;
-					case FKCONSTR_ON_KEY_CASCADE:
-						fk_trigger->funcname = "RI_FKey_cascade_upd";
-						break;
-					case FKCONSTR_ON_KEY_SETNULL:
-						fk_trigger->funcname = "RI_FKey_setnull_upd";
-						break;
-					case FKCONSTR_ON_KEY_SETDEFAULT:
-						fk_trigger->funcname = "RI_FKey_setdefault_upd";
-						break;
-					default:
-						elog(ERROR, "Only one ON UPDATE action can be specified for FOREIGN KEY constraint");
-						break;
-				}
-				fk_trigger->before			= false;
-				fk_trigger->row				= true;
-				fk_trigger->actions[0]		= 'u';
-				fk_trigger->actions[1]		= '\0';
-				fk_trigger->lang			= NULL;
-				fk_trigger->text			= NULL;
-				fk_trigger->attr			= NIL;
-				fk_trigger->when			= NULL;
-				fk_trigger->isconstraint	= true;
-				fk_trigger->deferrable		= fkconstraint->deferrable;
-				fk_trigger->initdeferred	= fkconstraint->initdeferred;
-				fk_trigger->constrrelname	= stmt->relname;
-
-				fk_trigger->args		= NIL;
-				fk_trigger->args = lappend(fk_trigger->args,
-											fkconstraint->constr_name);
-				fk_trigger->args = lappend(fk_trigger->args,
-											stmt->relname);
-				fk_trigger->args = lappend(fk_trigger->args,
-											fkconstraint->pktable_name);
-				fk_trigger->args = lappend(fk_trigger->args,
-											fkconstraint->match_type);
-				fk_attr = fkconstraint->fk_attrs;
-				pk_attr = fkconstraint->pk_attrs;
-				while (fk_attr != NIL)
-				{
-					id = (Ident *)lfirst(fk_attr);
-					fk_trigger->args = lappend(fk_trigger->args, id->name);
+				case FKCONSTR_ON_KEY_NOACTION:
+					fk_trigger->funcname = "RI_FKey_noaction_upd";
+					break;
+				case FKCONSTR_ON_KEY_RESTRICT:
+					fk_trigger->funcname = "RI_FKey_restrict_upd";
+					break;
+				case FKCONSTR_ON_KEY_CASCADE:
+					fk_trigger->funcname = "RI_FKey_cascade_upd";
+					break;
+				case FKCONSTR_ON_KEY_SETNULL:
+					fk_trigger->funcname = "RI_FKey_setnull_upd";
+					break;
+				case FKCONSTR_ON_KEY_SETDEFAULT:
+					fk_trigger->funcname = "RI_FKey_setdefault_upd";
+					break;
+				default:
+					elog(ERROR, "Only one ON UPDATE action can be specified for FOREIGN KEY constraint");
+					break;
+			}
+			fk_trigger->before			= false;
+			fk_trigger->row				= true;
+			fk_trigger->actions[0]		= 'u';
+			fk_trigger->actions[1]		= '\0';
+			fk_trigger->lang			= NULL;
+			fk_trigger->text			= NULL;
+			fk_trigger->attr			= NIL;
+			fk_trigger->when			= NULL;
+			fk_trigger->isconstraint	= true;
+			fk_trigger->deferrable		= fkconstraint->deferrable;
+			fk_trigger->initdeferred	= fkconstraint->initdeferred;
+			fk_trigger->constrrelname	= stmt->relname;
 
-					id = (Ident *)lfirst(pk_attr);
-					fk_trigger->args = lappend(fk_trigger->args, id->name);
+			fk_trigger->args		= NIL;
+			fk_trigger->args = lappend(fk_trigger->args,
+										fkconstraint->constr_name);
+			fk_trigger->args = lappend(fk_trigger->args,
+										stmt->relname);
+			fk_trigger->args = lappend(fk_trigger->args,
+										fkconstraint->pktable_name);
+			fk_trigger->args = lappend(fk_trigger->args,
+										fkconstraint->match_type);
+			fk_attr = fkconstraint->fk_attrs;
+			pk_attr = fkconstraint->pk_attrs;
+			while (fk_attr != NIL)
+			{
+				id = (Ident *)lfirst(fk_attr);
+				fk_trigger->args = lappend(fk_trigger->args, id->name);
 
-					fk_attr = lnext(fk_attr);
-					pk_attr = lnext(pk_attr);
-				}
+				id = (Ident *)lfirst(pk_attr);
+				fk_trigger->args = lappend(fk_trigger->args, id->name);
 
-				extras_after = lappend(extras_after, (Node *)fk_trigger);
+				fk_attr = lnext(fk_attr);
+				pk_attr = lnext(pk_attr);
 			}
+
+			extras_after = lappend(extras_after, (Node *)fk_trigger);
 		}
 	}
 
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index a5c7fe8b321af8628a384fb3a4731cb5b77eb765..e2b38821d85a2f2c2fb8b4740190cfd8d562543e 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -6,7 +6,7 @@
  *
  *	1999 Jan Wieck
  *
- * $Header: /cvsroot/pgsql/src/backend/utils/adt/ri_triggers.c,v 1.11 2000/01/06 16:30:43 wieck Exp $
+ * $Header: /cvsroot/pgsql/src/backend/utils/adt/ri_triggers.c,v 1.12 2000/01/06 20:46:51 wieck Exp $
  *
  * ----------
  */
@@ -466,6 +466,34 @@ RI_FKey_check_upd (FmgrInfo *proinfo)
 }
 
 
+/* ----------
+ * RI_FKey_noaction_del -
+ *
+ *	This is here only to let the trigger manager trace for
+ *  "triggered data change violation"
+ * ----------
+ */
+HeapTuple
+RI_FKey_noaction_del (FmgrInfo *proinfo)
+{
+	return NULL;
+}
+
+
+/* ----------
+ * RI_FKey_noaction_upd -
+ *
+ *	This is here only to let the trigger manager trace for
+ *  "triggered data change violation"
+ * ----------
+ */
+HeapTuple
+RI_FKey_noaction_upd (FmgrInfo *proinfo)
+{
+	return NULL;
+}
+
+
 /* ----------
  * RI_FKey_cascade_del -
  *
@@ -2252,6 +2280,101 @@ RI_FKey_setdefault_upd (FmgrInfo *proinfo)
 }
 
 
+/* ----------
+ * RI_FKey_keyequal_upd -
+ *
+ *	Check if we have a key change on update.
+ *
+ *	This is no real trigger procedure. It is used by the deferred
+ *	trigger queue manager to detect "triggered data change violation".
+ * ----------
+ */
+bool
+RI_FKey_keyequal_upd (void)
+{
+	TriggerData		   *trigdata;
+	int					tgnargs;
+	char			  **tgargs;
+	Relation			fk_rel;
+	Relation			pk_rel;
+	HeapTuple			new_row;
+	HeapTuple			old_row;
+	RI_QueryKey			qkey;
+
+	trigdata = CurrentTriggerData;
+	CurrentTriggerData	= NULL;
+
+	/* ----------
+	 * Check for the correct # of call arguments 
+	 * ----------
+	 */
+	tgnargs = trigdata->tg_trigger->tgnargs;
+	tgargs  = trigdata->tg_trigger->tgargs;
+	if (tgnargs < 4 || (tgnargs % 2) != 0)
+		elog(ERROR, "wrong # of arguments in call to RI_FKey_keyequal_upd()");
+	if (tgnargs > RI_MAX_ARGUMENTS)
+		elog(ERROR, "too many keys (%d max) in call to RI_FKey_keyequal_upd()",
+						RI_MAX_NUMKEYS);
+
+	/* ----------
+	 * Nothing to do if no column names to compare given
+	 * ----------
+	 */
+	if (tgnargs == 4)
+		return true;
+
+	/* ----------
+	 * Get the relation descriptors of the FK and PK tables and
+	 * the new and old tuple.
+	 * ----------
+	 */
+	fk_rel	= heap_openr(tgargs[RI_FK_RELNAME_ARGNO], NoLock);
+	pk_rel  = trigdata->tg_relation;
+	new_row = trigdata->tg_newtuple;
+	old_row = trigdata->tg_trigtuple;
+
+	switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
+	{
+		/* ----------
+		 * MATCH <UNSPECIFIED>
+		 * ----------
+		 */
+		case RI_MATCH_TYPE_UNSPECIFIED:
+			elog(ERROR, "MATCH <unspecified> not implemented yet");
+			break;
+
+		case RI_MATCH_TYPE_FULL:
+			ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
+									0,
+									fk_rel, pk_rel,
+									tgnargs, tgargs);
+			heap_close(fk_rel, NoLock);
+
+			/* ----------
+			 * Return if key's are equal
+			 * ----------
+			 */
+			return ri_KeysEqual(pk_rel, old_row, new_row, &qkey,
+													RI_KEYPAIR_PK_IDX);
+
+		/* ----------
+		 * Handle MATCH PARTIAL set null delete.
+		 * ----------
+		 */
+		case RI_MATCH_TYPE_PARTIAL:
+			elog(ERROR, "MATCH PARTIAL not yet supported");
+			break;
+	}
+
+	/* ----------
+	 * Never reached
+	 * ----------
+	 */
+	elog(ERROR, "internal error #9 in ri_triggers.c");
+	return false;
+}
+
+
 
 
 
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index be62e108d4a545f1b164acb7940cd1b8afe7ac5c..abb03c76f8a9a6846db80b10c178421eb731f02c 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -6,7 +6,7 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- * $Id: pg_proc.h,v 1.110 1999/12/28 13:40:50 wieck Exp $
+ * $Id: pg_proc.h,v 1.111 2000/01/06 20:46:54 wieck Exp $
  *
  * NOTES
  *	  The script catalog/genbki.sh reads this file and generates .bki
@@ -2142,6 +2142,10 @@ DATA(insert OID = 1652 (  RI_FKey_setdefault_del PGUID 11 f t f 0 f 0 "" 100 0 0
 DESCR("referential integrity ON DELETE SET DEFAULT");
 DATA(insert OID = 1653 (  RI_FKey_setdefault_upd PGUID 11 f t f 0 f 0 "" 100 0 0 100  RI_FKey_setdefault_upd - ));
 DESCR("referential integrity ON UPDATE SET DEFAULT");
+DATA(insert OID = 1654 (  RI_FKey_noaction_del PGUID 11 f t f 0 f 0 "" 100 0 0 100  RI_FKey_setdefault_del - ));
+DESCR("referential integrity ON DELETE NO ACTION");
+DATA(insert OID = 1655 (  RI_FKey_noaction_upd PGUID 11 f t f 0 f 0 "" 100 0 0 100  RI_FKey_setdefault_upd - ));
+DESCR("referential integrity ON UPDATE NO ACTION");
 
 /* for mac type support */
 DATA(insert OID = 436 (  macaddr_in			PGUID 11 f t t 1 f 829 "0" 100 0 0 100	macaddr_in - ));
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index 8ab4342fa7ae31d45eb1c90d48a961d9cdcd9cf5..ddb9f7ca5b45e160bdcd1f0ceb9058b56d745b25 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -37,7 +37,9 @@ extern DLLIMPORT TriggerData *CurrentTriggerData;
 #define TRIGGER_DEFERRED_DEFERRABLE		0x00000040
 #define TRIGGER_DEFERRED_INITDEFERRED	0x00000080
 #define TRIGGER_DEFERRED_HAS_BEFORE		0x00000100
-#define TRIGGER_DEFERRED_MASK			0x000001F0
+#define TRIGGER_DEFERRED_ROW_INSERTED	0x00000200
+#define TRIGGER_DEFERRED_KEY_CHANGED	0x00000400
+#define TRIGGER_DEFERRED_MASK			0x000007F0
 
 #define TRIGGER_FIRED_BY_INSERT(event)	\
 		(((TriggerEvent) (event) & TRIGGER_EVENT_OPMASK) == \
@@ -117,4 +119,10 @@ extern void DeferredTriggerSaveEvent(Relation rel, int event,
 					HeapTuple oldtup, HeapTuple newtup);
 
 
+/*
+ * in utils/adt/ri_triggers.c
+ *
+ */
+extern bool RI_FKey_keyequal_upd(void);
+
 #endif	 /* TRIGGER_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 4486131d416abc7fa66ac628dafc6d1d3be035b6..eca5f42d33afb32427f3468f84e90828426c6fbc 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -6,7 +6,7 @@
  *
  * Copyright (c) 1994, Regents of the University of California
  *
- * $Id: builtins.h,v 1.93 1999/12/28 13:40:52 wieck Exp $
+ * $Id: builtins.h,v 1.94 2000/01/06 20:47:01 wieck Exp $
  *
  * NOTES
  *	  This should normally only be included by fmgr.h.
@@ -623,6 +623,8 @@ float64		numeric_float8(Numeric num);
 /* ri_triggers.c */
 HeapTuple	RI_FKey_check_ins(FmgrInfo *proinfo);
 HeapTuple	RI_FKey_check_upd(FmgrInfo *proinfo);
+HeapTuple	RI_FKey_noaction_del(FmgrInfo *proinfo);
+HeapTuple	RI_FKey_noaction_upd(FmgrInfo *proinfo);
 HeapTuple	RI_FKey_cascade_del(FmgrInfo *proinfo);
 HeapTuple	RI_FKey_cascade_upd(FmgrInfo *proinfo);
 HeapTuple	RI_FKey_restrict_del(FmgrInfo *proinfo);