view_triggers.patch

application/octet-stream

Filename: view_triggers.patch
Type: application/octet-stream
Part: 0
Message: WIP: Triggers on VIEWs

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: context
File+
contrib/spi/autoinc.c 1 1
contrib/spi/insert_username.c 1 1
contrib/spi/moddatetime.c 1 1
contrib/spi/timetravel.c 1 1
src/backend/catalog/index.c 1 0
src/backend/commands/tablecmds.c 3 0
src/backend/commands/trigger.c 208 4
src/backend/executor/execMain.c 1 4
src/backend/executor/nodeModifyTable.c 306 47
src/backend/nodes/copyfuncs.c 1 0
src/backend/nodes/equalfuncs.c 1 0
src/backend/optimizer/plan/planmain.c 9 0
src/backend/optimizer/prep/preptlist.c 71 38
src/backend/optimizer/util/plancat.c 6 1
src/backend/parser/gram.y 8 4
src/backend/rewrite/rewriteHandler.c 129 36
src/backend/utils/adt/ruleutils.c 2 0
src/backend/utils/adt/tsvector_op.c 1 1
src/bin/pg_dump/pg_dump.c 2 0
src/bin/psql/describe.c 21 0
src/include/catalog/pg_trigger.h 3 0
src/include/commands/trigger.h 15 1
src/include/nodes/parsenodes.h 1 0
src/include/utils/rel.h 2 0
src/pl/plperl/plperl.c 2 0
src/pl/plpgsql/src/pl_exec.c 3 1
src/pl/plpython/plpython.c 2 0
src/pl/tcl/pltcl.c 2 0
src/test/regress/expected/triggers.out 398 0
src/test/regress/regress.c 1 1
src/test/regress/sql/triggers.sql 159 0
*** ./contrib/spi/autoinc.c.orig	2010-07-31 12:06:47.000000000 +0100
--- ./contrib/spi/autoinc.c	2010-08-15 12:55:08.000000000 +0100
***************
*** 38,44 ****
  	if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
  		/* internal error */
  		elog(ERROR, "cannot process STATEMENT events");
! 	if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
  		/* internal error */
  		elog(ERROR, "must be fired before event");
  
--- 38,44 ----
  	if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
  		/* internal error */
  		elog(ERROR, "cannot process STATEMENT events");
! 	if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event))
  		/* internal error */
  		elog(ERROR, "must be fired before event");
  
*** ./contrib/spi/insert_username.c.orig	2010-07-31 12:07:44.000000000 +0100
--- ./contrib/spi/insert_username.c	2010-08-15 12:56:59.000000000 +0100
***************
*** 41,47 ****
  	if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
  		/* internal error */
  		elog(ERROR, "insert_username: cannot process STATEMENT events");
! 	if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
  		/* internal error */
  		elog(ERROR, "insert_username: must be fired before event");
  
--- 41,47 ----
  	if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
  		/* internal error */
  		elog(ERROR, "insert_username: cannot process STATEMENT events");
! 	if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event))
  		/* internal error */
  		elog(ERROR, "insert_username: must be fired before event");
  
*** ./contrib/spi/moddatetime.c.orig	2010-07-31 12:10:47.000000000 +0100
--- ./contrib/spi/moddatetime.c	2010-08-15 12:57:35.000000000 +0100
***************
*** 47,53 ****
  		/* internal error */
  		elog(ERROR, "moddatetime: cannot process STATEMENT events");
  
! 	if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
  		/* internal error */
  		elog(ERROR, "moddatetime: must be fired before event");
  
--- 47,53 ----
  		/* internal error */
  		elog(ERROR, "moddatetime: cannot process STATEMENT events");
  
! 	if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event))
  		/* internal error */
  		elog(ERROR, "moddatetime: must be fired before event");
  
*** ./contrib/spi/timetravel.c.orig	2010-07-31 12:13:01.000000000 +0100
--- ./contrib/spi/timetravel.c	2010-08-15 16:42:48.000000000 +0100
***************
*** 122,128 ****
  		elog(ERROR, "timetravel: cannot process STATEMENT events");
  
  	/* Should be called BEFORE */
! 	if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
  		elog(ERROR, "timetravel: must be fired before event");
  
  	/* INSERT ? */
--- 122,128 ----
  		elog(ERROR, "timetravel: cannot process STATEMENT events");
  
  	/* Should be called BEFORE */
! 	if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event))
  		elog(ERROR, "timetravel: must be fired before event");
  
  	/* INSERT ? */
*** ./src/backend/catalog/index.c.orig	2010-08-14 23:52:07.000000000 +0100
--- ./src/backend/catalog/index.c	2010-08-15 09:53:21.000000000 +0100
***************
*** 825,830 ****
--- 825,831 ----
  				trigger->funcname = SystemFuncName("unique_key_recheck");
  				trigger->args = NIL;
  				trigger->before = false;
+ 				trigger->instead = false;
  				trigger->row = true;
  				trigger->events = TRIGGER_TYPE_INSERT | TRIGGER_TYPE_UPDATE;
  				trigger->columns = NIL;
*** ./src/backend/commands/tablecmds.c.orig	2010-08-14 23:54:39.000000000 +0100
--- ./src/backend/commands/tablecmds.c	2010-08-15 09:54:57.000000000 +0100
***************
*** 5605,5610 ****
--- 5605,5611 ----
  	fk_trigger->trigname = "RI_ConstraintTrigger";
  	fk_trigger->relation = myRel;
  	fk_trigger->before = false;
+ 	fk_trigger->instead = false;
  	fk_trigger->row = true;
  
  	/* Either ON INSERT or ON UPDATE */
***************
*** 5668,5673 ****
--- 5669,5675 ----
  	fk_trigger->trigname = "RI_ConstraintTrigger";
  	fk_trigger->relation = fkconstraint->pktable;
  	fk_trigger->before = false;
+ 	fk_trigger->instead = false;
  	fk_trigger->row = true;
  	fk_trigger->events = TRIGGER_TYPE_DELETE;
  	fk_trigger->columns = NIL;
***************
*** 5721,5726 ****
--- 5723,5729 ----
  	fk_trigger->trigname = "RI_ConstraintTrigger";
  	fk_trigger->relation = fkconstraint->pktable;
  	fk_trigger->before = false;
+ 	fk_trigger->instead = false;
  	fk_trigger->row = true;
  	fk_trigger->events = TRIGGER_TYPE_UPDATE;
  	fk_trigger->columns = NIL;
*** ./src/backend/commands/trigger.c.orig	2010-08-14 23:58:52.000000000 +0100
--- ./src/backend/commands/trigger.c	2010-08-15 10:22:19.000000000 +0100
***************
*** 150,159 ****
  	 */
  	rel = heap_openrv(stmt->relation, ShareRowExclusiveLock);
  
! 	if (rel->rd_rel->relkind != RELKIND_RELATION)
  		ereport(ERROR,
  				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! 				 errmsg("\"%s\" is not a table",
  						RelationGetRelationName(rel))));
  
  	if (!allowSystemTableMods && IsSystemRelation(rel))
--- 150,198 ----
  	 */
  	rel = heap_openrv(stmt->relation, ShareRowExclusiveLock);
  
! 	if (rel->rd_rel->relkind == RELKIND_RELATION)
! 	{
! 		/* Forbid INSTEAD OF triggers on tables */
! 		if (stmt->instead)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! 					 errmsg("\"%s\" is a table",
! 							RelationGetRelationName(rel)),
! 					 errhint("Define INSTEAD OF triggers on views")));
! 	}
! 	else if (rel->rd_rel->relkind == RELKIND_VIEW)
! 	{
! 		/* Only allow INSTEAD OF or STATEMENT-level triggers on views */
! 		if (!stmt->instead && stmt->row)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! 					 errmsg("\"%s\" is a view",
! 							RelationGetRelationName(rel)),
! 					 errhint("Define ROW-level BEFORE and AFTER triggers on tables")));
! 
! 		/* Disallow STATEMENT-level INSTEAD OF triggers */
! 		if (stmt->instead && !stmt->row)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 					 errmsg("STATEMENT-level INSTEAD OF triggers are not supported"),
! 					 errhint("Use FOR EACH ROW for INSTEAD OF triggers")));
! 
! 		/* Disallow WHEN clauses with INSTEAD OF triggers */
! 		if (stmt->instead && stmt->whenClause)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 					 errmsg("cannot use a WHEN condition with an INSTEAD OF trigger")));
! 
! 		/* Disallow column selection with INSTEAD OF triggers */
! 		if (stmt->instead && stmt->columns != NIL)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 					 errmsg("cannot use a column list with an INSTEAD OF trigger")));
! 	}
! 	else
  		ereport(ERROR,
  				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! 				 errmsg("\"%s\" is not a table or view",
  						RelationGetRelationName(rel))));
  
  	if (!allowSystemTableMods && IsSystemRelation(rel))
***************
*** 188,193 ****
--- 227,234 ----
  	TRIGGER_CLEAR_TYPE(tgtype);
  	if (stmt->before)
  		TRIGGER_SETT_BEFORE(tgtype);
+ 	if (stmt->instead)
+ 		TRIGGER_SETT_INSTEAD(tgtype);
  	if (stmt->row)
  		TRIGGER_SETT_ROW(tgtype);
  	tgtype |= stmt->events;
***************
*** 198,203 ****
--- 239,252 ----
  				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
  				 errmsg("TRUNCATE FOR EACH ROW triggers are not supported")));
  
+ 	/* Disallow TRUNCATE triggers on VIEWs */
+ 	if (rel->rd_rel->relkind == RELKIND_VIEW && TRIGGER_FOR_TRUNCATE(tgtype))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 				 errmsg("\"%s\" is a view",
+ 						RelationGetRelationName(rel)),
+ 				 errhint("Define TRUNCATE triggers on tables")));
+ 
  	/*
  	 * Parse the WHEN clause, if any
  	 */
***************
*** 1031,1040 ****
  
  	rel = heap_open(relid, ShareRowExclusiveLock);
  
! 	if (rel->rd_rel->relkind != RELKIND_RELATION)
  		ereport(ERROR,
  				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! 				 errmsg("\"%s\" is not a table",
  						RelationGetRelationName(rel))));
  
  	if (!allowSystemTableMods && IsSystemRelation(rel))
--- 1080,1090 ----
  
  	rel = heap_open(relid, ShareRowExclusiveLock);
  
! 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
! 		rel->rd_rel->relkind != RELKIND_VIEW)
  		ereport(ERROR,
  				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! 				 errmsg("\"%s\" is not a table or view",
  						RelationGetRelationName(rel))));
  
  	if (!allowSystemTableMods && IsSystemRelation(rel))
***************
*** 1506,1511 ****
--- 1556,1566 ----
  			n = trigdesc->n_before_row;
  			t = trigdesc->tg_before_row;
  		}
+ 		else if (TRIGGER_FOR_INSTEAD(trigger->tgtype))
+ 		{
+ 			n = trigdesc->n_instead_row;
+ 			t = trigdesc->tg_instead_row;
+ 		}
  		else
  		{
  			n = trigdesc->n_after_row;
***************
*** 1656,1661 ****
--- 1711,1729 ----
  		else
  			t[i] = NULL;
  	}
+ 	n = newdesc->n_instead_row;
+ 	t = newdesc->tg_instead_row;
+ 	for (i = 0; i < TRIGGER_NUM_EVENT_CLASSES; i++)
+ 	{
+ 		if (n[i] > 0)
+ 		{
+ 			tnew = (int *) palloc(n[i] * sizeof(int));
+ 			memcpy(tnew, t[i], n[i] * sizeof(int));
+ 			t[i] = tnew;
+ 		}
+ 		else
+ 			t[i] = NULL;
+ 	}
  	n = newdesc->n_after_statement;
  	t = newdesc->tg_after_statement;
  	for (i = 0; i < TRIGGER_NUM_EVENT_CLASSES; i++)
***************
*** 1698,1703 ****
--- 1766,1775 ----
  	for (i = 0; i < TRIGGER_NUM_EVENT_CLASSES; i++)
  		if (t[i] != NULL)
  			pfree(t[i]);
+ 	t = trigdesc->tg_instead_row;
+ 	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 < TRIGGER_NUM_EVENT_CLASSES; i++)
  		if (t[i] != NULL)
***************
*** 2000,2005 ****
--- 2072,2121 ----
  							  true, NULL, trigtuple, recheckIndexes, NULL);
  }
  
+ HeapTuple
+ ExecIRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
+ 					 HeapTuple trigtuple)
+ {
+ 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+ 	int			ntrigs = trigdesc->n_instead_row[TRIGGER_EVENT_INSERT];
+ 	int		   *tgindx = trigdesc->tg_instead_row[TRIGGER_EVENT_INSERT];
+ 	TriggerData LocTriggerData;
+ 	HeapTuple	newtuple = trigtuple;
+ 	HeapTuple	rettuple;
+ 	int			i;
+ 
+ 	LocTriggerData.type = T_TriggerData;
+ 	LocTriggerData.tg_event = TRIGGER_EVENT_INSERT |
+ 		TRIGGER_EVENT_ROW |
+ 		TRIGGER_EVENT_INSTEAD;
+ 	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
+ 	for (i = 0; i < ntrigs; i++)
+ 	{
+ 		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
+ 
+ 		if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
+ 							NULL, NULL, trigtuple))
+ 			continue;
+ 
+ 		LocTriggerData.tg_trigtuple = newtuple;
+ 		LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
+ 		LocTriggerData.tg_newtuple = NULL;
+ 		LocTriggerData.tg_newtuplebuf = InvalidBuffer;
+ 		LocTriggerData.tg_trigger = trigger;
+ 		rettuple = ExecCallTriggerFunc(&LocTriggerData,
+ 									   tgindx[i],
+ 									   relinfo->ri_TrigFunctions,
+ 									   relinfo->ri_TrigInstrument,
+ 									   GetPerTupleMemoryContext(estate));
+ 		if (rettuple != newtuple && newtuple != trigtuple)
+ 			heap_freetuple(newtuple);
+ 		newtuple = rettuple;
+ 		if (newtuple == NULL)
+ 			break;
+ 	}
+ 	return newtuple;
+ }
+ 
  void
  ExecBSDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
  {
***************
*** 2134,2139 ****
--- 2250,2299 ----
  	}
  }
  
+ HeapTuple
+ ExecIRDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
+ 					 HeapTuple trigtuple)
+ {
+ 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+ 	int			ntrigs = trigdesc->n_instead_row[TRIGGER_EVENT_DELETE];
+ 	int		   *tgindx = trigdesc->tg_instead_row[TRIGGER_EVENT_DELETE];
+ 	TriggerData LocTriggerData;
+ 	HeapTuple	oldtuple = NULL;
+ 	HeapTuple	rettuple;
+ 	int			i;
+ 
+ 	LocTriggerData.type = T_TriggerData;
+ 	LocTriggerData.tg_event = TRIGGER_EVENT_DELETE |
+ 		TRIGGER_EVENT_ROW |
+ 		TRIGGER_EVENT_INSTEAD;
+ 	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
+ 	for (i = 0; i < ntrigs; i++)
+ 	{
+ 		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
+ 
+ 		if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
+ 							NULL, trigtuple, NULL))
+ 			continue;
+ 
+ 		LocTriggerData.tg_trigtuple = trigtuple;
+ 		LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
+ 		LocTriggerData.tg_newtuple = NULL;
+ 		LocTriggerData.tg_newtuplebuf = InvalidBuffer;
+ 		LocTriggerData.tg_trigger = trigger;
+ 		rettuple = ExecCallTriggerFunc(&LocTriggerData,
+ 									   tgindx[i],
+ 									   relinfo->ri_TrigFunctions,
+ 									   relinfo->ri_TrigInstrument,
+ 									   GetPerTupleMemoryContext(estate));
+ 		if (rettuple != oldtuple && oldtuple != NULL)
+ 			heap_freetuple(oldtuple);
+ 		oldtuple = rettuple;
+ 		if (oldtuple == NULL)
+ 			break;
+ 	}
+ 	return oldtuple;
+ }
+ 
  void
  ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
  {
***************
*** 2281,2286 ****
--- 2441,2490 ----
  	}
  }
  
+ HeapTuple
+ ExecIRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
+ 					 HeapTuple oldtuple, HeapTuple newtuple)
+ {
+ 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+ 	int			ntrigs = trigdesc->n_instead_row[TRIGGER_EVENT_UPDATE];
+ 	int		   *tgindx = trigdesc->tg_instead_row[TRIGGER_EVENT_UPDATE];
+ 	TriggerData LocTriggerData;
+ 	HeapTuple	intuple = newtuple;
+ 	HeapTuple	rettuple;
+ 	int			i;
+ 
+ 	LocTriggerData.type = T_TriggerData;
+ 	LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
+ 		TRIGGER_EVENT_ROW |
+ 		TRIGGER_EVENT_INSTEAD;
+ 	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
+ 	for (i = 0; i < ntrigs; i++)
+ 	{
+ 		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
+ 
+ 		if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
+ 							NULL, oldtuple, newtuple))
+ 			continue;
+ 
+ 		LocTriggerData.tg_trigtuple = oldtuple;
+ 		LocTriggerData.tg_newtuple = newtuple;
+ 		LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
+ 		LocTriggerData.tg_newtuplebuf = InvalidBuffer;
+ 		LocTriggerData.tg_trigger = trigger;
+ 		rettuple = ExecCallTriggerFunc(&LocTriggerData,
+ 									   tgindx[i],
+ 									   relinfo->ri_TrigFunctions,
+ 									   relinfo->ri_TrigInstrument,
+ 									   GetPerTupleMemoryContext(estate));
+ 		if (rettuple != newtuple && newtuple != intuple)
+ 			heap_freetuple(newtuple);
+ 		newtuple = rettuple;
+ 		if (newtuple == NULL)
+ 			break;
+ 	}
+ 	return newtuple;
+ }
+ 
  void
  ExecBSTruncateTriggers(EState *estate, ResultRelInfo *relinfo)
  {
*** ./src/backend/executor/execMain.c.orig	2010-08-15 00:04:31.000000000 +0100
--- ./src/backend/executor/execMain.c	2010-08-15 10:01:37.000000000 +0100
***************
*** 899,908 ****
  							RelationGetRelationName(resultRelationDesc))));
  			break;
  		case RELKIND_VIEW:
! 			ereport(ERROR,
! 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! 					 errmsg("cannot change view \"%s\"",
! 							RelationGetRelationName(resultRelationDesc))));
  			break;
  		default:
  			ereport(ERROR,
--- 899,905 ----
  							RelationGetRelationName(resultRelationDesc))));
  			break;
  		case RELKIND_VIEW:
! 			/* OK */
  			break;
  		default:
  			ereport(ERROR,
*** ./src/backend/executor/nodeModifyTable.c.orig	2010-07-28 09:37:15.000000000 +0100
--- ./src/backend/executor/nodeModifyTable.c	2010-08-15 15:19:54.000000000 +0100
***************
*** 149,154 ****
--- 149,200 ----
  }
  
  /* ----------------------------------------------------------------
+  *		ExecCheckViewTriggers
+  *
+  *		If the result relation is a view, check that it has the
+  *		appropriate INSTEAD OF triggers to carry out the current
+  *		operation.
+  * ----------------------------------------------------------------
+  */
+ static void
+ ExecCheckViewTriggers(ResultRelInfo *resultRelInfo,
+ 					  CmdType operation)
+ {
+ 	TriggerDesc	*trigDesc = resultRelInfo->ri_TrigDesc;
+ 
+ 	switch (operation)
+ 	{
+ 		case CMD_INSERT:
+ 			if (!trigDesc ||
+ 				trigDesc->n_instead_row[TRIGGER_EVENT_INSERT] == 0)
+ 				ereport(ERROR,
+ 						(errmsg("cannot insert into a view"),
+ 						 errhint("You need an unconditional ON INSERT DO INSTEAD rule or an INSTEAD OF INSERT trigger.")));
+ 			break;
+ 
+ 		case CMD_UPDATE:
+ 			if (!trigDesc ||
+ 				trigDesc->n_instead_row[TRIGGER_EVENT_UPDATE] == 0)
+ 				ereport(ERROR,
+ 						(errmsg("cannot update a view"),
+ 						 errhint("You need an unconditional ON UPDATE DO INSTEAD rule or an INSTEAD OF UPDATE trigger.")));
+ 			break;
+ 
+ 		case CMD_DELETE:
+ 			if (!trigDesc ||
+ 				trigDesc->n_instead_row[TRIGGER_EVENT_DELETE] == 0)
+ 				ereport(ERROR,
+ 						(errmsg("cannot delete from a view"),
+ 						 errhint("You need an unconditional ON DELETE DO INSTEAD rule or an INSTEAD OF DELETE trigger.")));
+ 			break;
+ 
+ 		default:
+ 			elog(ERROR, "unknown operation");
+ 			break;
+ 	}
+ }
+ 
+ /* ----------------------------------------------------------------
   *		ExecInsert
   *
   *		For INSERT, we have to insert the tuple into the target relation
***************
*** 167,172 ****
--- 213,219 ----
  	Relation	resultRelationDesc;
  	Oid			newId;
  	List	   *recheckIndexes = NIL;
+ 	bool		resultRelIsView;
  
  	/*
  	 * get the heap tuple out of the tuple table slot, making sure we have a
***************
*** 179,184 ****
--- 226,232 ----
  	 */
  	resultRelInfo = estate->es_result_relation_info;
  	resultRelationDesc = resultRelInfo->ri_RelationDesc;
+ 	resultRelIsView = resultRelationDesc->rd_rel->relkind == RELKIND_VIEW;
  
  	/*
  	 * If the result relation has OIDs, force the tuple's OID to zero so that
***************
*** 195,202 ****
  	if (resultRelationDesc->rd_rel->relhasoids)
  		HeapTupleSetOid(tuple, InvalidOid);
  
! 	/* BEFORE ROW INSERT Triggers */
! 	if (resultRelInfo->ri_TrigDesc &&
  		resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_INSERT] > 0)
  	{
  		HeapTuple	newtuple;
--- 243,258 ----
  	if (resultRelationDesc->rd_rel->relhasoids)
  		HeapTupleSetOid(tuple, InvalidOid);
  
! 	/*
! 	 * BEFORE ROW INSERT Triggers.
! 	 *
! 	 * We disallow BEFORE ROW triggers on views. Technically we could allow
! 	 * them for INSERT, but we can't do them for UPDATE or DELETE because we
! 	 * won't have a tupleid, so we disallow them for INSERT as well for
! 	 * consistency.
! 	 */
! 	if (!resultRelIsView &&
! 		resultRelInfo->ri_TrigDesc &&
  		resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_INSERT] > 0)
  	{
  		HeapTuple	newtuple;
***************
*** 237,244 ****
  	 * Note: heap_insert returns the tid (location) of the new tuple in the
  	 * t_self field.
  	 */
! 	newId = heap_insert(resultRelationDesc, tuple,
! 						estate->es_output_cid, 0, NULL);
  
  	(estate->es_processed)++;
  	estate->es_lastoid = newId;
--- 293,334 ----
  	 * Note: heap_insert returns the tid (location) of the new tuple in the
  	 * t_self field.
  	 */
! 	if (resultRelIsView)
! 	{
! 		/* INSTEAD OF trigger(s) do the actual insert */
! 		HeapTuple	newtuple;
! 
! 		ExecCheckViewTriggers(resultRelInfo, CMD_INSERT);
! 		newtuple = ExecIRInsertTriggers(estate, resultRelInfo, tuple);
! 
! 		if (newtuple == NULL)	/* trigger(s) did nothing */
! 			return NULL;
! 
! 		if (newtuple != tuple)	/* modified by Trigger(s) */
! 		{
! 			/*
! 			 * Put the modified tuple into a slot for convenience of
! 			 * routines below.  We assume the tuple was allocated in
! 			 * per-tuple memory context, and therefore will go away by
! 			 * itself. The tuple table slot should not try to clear it.
! 			 */
! 			TupleTableSlot *newslot = estate->es_trig_tuple_slot;
! 			TupleDesc	tupdesc = RelationGetDescr(resultRelationDesc);
! 
! 			if (newslot->tts_tupleDescriptor != tupdesc)
! 				ExecSetSlotDescriptor(newslot, tupdesc);
! 			ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
! 			slot = newslot;
! 			tuple = newtuple;
! 		}
! 
! 		newId = InvalidOid;
! 	}
! 	else
! 	{
! 		newId = heap_insert(resultRelationDesc, tuple,
! 							estate->es_output_cid, 0, NULL);
! 	}
  
  	(estate->es_processed)++;
  	estate->es_lastoid = newId;
***************
*** 251,258 ****
  		recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
  											   estate);
  
! 	/* AFTER ROW INSERT Triggers */
! 	ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes);
  
  	list_free(recheckIndexes);
  
--- 341,353 ----
  		recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
  											   estate);
  
! 	/*
! 	 * AFTER ROW INSERT Triggers
! 	 *
! 	 * As with BEFORE ROW triggers, we disallow these on views.
! 	 */
! 	if (!resultRelIsView)
! 		ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes);
  
  	list_free(recheckIndexes);
  
***************
*** 268,280 ****
   *		ExecDelete
   *
   *		DELETE is like UPDATE, except that we delete the tuple and no
!  *		index modifications are needed
   *
   *		Returns RETURNING result if any, otherwise NULL.
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
  ExecDelete(ItemPointer tupleid,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
  		   EState *estate)
--- 363,381 ----
   *		ExecDelete
   *
   *		DELETE is like UPDATE, except that we delete the tuple and no
!  *		index modifications are needed.
!  *
!  *		When deleting from a table, tupleid identifies the tuple to
!  *		delete and oldtuple is NULL.  When deleting from a view,
!  *		oldtuple is passed to the INSTEAD OF triggers and identifies
!  *		what to delete, and tupleid is invalid.
   *
   *		Returns RETURNING result if any, otherwise NULL.
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
  ExecDelete(ItemPointer tupleid,
+ 		   HeapTupleHeader oldtuple,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
  		   EState *estate)
***************
*** 284,298 ****
  	HTSU_Result result;
  	ItemPointerData update_ctid;
  	TransactionId update_xmax;
  
  	/*
  	 * get information on the (current) result relation
  	 */
  	resultRelInfo = estate->es_result_relation_info;
  	resultRelationDesc = resultRelInfo->ri_RelationDesc;
  
! 	/* BEFORE ROW DELETE Triggers */
! 	if (resultRelInfo->ri_TrigDesc &&
  		resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_DELETE] > 0)
  	{
  		bool		dodelete;
--- 385,407 ----
  	HTSU_Result result;
  	ItemPointerData update_ctid;
  	TransactionId update_xmax;
+ 	bool		resultRelIsView;
  
  	/*
  	 * get information on the (current) result relation
  	 */
  	resultRelInfo = estate->es_result_relation_info;
  	resultRelationDesc = resultRelInfo->ri_RelationDesc;
+ 	resultRelIsView = resultRelationDesc->rd_rel->relkind == RELKIND_VIEW;
  
! 	/*
! 	 * BEFORE ROW DELETE Triggers
! 	 *
! 	 * We disallow BEFORE ROW triggers on views because we don't have a
! 	 * tupleid.
! 	 */
! 	if (!resultRelIsView &&
! 		resultRelInfo->ri_TrigDesc &&
  		resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_DELETE] > 0)
  	{
  		bool		dodelete;
***************
*** 313,323 ****
  	 * referential integrity updates in serializable transactions.
  	 */
  ldelete:;
! 	result = heap_delete(resultRelationDesc, tupleid,
! 						 &update_ctid, &update_xmax,
! 						 estate->es_output_cid,
! 						 estate->es_crosscheck_snapshot,
! 						 true /* wait for commit */ );
  	switch (result)
  	{
  		case HeapTupleSelfUpdated:
--- 422,472 ----
  	 * referential integrity updates in serializable transactions.
  	 */
  ldelete:;
! 	if (resultRelIsView)
! 	{
! 		/* INSTEAD OF trigger(s) do the actual delete */
! 		HeapTupleData tuple;
! 		HeapTuple	deltuple;
! 
! 		tuple.t_data = oldtuple;
! 		tuple.t_len = HeapTupleHeaderGetDatumLength(oldtuple);
! 		ItemPointerSetInvalid(&(tuple.t_self));
! 		tuple.t_tableOid = InvalidOid;
! 
! 		ExecCheckViewTriggers(resultRelInfo, CMD_DELETE);
! 		deltuple = ExecIRDeleteTriggers(estate, resultRelInfo, &tuple);
! 
! 		if (deltuple == NULL)	/* trigger(s) did nothing */
! 			return NULL;
! 
! 		if (deltuple != &tuple)	/* modified by Trigger(s) */
! 		{
! 			/*
! 			 * Put the modified tuple into a slot for convenience of
! 			 * routines below.  We assume the tuple was allocated in
! 			 * per-tuple memory context, and therefore will go away by
! 			 * itself. The tuple table slot should not try to clear it.
! 			 */
! 			TupleTableSlot *slot = estate->es_trig_tuple_slot;
! 			TupleDesc	tupdesc = RelationGetDescr(resultRelationDesc);
! 
! 			if (slot->tts_tupleDescriptor != tupdesc)
! 				ExecSetSlotDescriptor(slot, tupdesc);
! 			ExecStoreTuple(deltuple, slot, InvalidBuffer, false);
! 			oldtuple = deltuple->t_data;
! 		}
! 
! 		result = HeapTupleMayBeUpdated;
! 	}
! 	else
! 	{
! 		result = heap_delete(resultRelationDesc, tupleid,
! 							 &update_ctid, &update_xmax,
! 							 estate->es_output_cid,
! 							 estate->es_crosscheck_snapshot,
! 							 true /* wait for commit */ );
! 	}
! 
  	switch (result)
  	{
  		case HeapTupleSelfUpdated:
***************
*** 367,374 ****
  	 * anyway, since the tuple is still visible to other transactions.
  	 */
  
! 	/* AFTER ROW DELETE Triggers */
! 	ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
  
  	/* Process RETURNING if present */
  	if (resultRelInfo->ri_projectReturning)
--- 516,528 ----
  	 * anyway, since the tuple is still visible to other transactions.
  	 */
  
! 	/*
! 	 * AFTER ROW DELETE Triggers
! 	 *
! 	 * As with BEFORE ROW triggers, we disallow these on views.
! 	 */
! 	if (!resultRelIsView)
! 		ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
  
  	/* Process RETURNING if present */
  	if (resultRelInfo->ri_projectReturning)
***************
*** 382,391 ****
  		HeapTupleData deltuple;
  		Buffer		delbuffer;
  
! 		deltuple.t_self = *tupleid;
! 		if (!heap_fetch(resultRelationDesc, SnapshotAny,
! 						&deltuple, &delbuffer, false, NULL))
! 			elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
  
  		if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
  			ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
--- 536,555 ----
  		HeapTupleData deltuple;
  		Buffer		delbuffer;
  
! 		if (resultRelIsView)
! 		{
! 			deltuple.t_data = oldtuple;
! 			deltuple.t_len = HeapTupleHeaderGetDatumLength(oldtuple);
! 			ItemPointerSetInvalid(&(deltuple.t_self));
! 			deltuple.t_tableOid = InvalidOid;
! 		}
! 		else
! 		{
! 			deltuple.t_self = *tupleid;
! 			if (!heap_fetch(resultRelationDesc, SnapshotAny,
! 							&deltuple, &delbuffer, false, NULL))
! 				elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
! 		}
  
  		if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
  			ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
***************
*** 395,401 ****
  									 slot, planSlot);
  
  		ExecClearTuple(slot);
! 		ReleaseBuffer(delbuffer);
  
  		return rslot;
  	}
--- 559,566 ----
  									 slot, planSlot);
  
  		ExecClearTuple(slot);
! 		if (!resultRelIsView)
! 			ReleaseBuffer(delbuffer);
  
  		return rslot;
  	}
***************
*** 413,423 ****
--- 578,594 ----
   *		is, we don't want to get stuck in an infinite loop
   *		which corrupts your database..
   *
+  *		When updating a table, tupleid identifies the tuple to
+  *		update and oldtuple is NULL.  When updating a view, oldtuple
+  *		is passed to the INSTEAD OF triggers and identifies what to
+  *		update, and tupleid is invalid.
+  *
   *		Returns RETURNING result if any, otherwise NULL.
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
  ExecUpdate(ItemPointer tupleid,
+ 		   HeapTupleHeader oldtuple,
  		   TupleTableSlot *slot,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
***************
*** 430,435 ****
--- 601,607 ----
  	ItemPointerData update_ctid;
  	TransactionId update_xmax;
  	List	   *recheckIndexes = NIL;
+ 	bool		resultRelIsView;
  
  	/*
  	 * abort the operation if not running transactions
***************
*** 448,456 ****
  	 */
  	resultRelInfo = estate->es_result_relation_info;
  	resultRelationDesc = resultRelInfo->ri_RelationDesc;
  
! 	/* BEFORE ROW UPDATE Triggers */
! 	if (resultRelInfo->ri_TrigDesc &&
  		resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_UPDATE] > 0)
  	{
  		HeapTuple	newtuple;
--- 620,635 ----
  	 */
  	resultRelInfo = estate->es_result_relation_info;
  	resultRelationDesc = resultRelInfo->ri_RelationDesc;
+ 	resultRelIsView = resultRelationDesc->rd_rel->relkind == RELKIND_VIEW;
  
! 	/*
! 	 * BEFORE ROW UPDATE Triggers
! 	 *
! 	 * We disallow BEFORE ROW triggers on views because we don't have a
! 	 * tupleid.
! 	 */
! 	if (!resultRelIsView &&
! 		resultRelInfo->ri_TrigDesc &&
  		resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_UPDATE] > 0)
  	{
  		HeapTuple	newtuple;
***************
*** 501,511 ****
  	 * serialize error if not.	This is a special-case behavior needed for
  	 * referential integrity updates in serializable transactions.
  	 */
! 	result = heap_update(resultRelationDesc, tupleid, tuple,
! 						 &update_ctid, &update_xmax,
! 						 estate->es_output_cid,
! 						 estate->es_crosscheck_snapshot,
! 						 true /* wait for commit */ );
  	switch (result)
  	{
  		case HeapTupleSelfUpdated:
--- 680,732 ----
  	 * serialize error if not.	This is a special-case behavior needed for
  	 * referential integrity updates in serializable transactions.
  	 */
! 	if (resultRelIsView)
! 	{
! 		/* INSTEAD OF trigger(s) do the actual update */
! 		HeapTupleData oldtup;
! 		HeapTuple	newtuple;
! 
! 		oldtup.t_data = oldtuple;
! 		oldtup.t_len = HeapTupleHeaderGetDatumLength(oldtuple);
! 		ItemPointerSetInvalid(&(oldtup.t_self));
! 		oldtup.t_tableOid = InvalidOid;
! 
! 		ExecCheckViewTriggers(resultRelInfo, CMD_UPDATE);
! 		newtuple = ExecIRUpdateTriggers(estate, resultRelInfo,
! 										&oldtup, tuple);
! 
! 		if (newtuple == NULL)	/* trigger(s) did nothing */
! 			return NULL;
! 
! 		if (newtuple != tuple)	/* modified by Trigger(s) */
! 		{
! 			/*
! 			 * Put the modified tuple into a slot for convenience of
! 			 * routines below.  We assume the tuple was allocated in
! 			 * per-tuple memory context, and therefore will go away by
! 			 * itself. The tuple table slot should not try to clear it.
! 			 */
! 			TupleTableSlot *newslot = estate->es_trig_tuple_slot;
! 			TupleDesc	tupdesc = RelationGetDescr(resultRelationDesc);
! 
! 			if (newslot->tts_tupleDescriptor != tupdesc)
! 				ExecSetSlotDescriptor(newslot, tupdesc);
! 			ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
! 			slot = newslot;
! 			tuple = newtuple;
! 		}
! 
! 		result = HeapTupleMayBeUpdated;
! 	}
! 	else
! 	{
! 		result = heap_update(resultRelationDesc, tupleid, tuple,
! 							 &update_ctid, &update_xmax,
! 							 estate->es_output_cid,
! 							 estate->es_crosscheck_snapshot,
! 							 true /* wait for commit */ );
! 	}
! 
  	switch (result)
  	{
  		case HeapTupleSelfUpdated:
***************
*** 568,576 ****
  		recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
  											   estate);
  
! 	/* AFTER ROW UPDATE Triggers */
! 	ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple,
! 						 recheckIndexes);
  
  	list_free(recheckIndexes);
  
--- 789,802 ----
  		recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
  											   estate);
  
! 	/*
! 	 * AFTER ROW UPDATE Triggers
! 	 *
! 	 * As with BEFORE ROW triggers, we disallow these on views.
! 	 */
! 	if (!resultRelIsView)
! 		ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple,
! 							 recheckIndexes);
  
  	list_free(recheckIndexes);
  
***************
*** 654,659 ****
--- 880,886 ----
  	TupleTableSlot *planSlot;
  	ItemPointer tupleid = NULL;
  	ItemPointerData tuple_ctid;
+ 	HeapTupleHeader	oldtuple;
  
  	/*
  	 * On first call, fire BEFORE STATEMENT triggers before proceeding.
***************
*** 705,727 ****
  
  		if (junkfilter != NULL)
  		{
  			/*
! 			 * extract the 'ctid' junk attribute.
  			 */
  			if (operation == CMD_UPDATE || operation == CMD_DELETE)
  			{
  				Datum		datum;
  				bool		isNull;
  
! 				datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo,
! 											 &isNull);
! 				/* shouldn't ever get a null result... */
! 				if (isNull)
! 					elog(ERROR, "ctid is NULL");
! 
! 				tupleid = (ItemPointer) DatumGetPointer(datum);
! 				tuple_ctid = *tupleid;	/* be sure we don't free the ctid!! */
! 				tupleid = &tuple_ctid;
  			}
  
  			/*
--- 932,968 ----
  
  		if (junkfilter != NULL)
  		{
+ 			ResultRelInfo  *resultRelInfo = estate->es_result_relation_info;
+ 			Relation		resultRel = resultRelInfo->ri_RelationDesc;
+ 
  			/*
! 			 * extract the 'ctid' or 'wholerow' junk attribute.
  			 */
  			if (operation == CMD_UPDATE || operation == CMD_DELETE)
  			{
  				Datum		datum;
  				bool		isNull;
  
! 				if (resultRel->rd_rel->relkind == RELKIND_VIEW)
! 				{
! 					datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo,
! 												 &isNull);
! 					if (isNull)
! 						elog(ERROR, "wholerow is NULL");
! 
! 					oldtuple = DatumGetHeapTupleHeader(datum);
! 				}
! 				else
! 				{
! 					datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo,
! 												 &isNull);
! 					if (isNull)
! 						elog(ERROR, "ctid is NULL");
! 
! 					tupleid = (ItemPointer) DatumGetPointer(datum);
! 					tuple_ctid = *tupleid;	/* be sure we don't free the ctid!! */
! 					tupleid = &tuple_ctid;
! 				}
  			}
  
  			/*
***************
*** 737,747 ****
  				slot = ExecInsert(slot, planSlot, estate);
  				break;
  			case CMD_UPDATE:
! 				slot = ExecUpdate(tupleid, slot, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			case CMD_DELETE:
! 				slot = ExecDelete(tupleid, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			default:
--- 978,988 ----
  				slot = ExecInsert(slot, planSlot, estate);
  				break;
  			case CMD_UPDATE:
! 				slot = ExecUpdate(tupleid, oldtuple, slot, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			case CMD_DELETE:
! 				slot = ExecDelete(tupleid, oldtuple, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			default:
***************
*** 825,830 ****
--- 1066,1075 ----
  	i = 0;
  	foreach(l, node->plans)
  	{
+ 		Relation resultRel = estate->es_result_relation_info->ri_RelationDesc;
+ 		if (resultRel->rd_rel->relkind == RELKIND_VIEW)
+ 			ExecCheckViewTriggers(estate->es_result_relation_info, operation);
+ 
  		subplan = (Plan *) lfirst(l);
  		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
  		estate->es_result_relation_info++;
***************
*** 980,989 ****
  
  				if (operation == CMD_UPDATE || operation == CMD_DELETE)
  				{
! 					/* For UPDATE/DELETE, find the ctid junk attr now */
! 					j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
! 					if (!AttributeNumberIsValid(j->jf_junkAttNo))
! 						elog(ERROR, "could not find junk ctid column");
  				}
  
  				resultRelInfo->ri_junkFilter = j;
--- 1225,1248 ----
  
  				if (operation == CMD_UPDATE || operation == CMD_DELETE)
  				{
! 					/*
! 					 * For UPDATE/DELETE of real tables, find the ctid junk
! 					 * attr now. For VIEWs use the wholerow junk attr.
! 					 */
! 					Relation resultRel = resultRelInfo->ri_RelationDesc;
! 
! 					if (resultRel->rd_rel->relkind == RELKIND_VIEW)
! 					{
! 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
! 						if (!AttributeNumberIsValid(j->jf_junkAttNo))
! 							elog(ERROR, "could not find junk wholerow column");
! 					}
! 					else
! 					{
! 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
! 						if (!AttributeNumberIsValid(j->jf_junkAttNo))
! 							elog(ERROR, "could not find junk ctid column");
! 					}
  				}
  
  				resultRelInfo->ri_junkFilter = j;
*** ./src/backend/nodes/copyfuncs.c.orig	2010-08-15 00:06:57.000000000 +0100
--- ./src/backend/nodes/copyfuncs.c	2010-08-15 09:56:30.000000000 +0100
***************
*** 3227,3232 ****
--- 3227,3233 ----
  	COPY_NODE_FIELD(funcname);
  	COPY_NODE_FIELD(args);
  	COPY_SCALAR_FIELD(before);
+ 	COPY_SCALAR_FIELD(instead);
  	COPY_SCALAR_FIELD(row);
  	COPY_SCALAR_FIELD(events);
  	COPY_NODE_FIELD(columns);
*** ./src/backend/nodes/equalfuncs.c.orig	2010-08-15 00:09:01.000000000 +0100
--- ./src/backend/nodes/equalfuncs.c	2010-08-15 09:57:10.000000000 +0100
***************
*** 1685,1690 ****
--- 1685,1691 ----
  	COMPARE_NODE_FIELD(funcname);
  	COMPARE_NODE_FIELD(args);
  	COMPARE_SCALAR_FIELD(before);
+ 	COMPARE_SCALAR_FIELD(instead);
  	COMPARE_SCALAR_FIELD(row);
  	COMPARE_SCALAR_FIELD(events);
  	COMPARE_NODE_FIELD(columns);
*** ./src/backend/optimizer/plan/planmain.c.orig	2010-07-28 09:37:12.000000000 +0100
--- ./src/backend/optimizer/plan/planmain.c	2010-08-15 09:29:08.000000000 +0100
***************
*** 180,185 ****
--- 180,194 ----
  	add_base_rels_to_query(root, (Node *) parse->jointree);
  
  	/*
+ 	 * If the query target is a VIEW, it won't be in the jointree, but we
+ 	 * need a dummy RelOptInfo node for it. This need not have any stats in
+ 	 * it because it always just goes at the top of the plan tree.
+ 	 */
+ 	if (parse->resultRelation &&
+ 		root->simple_rel_array[parse->resultRelation] == NULL)
+ 		build_simple_rel(root, parse->resultRelation, RELOPT_OTHER_MEMBER_REL);
+ 
+ 	/*
  	 * Examine the targetlist and qualifications, adding entries to baserel
  	 * targetlists for all referenced Vars.  Restrict and join clauses are
  	 * added to appropriate lists belonging to the mentioned relations.  We
*** ./src/backend/optimizer/prep/preptlist.c.orig	2010-07-28 09:37:12.000000000 +0100
--- ./src/backend/optimizer/prep/preptlist.c	2010-08-15 09:35:45.000000000 +0100
***************
*** 38,44 ****
  
  
  static List *expand_targetlist(List *tlist, int command_type,
! 				  Index result_relation, List *range_table);
  
  
  /*
--- 38,44 ----
  
  
  static List *expand_targetlist(List *tlist, int command_type,
! 				  Index result_relation, Relation rel);
  
  
  /*
***************
*** 52,57 ****
--- 52,58 ----
  {
  	Query	   *parse = root->parse;
  	int			result_relation = parse->resultRelation;
+ 	bool		result_is_view = false;
  	List	   *range_table = parse->rtable;
  	CmdType		command_type = parse->commandType;
  	ListCell   *lc;
***************
*** 63,81 ****
  	if (result_relation)
  	{
  		RangeTblEntry *rte = rt_fetch(result_relation, range_table);
  
  		if (rte->subquery != NULL || rte->relid == InvalidOid)
  			elog(ERROR, "subquery cannot be result relation");
  	}
  
  	/*
! 	 * for heap_form_tuple to work, the targetlist must match the exact order
! 	 * of the attributes. We also need to fill in any missing attributes. -ay
! 	 * 10/94
  	 */
! 	if (command_type == CMD_INSERT || command_type == CMD_UPDATE)
! 		tlist = expand_targetlist(tlist, command_type,
! 								  result_relation, range_table);
  
  	/*
  	 * for "update" and "delete" queries, add ctid of the result relation into
--- 64,100 ----
  	if (result_relation)
  	{
  		RangeTblEntry *rte = rt_fetch(result_relation, range_table);
+ 		Relation	rel;
  
  		if (rte->subquery != NULL || rte->relid == InvalidOid)
  			elog(ERROR, "subquery cannot be result relation");
+ 
+ 		/*
+ 		 * Open the result relation.  We assume that the rewriter already
+ 		 * acquired at least AccessShareLock on it, so we need no lock here.
+ 		 */
+ 		rel = heap_open(getrelid(result_relation, range_table), NoLock);
+ 		result_is_view = rel->rd_rel->relkind == RELKIND_VIEW;
+ 
+ 		/*
+ 		 * for heap_form_tuple to work, the targetlist must match the exact
+ 		 * order of the attributes. We also need to fill in any missing
+ 		 * attributes. -ay 10/94
+ 		 */
+ 		if (command_type == CMD_INSERT || command_type == CMD_UPDATE)
+ 			tlist = expand_targetlist(tlist, command_type,
+ 									  result_relation, rel);
+ 
+ 		heap_close(rel, NoLock);
  	}
  
  	/*
! 	 * For an UPDATE, expand_targetlist already created a fresh tlist. For
! 	 * DELETE, better do a listCopy so that we don't destructively modify
! 	 * the original tlist (is this really necessary?).
  	 */
! 	if (command_type == CMD_DELETE)
! 		tlist = list_copy(tlist);
  
  	/*
  	 * for "update" and "delete" queries, add ctid of the result relation into
***************
*** 83,90 ****
  	 * ExecutePlan() will be able to identify the right tuple to replace or
  	 * delete.	This extra field is marked "junk" so that it is not stored
  	 * back into the tuple.
  	 */
! 	if (command_type == CMD_UPDATE || command_type == CMD_DELETE)
  	{
  		TargetEntry *tle;
  		Var		   *var;
--- 102,114 ----
  	 * ExecutePlan() will be able to identify the right tuple to replace or
  	 * delete.	This extra field is marked "junk" so that it is not stored
  	 * back into the tuple.
+ 	 *
+ 	 * We don't do this if the result relation is a view, since that won't
+ 	 * expose a ctid. The rewriter should have already added a wholerow TLE
+ 	 * for the view's subselect.
  	 */
! 	if (!result_is_view &&
! 		(command_type == CMD_UPDATE || command_type == CMD_DELETE))
  	{
  		TargetEntry *tle;
  		Var		   *var;
***************
*** 97,110 ****
  							  pstrdup("ctid"),
  							  true);
  
- 		/*
- 		 * For an UPDATE, expand_targetlist already created a fresh tlist. For
- 		 * DELETE, better do a listCopy so that we don't destructively modify
- 		 * the original tlist (is this really necessary?).
- 		 */
- 		if (command_type == CMD_DELETE)
- 			tlist = list_copy(tlist);
- 
  		tlist = lappend(tlist, tle);
  	}
  
--- 121,126 ----
***************
*** 169,187 ****
  		}
  		else
  		{
! 			/* Not a table, so we need the whole row as a junk var */
! 			var = makeVar(rc->rti,
! 						  InvalidAttrNumber,
! 						  RECORDOID,
! 						  -1,
! 						  0);
! 			snprintf(resname, sizeof(resname), "wholerow%u", rc->rti);
! 			tle = makeTargetEntry((Expr *) var,
! 								  list_length(tlist) + 1,
! 								  pstrdup(resname),
! 								  true);
! 			tlist = lappend(tlist, tle);
! 			rc->wholeAttNo = tle->resno;
  		}
  	}
  
--- 185,227 ----
  		}
  		else
  		{
! 			bool		exists = false;
! 			ListCell   *l;
! 
! 			/*
! 			 * Not a table, so we need the whole row as a junk var. If the
! 			 * query target is a view, the rewriter will have already added
! 			 * a whole row var, so don't add another for that RTE.
! 			 */
! 			foreach(l, tlist)
! 			{
! 				TargetEntry	   *tle = (TargetEntry *) lfirst(l);
! 
! 				if (tle->resjunk &&
! 					IsA(tle->expr, Var) &&
! 					((Var *) tle->expr)->varno == rc->rti &&
! 					strcmp(tle->resname, "wholerow") == 0)
! 				{
! 					exists = true;
! 					break;
! 				}
! 			}
! 
! 			if (!exists)
! 			{
! 				var = makeVar(rc->rti,
! 							  InvalidAttrNumber,
! 							  RECORDOID,
! 							  -1,
! 							  0);
! 				snprintf(resname, sizeof(resname), "wholerow%u", rc->rti);
! 				tle = makeTargetEntry((Expr *) var,
! 									  list_length(tlist) + 1,
! 									  pstrdup(resname),
! 									  true);
! 				tlist = lappend(tlist, tle);
! 				rc->wholeAttNo = tle->resno;
! 			}
  		}
  	}
  
***************
*** 241,251 ****
   */
  static List *
  expand_targetlist(List *tlist, int command_type,
! 				  Index result_relation, List *range_table)
  {
  	List	   *new_tlist = NIL;
  	ListCell   *tlist_item;
- 	Relation	rel;
  	int			attrno,
  				numattrs;
  
--- 281,290 ----
   */
  static List *
  expand_targetlist(List *tlist, int command_type,
! 				  Index result_relation, Relation rel)
  {
  	List	   *new_tlist = NIL;
  	ListCell   *tlist_item;
  	int			attrno,
  				numattrs;
  
***************
*** 256,267 ****
  	 * order; but we have to insert TLEs for any missing attributes.
  	 *
  	 * Scan the tuple description in the relation's relcache entry to make
! 	 * sure we have all the user attributes in the right order.  We assume
! 	 * that the rewriter already acquired at least AccessShareLock on the
! 	 * relation, so we need no lock here.
  	 */
- 	rel = heap_open(getrelid(result_relation, range_table), NoLock);
- 
  	numattrs = RelationGetNumberOfAttributes(rel);
  
  	for (attrno = 1; attrno <= numattrs; attrno++)
--- 295,302 ----
  	 * order; but we have to insert TLEs for any missing attributes.
  	 *
  	 * Scan the tuple description in the relation's relcache entry to make
! 	 * sure we have all the user attributes in the right order.
  	 */
  	numattrs = RelationGetNumberOfAttributes(rel);
  
  	for (attrno = 1; attrno <= numattrs; attrno++)
***************
*** 399,406 ****
  		tlist_item = lnext(tlist_item);
  	}
  
- 	heap_close(rel, NoLock);
- 
  	return new_tlist;
  }
  
--- 434,439 ----
*** ./src/backend/optimizer/util/plancat.c.orig	2010-07-28 09:37:11.000000000 +0100
--- ./src/backend/optimizer/util/plancat.c	2010-08-15 09:19:26.000000000 +0100
***************
*** 441,447 ****
  			*tuples = 1;
  			break;
  		default:
! 			/* else it has no disk storage; probably shouldn't get here? */
  			*pages = 0;
  			*tuples = 0;
  			break;
--- 441,452 ----
  			*tuples = 1;
  			break;
  		default:
! 			/*
! 			 * Else it has no disk storage; probably shouldn't get here,
! 			 * unless this is a VIEW which is the query target, in which
! 			 * case we don't care about the sizes, since it will always be
! 			 * at the top of the plan tree.
! 			 */
  			*pages = 0;
  			*tuples = 0;
  			break;
*** ./src/backend/parser/gram.y.orig	2010-08-15 00:11:26.000000000 +0100
--- ./src/backend/parser/gram.y	2010-08-15 09:48:07.000000000 +0100
***************
*** 245,251 ****
  %type <str>		OptSchemaName
  %type <list>	OptSchemaEltList
  
! %type <boolean> TriggerActionTime TriggerForSpec opt_trusted opt_restart_seqs
  
  %type <list>	TriggerEvents TriggerOneEvent
  %type <value>	TriggerFuncArg
--- 245,252 ----
  %type <str>		OptSchemaName
  %type <list>	OptSchemaEltList
  
! %type <boolean> TriggerForSpec opt_trusted opt_restart_seqs
! %type <ival>	TriggerActionTime
  
  %type <list>	TriggerEvents TriggerOneEvent
  %type <value>	TriggerFuncArg
***************
*** 3375,3381 ****
  					n->relation = $7;
  					n->funcname = $12;
  					n->args = $14;
! 					n->before = $4;
  					n->row = $8;
  					n->events = intVal(linitial($5));
  					n->columns = (List *) lsecond($5);
--- 3376,3383 ----
  					n->relation = $7;
  					n->funcname = $12;
  					n->args = $14;
! 					n->before = ($4 & 1) != 0;
! 					n->instead = ($4 & 2) != 0;
  					n->row = $8;
  					n->events = intVal(linitial($5));
  					n->columns = (List *) lsecond($5);
***************
*** 3397,3402 ****
--- 3399,3405 ----
  					n->funcname = $17;
  					n->args = $19;
  					n->before = FALSE;
+ 					n->instead = FALSE;
  					n->row = TRUE;
  					n->events = intVal(linitial($6));
  					n->columns = (List *) lsecond($6);
***************
*** 3410,3417 ****
  		;
  
  TriggerActionTime:
! 			BEFORE									{ $$ = TRUE; }
! 			| AFTER									{ $$ = FALSE; }
  		;
  
  TriggerEvents:
--- 3413,3421 ----
  		;
  
  TriggerActionTime:
! 			BEFORE									{ $$ = 1; }
! 			| AFTER									{ $$ = 0; }
! 			| INSTEAD OF							{ $$ = 2; }
  		;
  
  TriggerEvents:
*** ./src/backend/rewrite/rewriteHandler.c.orig	2010-07-28 09:37:27.000000000 +0100
--- ./src/backend/rewrite/rewriteHandler.c	2010-08-15 09:07:20.000000000 +0100
***************
*** 564,570 ****
   * planner will later insert NULLs for them, but there's no reason to slow
   * down rewriter processing with extra tlist nodes.)  Also, for both INSERT
   * and UPDATE, replace explicit DEFAULT specifications with column default
!  * expressions.
   *
   * 2. Merge multiple entries for the same target attribute, or declare error
   * if we can't.  Multiple entries are only allowed for INSERT/UPDATE of
--- 564,572 ----
   * planner will later insert NULLs for them, but there's no reason to slow
   * down rewriter processing with extra tlist nodes.)  Also, for both INSERT
   * and UPDATE, replace explicit DEFAULT specifications with column default
!  * expressions.  For an UPDATE on a VIEW, add tlist entries assigning any
!  * unassigned attributes their current values.  The RHS of such assignments
!  * will be rewritten to refer to the view's subselect node.
   *
   * 2. Merge multiple entries for the same target attribute, or declare error
   * if we can't.  Multiple entries are only allowed for INSERT/UPDATE of
***************
*** 724,729 ****
--- 726,753 ----
  										  false);
  		}
  
+ 		/*
+ 		 * For an UPDATE on a VIEW where the TLE is missing, assign the
+ 		 * current value, which will be rewritten as a query from the view's
+ 		 * subselect node by ApplyRetrieveRule().
+ 		 */
+ 		if (new_tle == NULL && commandType == CMD_UPDATE &&
+ 			target_relation->rd_rel->relkind == RELKIND_VIEW)
+ 		{
+ 			Node	   *new_expr;
+ 
+ 			new_expr = (Node *) makeVar(parsetree->resultRelation,
+ 										attrno,
+ 										att_tup->atttypid,
+ 										att_tup->atttypmod,
+ 										0);
+ 
+ 			new_tle = makeTargetEntry((Expr *) new_expr,
+ 									  attrno,
+ 									  pstrdup(NameStr(att_tup->attname)),
+ 									  false);
+ 		}
+ 
  		if (new_tle)
  			new_tlist = lappend(new_tlist, new_tle);
  	}
***************
*** 1149,1154 ****
--- 1173,1180 ----
  	RangeTblEntry *rte,
  			   *subrte;
  	RowMarkClause *rc;
+ 	Var		   *var;
+ 	TargetEntry *tle;
  
  	if (list_length(rule->actions) != 1)
  		elog(ERROR, "expected just one rule action");
***************
*** 1177,1220 ****
  	 */
  	rule_action = fireRIRrules(rule_action, activeRIRs, forUpdatePushedDown);
  
! 	/*
! 	 * VIEWs are really easy --- just plug the view query in as a subselect,
! 	 * replacing the relation's original RTE.
! 	 */
! 	rte = rt_fetch(rt_index, parsetree->rtable);
  
! 	rte->rtekind = RTE_SUBQUERY;
! 	rte->relid = InvalidOid;
! 	rte->subquery = rule_action;
! 	rte->inh = false;			/* must not be set for a subquery */
  
! 	/*
! 	 * We move the view's permission check data down to its rangetable. The
! 	 * checks will actually be done against the OLD entry therein.
! 	 */
! 	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
! 	Assert(subrte->relid == relation->rd_id);
! 	subrte->requiredPerms = rte->requiredPerms;
! 	subrte->checkAsUser = rte->checkAsUser;
! 	subrte->selectedCols = rte->selectedCols;
! 	subrte->modifiedCols = rte->modifiedCols;
! 
! 	rte->requiredPerms = 0;		/* no permission check on subquery itself */
! 	rte->checkAsUser = InvalidOid;
! 	rte->selectedCols = NULL;
! 	rte->modifiedCols = NULL;
  
! 	/*
! 	 * If FOR UPDATE/SHARE of view, mark all the contained tables as implicit
! 	 * FOR UPDATE/SHARE, the same as the parser would have done if the view's
! 	 * subquery had been written out explicitly.
! 	 *
! 	 * Note: we don't consider forUpdatePushedDown here; such marks will be
! 	 * made by recursing from the upper level in markQueryForLocking.
! 	 */
! 	if (rc != NULL)
! 		markQueryForLocking(rule_action, (Node *) rule_action->jointree,
! 							rc->forUpdate, rc->noWait, true);
  
  	return parsetree;
  }
--- 1203,1313 ----
  	 */
  	rule_action = fireRIRrules(rule_action, activeRIRs, forUpdatePushedDown);
  
! 	if (rt_index == parsetree->resultRelation)
! 	{
! 		/*
! 		 * We have a VIEW as the query target. For DELETE and UPDATE, we
! 		 * add a new subselect RTE using the view's query and adjust any
! 		 * VARs in the original query to point to this instead of the
! 		 * original view RTE.
! 		 *
! 		 * We keep the original view in the rtable as the query target, and
! 		 * any Vars in the returning list that reference it are left alone.
! 		 *
! 		 * The resulting jointree fromlist will not refer to the view RTE,
! 		 * and so the planner won't try to join to it. This will result in
! 		 * a plan with a ModifyTable node at the root, referring to the
! 		 * original view relation, and a subselect based on the view's query
! 		 * merged with any user conditions.
! 		 *
! 		 * For INSERTS we do nothing. The original view remains the query
! 		 * target.
! 		 */
! 		if (parsetree->commandType == CMD_DELETE ||
! 			parsetree->commandType == CMD_UPDATE)
! 		{
! 			List	*returningList = parsetree->returningList;
  
! 			/*
! 			 * Make a new subselect RTE from the view query and adjust the
! 			 * the original query to point to this instead of the original
! 			 * view, while preserving the view resultRelation and any
! 			 * returningList Vars.
! 			 */
! 			rte = copyObject(rt_fetch(rt_index, parsetree->rtable));
  
! 			rte->rtekind = RTE_SUBQUERY;
! 			rte->relid = InvalidOid;
! 			rte->subquery = rule_action;
! 			rte->inh = false;			/* must not be set for a subquery */
  
! 			parsetree->rtable = lappend(parsetree->rtable, rte);
! 			parsetree->returningList = NIL;
! 
! 			ChangeVarNodes((Node *) parsetree, rt_index,
! 						   list_length(parsetree->rtable), 0);
! 
! 			parsetree->resultRelation = rt_index;
! 			parsetree->returningList = returningList;
! 
! 			/*
! 			 * Add a "wholerow" junk TLE so that the executor can retrieve
! 			 * the old VIEW tuples to pass to the INSTEAD OF triggers.
! 			 */
! 			var = makeVar(list_length(parsetree->rtable),
! 						  InvalidAttrNumber,
! 						  RECORDOID,
! 						  -1,
! 						  0);
! 
! 			tle = makeTargetEntry((Expr *) var,
! 								  list_length(parsetree->targetList) + 1,
! 								  pstrdup("wholerow"),
! 								  true);
! 			parsetree->targetList = lappend(parsetree->targetList, tle);
! 		}
! 	}
! 	else
! 	{
! 		/*
! 		 * Selecting from the VIEW --- just plug the view query in as a
! 		 * subselect, replacing the relation's original RTE.
! 		 */
! 		rte = rt_fetch(rt_index, parsetree->rtable);
! 
! 		rte->rtekind = RTE_SUBQUERY;
! 		rte->relid = InvalidOid;
! 		rte->subquery = rule_action;
! 		rte->inh = false;			/* must not be set for a subquery */
! 
! 		/*
! 		 * We move the view's permission check data down to its rangetable.
! 		 * The checks will actually be done against the OLD entry therein.
! 		 */
! 		subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
! 		Assert(subrte->relid == relation->rd_id);
! 		subrte->requiredPerms = rte->requiredPerms;
! 		subrte->checkAsUser = rte->checkAsUser;
! 		subrte->selectedCols = rte->selectedCols;
! 		subrte->modifiedCols = rte->modifiedCols;
! 
! 		rte->requiredPerms = 0;	/* no permission check on subquery itself */
! 		rte->checkAsUser = InvalidOid;
! 		rte->selectedCols = NULL;
! 		rte->modifiedCols = NULL;
! 
! 		/*
! 		 * If FOR UPDATE/SHARE of view, mark all the contained tables as
! 		 * implicit FOR UPDATE/SHARE, the same as the parser would have done
! 		 * if the view's subquery had been written out explicitly.
! 		 *
! 		 * Note: we don't consider forUpdatePushedDown here; such marks will
! 		 * be made by recursing from the upper level in markQueryForLocking.
! 		 */
! 		if (rc != NULL)
! 			markQueryForLocking(rule_action, (Node *) rule_action->jointree,
! 								rc->forUpdate, rc->noWait, true);
! 	}
  
  	return parsetree;
  }
*** ./src/backend/utils/adt/ruleutils.c.orig	2010-08-15 00:14:30.000000000 +0100
--- ./src/backend/utils/adt/ruleutils.c	2010-08-15 10:45:04.000000000 +0100
***************
*** 545,550 ****
--- 545,552 ----
  
  	if (TRIGGER_FOR_BEFORE(trigrec->tgtype))
  		appendStringInfo(&buf, "BEFORE");
+ 	else if (TRIGGER_FOR_INSTEAD(trigrec->tgtype))
+ 		appendStringInfo(&buf, "INSTEAD OF");
  	else
  		appendStringInfo(&buf, "AFTER");
  	if (TRIGGER_FOR_INSERT(trigrec->tgtype))
*** ./src/backend/utils/adt/tsvector_op.c.orig	2010-08-15 00:18:37.000000000 +0100
--- ./src/backend/utils/adt/tsvector_op.c	2010-08-15 12:50:47.000000000 +0100
***************
*** 1259,1265 ****
  	trigdata = (TriggerData *) fcinfo->context;
  	if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
  		elog(ERROR, "tsvector_update_trigger: can't process STATEMENT events");
! 	if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
  		elog(ERROR, "tsvector_update_trigger: must be fired BEFORE event");
  
  	if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
--- 1259,1265 ----
  	trigdata = (TriggerData *) fcinfo->context;
  	if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
  		elog(ERROR, "tsvector_update_trigger: can't process STATEMENT events");
! 	if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event))
  		elog(ERROR, "tsvector_update_trigger: must be fired BEFORE event");
  
  	if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
*** ./src/bin/pg_dump/pg_dump.c.orig	2010-08-15 00:21:49.000000000 +0100
--- ./src/bin/pg_dump/pg_dump.c	2010-08-15 10:52:30.000000000 +0100
***************
*** 11745,11750 ****
--- 11745,11752 ----
  		findx = 0;
  		if (TRIGGER_FOR_BEFORE(tginfo->tgtype))
  			appendPQExpBuffer(query, "BEFORE");
+ 		if (TRIGGER_FOR_INSTEAD(tginfo->tgtype))
+ 			appendPQExpBuffer(query, "INSTEAD OF");
  		else
  			appendPQExpBuffer(query, "AFTER");
  		if (TRIGGER_FOR_INSERT(tginfo->tgtype))
*** ./src/bin/psql/describe.c.orig	2010-08-03 17:16:15.000000000 +0100
--- ./src/bin/psql/describe.c	2010-08-15 10:50:29.000000000 +0100
***************
*** 1820,1827 ****
--- 1820,1838 ----
  			}
  			PQclear(result);
  		}
+ 	}
  
+ 	/*
+ 	 * Print triggers next.
+ 	 * This is part of the footer information about a table, but may also
+ 	 * apply to a view.
+ 	 */
+ 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v')
+ 	{
  		/* print triggers (but only user-defined triggers) */
+ 		PGresult   *result = NULL;
+ 		int			tuples = 0;
+ 
  		if (tableinfo.hastriggers)
  		{
  			printfPQExpBuffer(&buf,
***************
*** 1935,1942 ****
--- 1946,1963 ----
  			}
  			PQclear(result);
  		}
+ 	}
  
+ 	/*
+ 	 * Done printing trigger information for view or table.
+ 	 * Finish printing the footer information about a table.
+ 	 */
+ 	if (tableinfo.relkind == 'r')
+ 	{
  		/* print inherited tables */
+ 		PGresult   *result = NULL;
+ 		int			tuples = 0;
+ 
  		printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno", oid);
  
  		result = PSQLexec(buf.data, false);
*** ./src/include/catalog/pg_trigger.h.orig	2010-07-31 10:05:35.000000000 +0100
--- ./src/include/catalog/pg_trigger.h	2010-08-15 09:50:50.000000000 +0100
***************
*** 91,96 ****
--- 91,97 ----
  #define TRIGGER_TYPE_DELETE				(1 << 3)
  #define TRIGGER_TYPE_UPDATE				(1 << 4)
  #define TRIGGER_TYPE_TRUNCATE			(1 << 5)
+ #define TRIGGER_TYPE_INSTEAD			(1 << 6)
  
  /* Macros for manipulating tgtype */
  #define TRIGGER_CLEAR_TYPE(type)		((type) = 0)
***************
*** 101,106 ****
--- 102,108 ----
  #define TRIGGER_SETT_DELETE(type)		((type) |= TRIGGER_TYPE_DELETE)
  #define TRIGGER_SETT_UPDATE(type)		((type) |= TRIGGER_TYPE_UPDATE)
  #define TRIGGER_SETT_TRUNCATE(type)		((type) |= TRIGGER_TYPE_TRUNCATE)
+ #define TRIGGER_SETT_INSTEAD(type)		((type) |= TRIGGER_TYPE_INSTEAD)
  
  #define TRIGGER_FOR_ROW(type)			((type) & TRIGGER_TYPE_ROW)
  #define TRIGGER_FOR_BEFORE(type)		((type) & TRIGGER_TYPE_BEFORE)
***************
*** 108,112 ****
--- 110,115 ----
  #define TRIGGER_FOR_DELETE(type)		((type) & TRIGGER_TYPE_DELETE)
  #define TRIGGER_FOR_UPDATE(type)		((type) & TRIGGER_TYPE_UPDATE)
  #define TRIGGER_FOR_TRUNCATE(type)		((type) & TRIGGER_TYPE_TRUNCATE)
+ #define TRIGGER_FOR_INSTEAD(type)		((type) & TRIGGER_TYPE_INSTEAD)
  
  #endif   /* PG_TRIGGER_H */
*** ./src/include/commands/trigger.h.orig	2010-08-15 00:24:40.000000000 +0100
--- ./src/include/commands/trigger.h	2010-08-15 10:24:18.000000000 +0100
***************
*** 53,58 ****
--- 53,59 ----
  #define TRIGGER_EVENT_OPMASK			0x00000003
  #define TRIGGER_EVENT_ROW				0x00000004
  #define TRIGGER_EVENT_BEFORE			0x00000008
+ #define TRIGGER_EVENT_INSTEAD			0x00000040
  
  /* More TriggerEvent flags, used only within trigger.c */
  
***************
*** 84,91 ****
  #define TRIGGER_FIRED_BEFORE(event)				\
  		((TriggerEvent) (event) & TRIGGER_EVENT_BEFORE)
  
  #define TRIGGER_FIRED_AFTER(event)				\
! 		(!TRIGGER_FIRED_BEFORE (event))
  
  /*
   * Definitions for the replication role based firing.
--- 85,95 ----
  #define TRIGGER_FIRED_BEFORE(event)				\
  		((TriggerEvent) (event) & TRIGGER_EVENT_BEFORE)
  
+ #define TRIGGER_FIRED_INSTEAD(event)				\
+ 		((TriggerEvent) (event) & TRIGGER_EVENT_INSTEAD)
+ 
  #define TRIGGER_FIRED_AFTER(event)				\
! 		(!TRIGGER_FIRED_BEFORE (event) && !TRIGGER_FIRED_INSTEAD (event))
  
  /*
   * Definitions for the replication role based firing.
***************
*** 135,140 ****
--- 139,147 ----
  					 ResultRelInfo *relinfo,
  					 HeapTuple trigtuple,
  					 List *recheckIndexes);
+ extern HeapTuple ExecIRInsertTriggers(EState *estate,
+ 					 ResultRelInfo *relinfo,
+ 					 HeapTuple trigtuple);
  extern void ExecBSDeleteTriggers(EState *estate,
  					 ResultRelInfo *relinfo);
  extern void ExecASDeleteTriggers(EState *estate,
***************
*** 146,151 ****
--- 153,161 ----
  extern void ExecARDeleteTriggers(EState *estate,
  					 ResultRelInfo *relinfo,
  					 ItemPointer tupleid);
+ extern HeapTuple ExecIRDeleteTriggers(EState *estate,
+ 					 ResultRelInfo *relinfo,
+ 					 HeapTuple trigtuple);
  extern void ExecBSUpdateTriggers(EState *estate,
  					 ResultRelInfo *relinfo);
  extern void ExecASUpdateTriggers(EState *estate,
***************
*** 160,165 ****
--- 170,179 ----
  					 ItemPointer tupleid,
  					 HeapTuple newtuple,
  					 List *recheckIndexes);
+ extern HeapTuple ExecIRUpdateTriggers(EState *estate,
+ 					 ResultRelInfo *relinfo,
+ 					 HeapTuple oldtuple,
+ 					 HeapTuple newtuple);
  extern void ExecBSTruncateTriggers(EState *estate,
  					   ResultRelInfo *relinfo);
  extern void ExecASTruncateTriggers(EState *estate,
*** ./src/include/nodes/parsenodes.h.orig	2010-08-15 00:26:54.000000000 +0100
--- ./src/include/nodes/parsenodes.h	2010-08-15 09:45:52.000000000 +0100
***************
*** 1608,1613 ****
--- 1608,1614 ----
  	List	   *funcname;		/* qual. name of function to call */
  	List	   *args;			/* list of (T_String) Values or NIL */
  	bool		before;			/* BEFORE/AFTER */
+ 	bool		instead;		/* INSTEAD OF (overrides BEFORE/AFTER) */
  	bool		row;			/* ROW/STATEMENT */
  	/* events uses the TRIGGER_TYPE bits defined in catalog/pg_trigger.h */
  	int16		events;			/* INSERT/UPDATE/DELETE/TRUNCATE */
*** ./src/include/utils/rel.h.orig	2010-08-15 00:30:33.000000000 +0100
--- ./src/include/utils/rel.h	2010-08-15 10:20:43.000000000 +0100
***************
*** 82,91 ****
--- 82,93 ----
  	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_instead_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_instead_row[TRIGGER_NUM_EVENT_CLASSES];
  	int		   *tg_after_statement[TRIGGER_NUM_EVENT_CLASSES];
  
  	/* The actual array of triggers is here */
*** ./src/pl/plperl/plperl.c.orig	2010-07-31 12:19:30.000000000 +0100
--- ./src/pl/plperl/plperl.c	2010-08-15 12:44:58.000000000 +0100
***************
*** 954,959 ****
--- 954,961 ----
  		when = "BEFORE";
  	else if (TRIGGER_FIRED_AFTER(tdata->tg_event))
  		when = "AFTER";
+ 	else if (TRIGGER_FIRED_INSTEAD(tdata->tg_event))
+ 		when = "INSTEAD";
  	else
  		when = "UNKNOWN";
  	hv_store_string(hv, "when", newSVstring(when));
*** ./src/pl/plpgsql/src/pl_exec.c.orig	2010-08-15 00:34:14.000000000 +0100
--- ./src/pl/plpgsql/src/pl_exec.c	2010-08-15 12:44:01.000000000 +0100
***************
*** 581,588 ****
  		var->value = CStringGetTextDatum("BEFORE");
  	else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
  		var->value = CStringGetTextDatum("AFTER");
  	else
! 		elog(ERROR, "unrecognized trigger execution time: not BEFORE or AFTER");
  	var->isnull = false;
  	var->freeval = true;
  
--- 581,590 ----
  		var->value = CStringGetTextDatum("BEFORE");
  	else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
  		var->value = CStringGetTextDatum("AFTER");
+ 	else if (TRIGGER_FIRED_INSTEAD(trigdata->tg_event))
+ 		var->value = CStringGetTextDatum("INSTEAD");
  	else
! 		elog(ERROR, "unrecognized trigger execution time: not BEFORE, AFTER or INSTEAD");
  	var->isnull = false;
  	var->freeval = true;
  
*** ./src/pl/plpython/plpython.c.orig	2010-07-31 12:26:04.000000000 +0100
--- ./src/pl/plpython/plpython.c	2010-08-15 12:46:11.000000000 +0100
***************
*** 846,851 ****
--- 846,853 ----
  			pltwhen = PyString_FromString("BEFORE");
  		else if (TRIGGER_FIRED_AFTER(tdata->tg_event))
  			pltwhen = PyString_FromString("AFTER");
+ 		else if (TRIGGER_FIRED_INSTEAD(tdata->tg_event))
+ 			pltwhen = PyString_FromString("INSTEAD");
  		else
  		{
  			elog(ERROR, "unrecognized WHEN tg_event: %u", tdata->tg_event);
*** ./src/pl/tcl/pltcl.c.orig	2010-07-31 12:26:53.000000000 +0100
--- ./src/pl/tcl/pltcl.c	2010-08-15 12:47:27.000000000 +0100
***************
*** 822,827 ****
--- 822,829 ----
  			Tcl_DStringAppendElement(&tcl_cmd, "BEFORE");
  		else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
  			Tcl_DStringAppendElement(&tcl_cmd, "AFTER");
+ 		else if (TRIGGER_FIRED_INSTEAD(trigdata->tg_event))
+ 			Tcl_DStringAppendElement(&tcl_cmd, "INSTEAD");
  		else
  			elog(ERROR, "unrecognized WHEN tg_event: %u", trigdata->tg_event);
  
*** ./src/test/regress/expected/triggers.out.orig	2010-08-15 15:55:21.000000000 +0100
--- ./src/test/regress/expected/triggers.out	2010-08-15 16:04:07.000000000 +0100
***************
*** 791,793 ****
--- 791,1191 ----
  
  DROP TABLE min_updates_test;
  DROP TABLE min_updates_test_oids;
+ --
+ -- Test triggers on views
+ --
+ CREATE VIEW main_view AS SELECT a, b FROM main_table;
+ -- Updates should fail without rules or triggers
+ INSERT INTO main_view VALUES (1,2);
+ ERROR:  cannot insert into a view
+ HINT:  You need an unconditional ON INSERT DO INSTEAD rule or an INSTEAD OF INSERT trigger.
+ UPDATE main_view SET b=20 WHERE a=50;
+ ERROR:  cannot update a view
+ HINT:  You need an unconditional ON UPDATE DO INSTEAD rule or an INSTEAD OF UPDATE trigger.
+ DELETE FROM main_view WHERE a=50;
+ ERROR:  cannot delete from a view
+ HINT:  You need an unconditional ON DELETE DO INSTEAD rule or an INSTEAD OF DELETE trigger.
+ -- Should fail even when there are no matching rows
+ DELETE FROM main_view WHERE a=51;
+ ERROR:  cannot delete from a view
+ HINT:  You need an unconditional ON DELETE DO INSTEAD rule or an INSTEAD OF DELETE trigger.
+ -- Triggers that aren't allowed on views (these should all fail)
+ CREATE TRIGGER invalid_trig BEFORE INSERT ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_ins_row');
+ ERROR:  "main_view" is a view
+ HINT:  Define ROW-level BEFORE and AFTER triggers on tables
+ CREATE TRIGGER invalid_trig BEFORE UPDATE ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_upd_row');
+ ERROR:  "main_view" is a view
+ HINT:  Define ROW-level BEFORE and AFTER triggers on tables
+ CREATE TRIGGER invalid_trig BEFORE DELETE ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_del_row');
+ ERROR:  "main_view" is a view
+ HINT:  Define ROW-level BEFORE and AFTER triggers on tables
+ CREATE TRIGGER invalid_trig BEFORE TRUNCATE ON main_view
+ EXECUTE PROCEDURE trigger_func('before_tru_row');
+ ERROR:  "main_view" is a view
+ HINT:  Define TRUNCATE triggers on tables
+ CREATE TRIGGER invalid_trig AFTER INSERT ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_ins_row');
+ ERROR:  "main_view" is a view
+ HINT:  Define ROW-level BEFORE and AFTER triggers on tables
+ CREATE TRIGGER invalid_trig AFTER UPDATE ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_upd_row');
+ ERROR:  "main_view" is a view
+ HINT:  Define ROW-level BEFORE and AFTER triggers on tables
+ CREATE TRIGGER invalid_trig AFTER DELETE ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_del_row');
+ ERROR:  "main_view" is a view
+ HINT:  Define ROW-level BEFORE and AFTER triggers on tables
+ CREATE TRIGGER invalid_trig AFTER TRUNCATE ON main_view
+ EXECUTE PROCEDURE trigger_func('before_tru_row');
+ ERROR:  "main_view" is a view
+ HINT:  Define TRUNCATE triggers on tables
+ -- INSTEAD OF trigger function
+ CREATE OR REPLACE FUNCTION instead_of_trigger()  RETURNS trigger 
+ LANGUAGE plpgsql AS $$
+ 
+ declare
+ 
+ 	argstr text;
+ 	relid text;
+ 
+ begin
+ 
+ 	relid := TG_relid::regclass;
+ 
+ 	-- plpgsql can't discover its trigger data in a hash like perl and python
+ 	-- can, or by a sort of reflection like tcl can, 
+ 	-- so we have to hard code the names.
+ 	raise NOTICE 'TG_NAME: %', TG_name;
+ 	raise NOTICE 'TG_WHEN: %', TG_when;
+ 	raise NOTICE 'TG_LEVEL: %', TG_level;
+ 	raise NOTICE 'TG_OP: %', TG_op;
+ 	raise NOTICE 'TG_RELID::regclass: %', relid;
+ 	raise NOTICE 'TG_RELNAME: %', TG_relname;
+ 	raise NOTICE 'TG_TABLE_NAME: %', TG_table_name;
+ 	raise NOTICE 'TG_TABLE_SCHEMA: %', TG_table_schema;
+ 	raise NOTICE 'TG_NARGS: %', TG_nargs;
+ 
+ 	argstr := '[';
+ 	for i in 0 .. TG_nargs - 1 loop
+ 		if i > 0 then
+ 			argstr := argstr || ', ';
+ 		end if;
+ 		argstr := argstr || TG_argv[i];
+ 	end loop;
+ 	argstr := argstr || ']';
+ 	raise NOTICE 'TG_ARGV: %', argstr;
+ 
+ 	if TG_OP != 'INSERT' then
+ 		raise NOTICE 'OLD: %', OLD;
+ 	end if;
+ 
+ 	if TG_OP != 'DELETE' then
+ 		raise NOTICE 'NEW: %', NEW;
+ 	end if;
+ 
+ 	if TG_OP = 'INSERT' then
+ 		INSERT INTO main_table VALUES (NEW.a, NEW.b);
+ 		RETURN NEW;
+ 	end if;
+ 
+ 	if TG_OP = 'UPDATE' then
+ 		UPDATE main_table SET a=NEW.a, b=NEW.b WHERE a=OLD.a AND b=OLD.b;
+ 		if NOT FOUND then RETURN NULL; end if;
+ 		RETURN NEW;
+ 	end if;
+ 
+ 	if TG_OP = 'DELETE' then
+ 		DELETE FROM main_table WHERE a=OLD.a AND b=OLD.b;
+ 		if NOT FOUND then RETURN NULL; end if;
+ 		RETURN OLD;
+ 	end if;
+ end;
+ $$;
+ -- The following INSTEAD OF triggers shouldn't be allowed
+ CREATE TRIGGER invalid_trig INSTEAD OF INSERT ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE instead_of_trigger('instead_of_ins');
+ ERROR:  "main_table" is a table
+ HINT:  Define INSTEAD OF triggers on views
+ CREATE TRIGGER invalid_trig INSTEAD OF UPDATE ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE instead_of_trigger('instead_of_upd');
+ ERROR:  "main_table" is a table
+ HINT:  Define INSTEAD OF triggers on views
+ CREATE TRIGGER invalid_trig INSTEAD OF DELETE ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE instead_of_trigger('instead_of_del');
+ ERROR:  "main_table" is a table
+ HINT:  Define INSTEAD OF triggers on views
+ CREATE TRIGGER invalid_trig INSTEAD OF UPDATE ON main_view
+ FOR EACH ROW WHEN (OLD.a <> NEW.a) EXECUTE PROCEDURE instead_of_trigger('instead_of_upd');
+ ERROR:  cannot use a WHEN condition with an INSTEAD OF trigger
+ CREATE TRIGGER invalid_trig INSTEAD OF UPDATE OF a ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE instead_of_trigger('instead_of_upd');
+ ERROR:  cannot use a column list with an INSTEAD OF trigger
+ CREATE TRIGGER invalid_trig INSTEAD OF UPDATE ON main_view
+ EXECUTE PROCEDURE instead_of_trigger('instead_of_upd');
+ ERROR:  STATEMENT-level INSTEAD OF triggers are not supported
+ HINT:  Use FOR EACH ROW for INSTEAD OF triggers
+ -- Valid INSTEAD OF triggers
+ CREATE TRIGGER instead_of_insert_trig INSTEAD OF INSERT ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE instead_of_trigger('instead_of_ins');
+ CREATE TRIGGER instead_of_update_trig INSTEAD OF UPDATE ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE instead_of_trigger('instead_of_upd');
+ CREATE TRIGGER instead_of_delete_trig INSTEAD OF DELETE ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE instead_of_trigger('instead_of_del');
+ -- Valid BEFORE and AFTER statement triggers
+ CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_view
+ FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_view_ins_stmt');
+ CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_view
+ FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_view_ins_stmt');
+ \set QUIET false
+ INSERT INTO main_view VALUES (20, 30);
+ NOTICE:  trigger_func(before_view_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT
+ NOTICE:  TG_NAME: instead_of_insert_trig
+ NOTICE:  TG_WHEN: INSTEAD
+ NOTICE:  TG_LEVEL: ROW
+ NOTICE:  TG_OP: INSERT
+ NOTICE:  TG_RELID::regclass: main_view
+ NOTICE:  TG_RELNAME: main_view
+ NOTICE:  TG_TABLE_NAME: main_view
+ NOTICE:  TG_TABLE_SCHEMA: public
+ NOTICE:  TG_NARGS: 1
+ NOTICE:  TG_ARGV: [instead_of_ins]
+ NOTICE:  NEW: (20,30)
+ NOTICE:  trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT
+ CONTEXT:  SQL statement "INSERT INTO main_table VALUES (NEW.a, NEW.b)"
+ PL/pgSQL function "instead_of_trigger" line 44 at SQL statement
+ NOTICE:  trigger_func(after_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT
+ CONTEXT:  SQL statement "INSERT INTO main_table VALUES (NEW.a, NEW.b)"
+ PL/pgSQL function "instead_of_trigger" line 44 at SQL statement
+ NOTICE:  trigger_func(after_view_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT
+ INSERT 0 1
+ INSERT INTO main_view VALUES (21, 31) RETURNING a, b;
+ NOTICE:  trigger_func(before_view_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT
+ NOTICE:  TG_NAME: instead_of_insert_trig
+ NOTICE:  TG_WHEN: INSTEAD
+ NOTICE:  TG_LEVEL: ROW
+ NOTICE:  TG_OP: INSERT
+ NOTICE:  TG_RELID::regclass: main_view
+ NOTICE:  TG_RELNAME: main_view
+ NOTICE:  TG_TABLE_NAME: main_view
+ NOTICE:  TG_TABLE_SCHEMA: public
+ NOTICE:  TG_NARGS: 1
+ NOTICE:  TG_ARGV: [instead_of_ins]
+ NOTICE:  NEW: (21,31)
+ NOTICE:  trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT
+ CONTEXT:  SQL statement "INSERT INTO main_table VALUES (NEW.a, NEW.b)"
+ PL/pgSQL function "instead_of_trigger" line 44 at SQL statement
+ NOTICE:  trigger_func(after_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT
+ CONTEXT:  SQL statement "INSERT INTO main_table VALUES (NEW.a, NEW.b)"
+ PL/pgSQL function "instead_of_trigger" line 44 at SQL statement
+ NOTICE:  trigger_func(after_view_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT
+  a  | b  
+ ----+----
+  21 | 31
+ (1 row)
+ 
+ INSERT 0 1
+ UPDATE main_view SET b=31 WHERE a=20;
+ NOTICE:  TG_NAME: instead_of_update_trig
+ NOTICE:  TG_WHEN: INSTEAD
+ NOTICE:  TG_LEVEL: ROW
+ NOTICE:  TG_OP: UPDATE
+ NOTICE:  TG_RELID::regclass: main_view
+ NOTICE:  TG_RELNAME: main_view
+ NOTICE:  TG_TABLE_NAME: main_view
+ NOTICE:  TG_TABLE_SCHEMA: public
+ NOTICE:  TG_NARGS: 1
+ NOTICE:  TG_ARGV: [instead_of_upd]
+ NOTICE:  OLD: (20,30)
+ NOTICE:  NEW: (20,31)
+ NOTICE:  trigger_func(before_upd_a_stmt) called: action = UPDATE, when = BEFORE, level = STATEMENT
+ CONTEXT:  SQL statement "UPDATE main_table SET a=NEW.a, b=NEW.b WHERE a=OLD.a AND b=OLD.b"
+ PL/pgSQL function "instead_of_trigger" line 49 at SQL statement
+ NOTICE:  trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW
+ CONTEXT:  SQL statement "UPDATE main_table SET a=NEW.a, b=NEW.b WHERE a=OLD.a AND b=OLD.b"
+ PL/pgSQL function "instead_of_trigger" line 49 at SQL statement
+ NOTICE:  trigger_func(after_upd_b_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
+ CONTEXT:  SQL statement "UPDATE main_table SET a=NEW.a, b=NEW.b WHERE a=OLD.a AND b=OLD.b"
+ PL/pgSQL function "instead_of_trigger" line 49 at SQL statement
+ NOTICE:  trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
+ CONTEXT:  SQL statement "UPDATE main_table SET a=NEW.a, b=NEW.b WHERE a=OLD.a AND b=OLD.b"
+ PL/pgSQL function "instead_of_trigger" line 49 at SQL statement
+ UPDATE 0
+ UPDATE main_view SET b=32 WHERE a=21 AND b=31 RETURNING a,b;
+ NOTICE:  TG_NAME: instead_of_update_trig
+ NOTICE:  TG_WHEN: INSTEAD
+ NOTICE:  TG_LEVEL: ROW
+ NOTICE:  TG_OP: UPDATE
+ NOTICE:  TG_RELID::regclass: main_view
+ NOTICE:  TG_RELNAME: main_view
+ NOTICE:  TG_TABLE_NAME: main_view
+ NOTICE:  TG_TABLE_SCHEMA: public
+ NOTICE:  TG_NARGS: 1
+ NOTICE:  TG_ARGV: [instead_of_upd]
+ NOTICE:  OLD: (21,31)
+ NOTICE:  NEW: (21,32)
+ NOTICE:  trigger_func(before_upd_a_stmt) called: action = UPDATE, when = BEFORE, level = STATEMENT
+ CONTEXT:  SQL statement "UPDATE main_table SET a=NEW.a, b=NEW.b WHERE a=OLD.a AND b=OLD.b"
+ PL/pgSQL function "instead_of_trigger" line 49 at SQL statement
+ NOTICE:  trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW
+ CONTEXT:  SQL statement "UPDATE main_table SET a=NEW.a, b=NEW.b WHERE a=OLD.a AND b=OLD.b"
+ PL/pgSQL function "instead_of_trigger" line 49 at SQL statement
+ NOTICE:  trigger_func(after_upd_b_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
+ CONTEXT:  SQL statement "UPDATE main_table SET a=NEW.a, b=NEW.b WHERE a=OLD.a AND b=OLD.b"
+ PL/pgSQL function "instead_of_trigger" line 49 at SQL statement
+ NOTICE:  trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
+ CONTEXT:  SQL statement "UPDATE main_table SET a=NEW.a, b=NEW.b WHERE a=OLD.a AND b=OLD.b"
+ PL/pgSQL function "instead_of_trigger" line 49 at SQL statement
+  a | b 
+ ---+---
+ (0 rows)
+ 
+ UPDATE 0
+ DROP TRIGGER before_upd_a_row_trig ON main_table;
+ DROP TRIGGER
+ UPDATE main_view SET b=31 WHERE a=20;
+ NOTICE:  TG_NAME: instead_of_update_trig
+ NOTICE:  TG_WHEN: INSTEAD
+ NOTICE:  TG_LEVEL: ROW
+ NOTICE:  TG_OP: UPDATE
+ NOTICE:  TG_RELID::regclass: main_view
+ NOTICE:  TG_RELNAME: main_view
+ NOTICE:  TG_TABLE_NAME: main_view
+ NOTICE:  TG_TABLE_SCHEMA: public
+ NOTICE:  TG_NARGS: 1
+ NOTICE:  TG_ARGV: [instead_of_upd]
+ NOTICE:  OLD: (20,30)
+ NOTICE:  NEW: (20,31)
+ NOTICE:  trigger_func(before_upd_a_stmt) called: action = UPDATE, when = BEFORE, level = STATEMENT
+ CONTEXT:  SQL statement "UPDATE main_table SET a=NEW.a, b=NEW.b WHERE a=OLD.a AND b=OLD.b"
+ PL/pgSQL function "instead_of_trigger" line 49 at SQL statement
+ NOTICE:  trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW
+ CONTEXT:  SQL statement "UPDATE main_table SET a=NEW.a, b=NEW.b WHERE a=OLD.a AND b=OLD.b"
+ PL/pgSQL function "instead_of_trigger" line 49 at SQL statement
+ NOTICE:  trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW
+ CONTEXT:  SQL statement "UPDATE main_table SET a=NEW.a, b=NEW.b WHERE a=OLD.a AND b=OLD.b"
+ PL/pgSQL function "instead_of_trigger" line 49 at SQL statement
+ NOTICE:  trigger_func(after_upd_b_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
+ CONTEXT:  SQL statement "UPDATE main_table SET a=NEW.a, b=NEW.b WHERE a=OLD.a AND b=OLD.b"
+ PL/pgSQL function "instead_of_trigger" line 49 at SQL statement
+ NOTICE:  trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
+ CONTEXT:  SQL statement "UPDATE main_table SET a=NEW.a, b=NEW.b WHERE a=OLD.a AND b=OLD.b"
+ PL/pgSQL function "instead_of_trigger" line 49 at SQL statement
+ UPDATE 1
+ UPDATE main_view SET b=32 WHERE a=21 AND b=31 RETURNING a,b;
+ NOTICE:  TG_NAME: instead_of_update_trig
+ NOTICE:  TG_WHEN: INSTEAD
+ NOTICE:  TG_LEVEL: ROW
+ NOTICE:  TG_OP: UPDATE
+ NOTICE:  TG_RELID::regclass: main_view
+ NOTICE:  TG_RELNAME: main_view
+ NOTICE:  TG_TABLE_NAME: main_view
+ NOTICE:  TG_TABLE_SCHEMA: public
+ NOTICE:  TG_NARGS: 1
+ NOTICE:  TG_ARGV: [instead_of_upd]
+ NOTICE:  OLD: (21,31)
+ NOTICE:  NEW: (21,32)
+ NOTICE:  trigger_func(before_upd_a_stmt) called: action = UPDATE, when = BEFORE, level = STATEMENT
+ CONTEXT:  SQL statement "UPDATE main_table SET a=NEW.a, b=NEW.b WHERE a=OLD.a AND b=OLD.b"
+ PL/pgSQL function "instead_of_trigger" line 49 at SQL statement
+ NOTICE:  trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW
+ CONTEXT:  SQL statement "UPDATE main_table SET a=NEW.a, b=NEW.b WHERE a=OLD.a AND b=OLD.b"
+ PL/pgSQL function "instead_of_trigger" line 49 at SQL statement
+ NOTICE:  trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW
+ CONTEXT:  SQL statement "UPDATE main_table SET a=NEW.a, b=NEW.b WHERE a=OLD.a AND b=OLD.b"
+ PL/pgSQL function "instead_of_trigger" line 49 at SQL statement
+ NOTICE:  trigger_func(after_upd_b_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
+ CONTEXT:  SQL statement "UPDATE main_table SET a=NEW.a, b=NEW.b WHERE a=OLD.a AND b=OLD.b"
+ PL/pgSQL function "instead_of_trigger" line 49 at SQL statement
+ NOTICE:  trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
+ CONTEXT:  SQL statement "UPDATE main_table SET a=NEW.a, b=NEW.b WHERE a=OLD.a AND b=OLD.b"
+ PL/pgSQL function "instead_of_trigger" line 49 at SQL statement
+  a  | b  
+ ----+----
+  21 | 32
+ (1 row)
+ 
+ UPDATE 1
+ DELETE FROM main_view WHERE a IN (20,21);
+ NOTICE:  TG_NAME: instead_of_delete_trig
+ NOTICE:  TG_WHEN: INSTEAD
+ NOTICE:  TG_LEVEL: ROW
+ NOTICE:  TG_OP: DELETE
+ NOTICE:  TG_RELID::regclass: main_view
+ NOTICE:  TG_RELNAME: main_view
+ NOTICE:  TG_TABLE_NAME: main_view
+ NOTICE:  TG_TABLE_SCHEMA: public
+ NOTICE:  TG_NARGS: 1
+ NOTICE:  TG_ARGV: [instead_of_del]
+ NOTICE:  OLD: (21,10)
+ NOTICE:  TG_NAME: instead_of_delete_trig
+ NOTICE:  TG_WHEN: INSTEAD
+ NOTICE:  TG_LEVEL: ROW
+ NOTICE:  TG_OP: DELETE
+ NOTICE:  TG_RELID::regclass: main_view
+ NOTICE:  TG_RELNAME: main_view
+ NOTICE:  TG_TABLE_NAME: main_view
+ NOTICE:  TG_TABLE_SCHEMA: public
+ NOTICE:  TG_NARGS: 1
+ NOTICE:  TG_ARGV: [instead_of_del]
+ NOTICE:  OLD: (20,31)
+ NOTICE:  TG_NAME: instead_of_delete_trig
+ NOTICE:  TG_WHEN: INSTEAD
+ NOTICE:  TG_LEVEL: ROW
+ NOTICE:  TG_OP: DELETE
+ NOTICE:  TG_RELID::regclass: main_view
+ NOTICE:  TG_RELNAME: main_view
+ NOTICE:  TG_TABLE_NAME: main_view
+ NOTICE:  TG_TABLE_SCHEMA: public
+ NOTICE:  TG_NARGS: 1
+ NOTICE:  TG_ARGV: [instead_of_del]
+ NOTICE:  OLD: (21,32)
+ DELETE 3
+ DELETE FROM main_view WHERE a=31 RETURNING a,b;
+ NOTICE:  TG_NAME: instead_of_delete_trig
+ NOTICE:  TG_WHEN: INSTEAD
+ NOTICE:  TG_LEVEL: ROW
+ NOTICE:  TG_OP: DELETE
+ NOTICE:  TG_RELID::regclass: main_view
+ NOTICE:  TG_RELNAME: main_view
+ NOTICE:  TG_TABLE_NAME: main_view
+ NOTICE:  TG_TABLE_SCHEMA: public
+ NOTICE:  TG_NARGS: 1
+ NOTICE:  TG_ARGV: [instead_of_del]
+ NOTICE:  OLD: (31,10)
+  a  | b  
+ ----+----
+  31 | 10
+ (1 row)
+ 
+ DELETE 1
+ \set QUIET true
+ \d main_view
+    View "public.main_view"
+  Column |  Type   | Modifiers 
+ --------+---------+-----------
+  a      | integer | 
+  b      | integer | 
+ Triggers:
+     after_ins_stmt_trig AFTER INSERT ON main_view FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_view_ins_stmt')
+     before_ins_stmt_trig BEFORE INSERT ON main_view FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_view_ins_stmt')
+     instead_of_delete_trig INSTEAD OF DELETE ON main_view FOR EACH ROW EXECUTE PROCEDURE instead_of_trigger('instead_of_del')
+     instead_of_insert_trig INSTEAD OF INSERT ON main_view FOR EACH ROW EXECUTE PROCEDURE instead_of_trigger('instead_of_ins')
+     instead_of_update_trig INSTEAD OF UPDATE ON main_view FOR EACH ROW EXECUTE PROCEDURE instead_of_trigger('instead_of_upd')
+ 
+ DROP TRIGGER instead_of_insert_trig ON main_view;
+ DROP TRIGGER instead_of_delete_trig ON main_view;
+ \d main_view
+    View "public.main_view"
+  Column |  Type   | Modifiers 
+ --------+---------+-----------
+  a      | integer | 
+  b      | integer | 
+ Triggers:
+     after_ins_stmt_trig AFTER INSERT ON main_view FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_view_ins_stmt')
+     before_ins_stmt_trig BEFORE INSERT ON main_view FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_view_ins_stmt')
+     instead_of_update_trig INSTEAD OF UPDATE ON main_view FOR EACH ROW EXECUTE PROCEDURE instead_of_trigger('instead_of_upd')
+ 
+ DROP VIEW main_view;
*** ./src/test/regress/regress.c.orig	2010-07-31 12:28:32.000000000 +0100
--- ./src/test/regress/regress.c	2010-08-15 12:52:20.000000000 +0100
***************
*** 489,495 ****
  		elog(ERROR, "ttdummy: not fired by trigger manager");
  	if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
  		elog(ERROR, "ttdummy: cannot process STATEMENT events");
! 	if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
  		elog(ERROR, "ttdummy: must be fired before event");
  	if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
  		elog(ERROR, "ttdummy: cannot process INSERT event");
--- 489,495 ----
  		elog(ERROR, "ttdummy: not fired by trigger manager");
  	if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
  		elog(ERROR, "ttdummy: cannot process STATEMENT events");
! 	if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event))
  		elog(ERROR, "ttdummy: must be fired before event");
  	if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
  		elog(ERROR, "ttdummy: cannot process INSERT event");
*** ./src/test/regress/sql/triggers.sql.orig	2010-08-15 13:15:16.000000000 +0100
--- ./src/test/regress/sql/triggers.sql	2010-08-15 16:03:34.000000000 +0100
***************
*** 579,581 ****
--- 579,740 ----
  
  DROP TABLE min_updates_test_oids;
  
+ --
+ -- Test triggers on views
+ --
+ 
+ CREATE VIEW main_view AS SELECT a, b FROM main_table;
+ 
+ -- Updates should fail without rules or triggers
+ INSERT INTO main_view VALUES (1,2);
+ UPDATE main_view SET b=20 WHERE a=50;
+ DELETE FROM main_view WHERE a=50;
+ -- Should fail even when there are no matching rows
+ DELETE FROM main_view WHERE a=51;
+ 
+ -- Triggers that aren't allowed on views (these should all fail)
+ CREATE TRIGGER invalid_trig BEFORE INSERT ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_ins_row');
+ 
+ CREATE TRIGGER invalid_trig BEFORE UPDATE ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_upd_row');
+ 
+ CREATE TRIGGER invalid_trig BEFORE DELETE ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_del_row');
+ 
+ CREATE TRIGGER invalid_trig BEFORE TRUNCATE ON main_view
+ EXECUTE PROCEDURE trigger_func('before_tru_row');
+ 
+ CREATE TRIGGER invalid_trig AFTER INSERT ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_ins_row');
+ 
+ CREATE TRIGGER invalid_trig AFTER UPDATE ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_upd_row');
+ 
+ CREATE TRIGGER invalid_trig AFTER DELETE ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_del_row');
+ 
+ CREATE TRIGGER invalid_trig AFTER TRUNCATE ON main_view
+ EXECUTE PROCEDURE trigger_func('before_tru_row');
+ 
+ -- INSTEAD OF trigger function
+ CREATE OR REPLACE FUNCTION instead_of_trigger()  RETURNS trigger 
+ LANGUAGE plpgsql AS $$
+ 
+ declare
+ 
+ 	argstr text;
+ 	relid text;
+ 
+ begin
+ 
+ 	relid := TG_relid::regclass;
+ 
+ 	-- plpgsql can't discover its trigger data in a hash like perl and python
+ 	-- can, or by a sort of reflection like tcl can, 
+ 	-- so we have to hard code the names.
+ 	raise NOTICE 'TG_NAME: %', TG_name;
+ 	raise NOTICE 'TG_WHEN: %', TG_when;
+ 	raise NOTICE 'TG_LEVEL: %', TG_level;
+ 	raise NOTICE 'TG_OP: %', TG_op;
+ 	raise NOTICE 'TG_RELID::regclass: %', relid;
+ 	raise NOTICE 'TG_RELNAME: %', TG_relname;
+ 	raise NOTICE 'TG_TABLE_NAME: %', TG_table_name;
+ 	raise NOTICE 'TG_TABLE_SCHEMA: %', TG_table_schema;
+ 	raise NOTICE 'TG_NARGS: %', TG_nargs;
+ 
+ 	argstr := '[';
+ 	for i in 0 .. TG_nargs - 1 loop
+ 		if i > 0 then
+ 			argstr := argstr || ', ';
+ 		end if;
+ 		argstr := argstr || TG_argv[i];
+ 	end loop;
+ 	argstr := argstr || ']';
+ 	raise NOTICE 'TG_ARGV: %', argstr;
+ 
+ 	if TG_OP != 'INSERT' then
+ 		raise NOTICE 'OLD: %', OLD;
+ 	end if;
+ 
+ 	if TG_OP != 'DELETE' then
+ 		raise NOTICE 'NEW: %', NEW;
+ 	end if;
+ 
+ 	if TG_OP = 'INSERT' then
+ 		INSERT INTO main_table VALUES (NEW.a, NEW.b);
+ 		RETURN NEW;
+ 	end if;
+ 
+ 	if TG_OP = 'UPDATE' then
+ 		UPDATE main_table SET a=NEW.a, b=NEW.b WHERE a=OLD.a AND b=OLD.b;
+ 		if NOT FOUND then RETURN NULL; end if;
+ 		RETURN NEW;
+ 	end if;
+ 
+ 	if TG_OP = 'DELETE' then
+ 		DELETE FROM main_table WHERE a=OLD.a AND b=OLD.b;
+ 		if NOT FOUND then RETURN NULL; end if;
+ 		RETURN OLD;
+ 	end if;
+ end;
+ $$;
+ 
+ -- The following INSTEAD OF triggers shouldn't be allowed
+ CREATE TRIGGER invalid_trig INSTEAD OF INSERT ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE instead_of_trigger('instead_of_ins');
+ 
+ CREATE TRIGGER invalid_trig INSTEAD OF UPDATE ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE instead_of_trigger('instead_of_upd');
+ 
+ CREATE TRIGGER invalid_trig INSTEAD OF DELETE ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE instead_of_trigger('instead_of_del');
+ 
+ CREATE TRIGGER invalid_trig INSTEAD OF UPDATE ON main_view
+ FOR EACH ROW WHEN (OLD.a <> NEW.a) EXECUTE PROCEDURE instead_of_trigger('instead_of_upd');
+ 
+ CREATE TRIGGER invalid_trig INSTEAD OF UPDATE OF a ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE instead_of_trigger('instead_of_upd');
+ 
+ CREATE TRIGGER invalid_trig INSTEAD OF UPDATE ON main_view
+ EXECUTE PROCEDURE instead_of_trigger('instead_of_upd');
+ 
+ -- Valid INSTEAD OF triggers
+ CREATE TRIGGER instead_of_insert_trig INSTEAD OF INSERT ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE instead_of_trigger('instead_of_ins');
+ 
+ CREATE TRIGGER instead_of_update_trig INSTEAD OF UPDATE ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE instead_of_trigger('instead_of_upd');
+ 
+ CREATE TRIGGER instead_of_delete_trig INSTEAD OF DELETE ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE instead_of_trigger('instead_of_del');
+ 
+ -- Valid BEFORE and AFTER statement triggers
+ CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_view
+ FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_view_ins_stmt');
+ 
+ CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_view
+ FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_view_ins_stmt');
+ 
+ \set QUIET false
+ 
+ INSERT INTO main_view VALUES (20, 30);
+ INSERT INTO main_view VALUES (21, 31) RETURNING a, b;
+ 
+ UPDATE main_view SET b=31 WHERE a=20;
+ UPDATE main_view SET b=32 WHERE a=21 AND b=31 RETURNING a,b;
+ 
+ DROP TRIGGER before_upd_a_row_trig ON main_table;
+ UPDATE main_view SET b=31 WHERE a=20;
+ UPDATE main_view SET b=32 WHERE a=21 AND b=31 RETURNING a,b;
+ 
+ DELETE FROM main_view WHERE a IN (20,21);
+ DELETE FROM main_view WHERE a=31 RETURNING a,b;
+ 
+ \set QUIET true
+ 
+ \d main_view
+ DROP TRIGGER instead_of_insert_trig ON main_view;
+ DROP TRIGGER instead_of_delete_trig ON main_view;
+ \d main_view
+ DROP VIEW main_view;