iot-demo.patch

text/x-patch

Filename: iot-demo.patch
Type: text/x-patch
Part: 0
Message: Proposal / proof of concept: 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+
src/backend/executor/execMain.c 1 4
src/backend/executor/nodeModifyTable.c 228 46
src/backend/optimizer/plan/planmain.c 7 0
src/backend/optimizer/prep/preptlist.c 71 38
src/backend/optimizer/util/plancat.c 6 1
src/backend/rewrite/rewriteHandler.c 129 36
*** ./src/backend/executor/execMain.c.orig	2010-07-26 22:35:38.000000000 +0100
--- ./src/backend/executor/execMain.c	2010-07-26 22:35:30.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-26 22:29:13.000000000 +0100
--- ./src/backend/executor/nodeModifyTable.c	2010-08-03 08:22:13.000000000 +0100
***************
*** 38,43 ****
--- 38,44 ----
  #include "postgres.h"
  
  #include "access/xact.h"
+ #include "catalog/pg_type.h"
  #include "commands/trigger.h"
  #include "executor/executor.h"
  #include "executor/nodeModifyTable.h"
***************
*** 148,153 ****
--- 149,213 ----
  	return ExecProject(projectReturning, NULL);
  }
  
+ static char *
+ xxx_TupleToString(HeapTupleHeader tuple)
+ {
+ 	StringInfoData buf;
+ 	char	   *val;
+ 	Oid			foutoid;
+ 	bool		typisvarlena;
+ 
+ 	initStringInfo(&buf);
+ 
+ 	getTypeOutputInfo(RECORDOID, &foutoid, &typisvarlena);
+ 	val = OidOutputFunctionCall(foutoid, PointerGetDatum(tuple));
+ 	appendStringInfoString(&buf, val);
+ 
+ 	return buf.data;
+ }
+ 
+ static char *
+ xxx_SlotToString(TupleTableSlot *slot)
+ {
+ 	StringInfoData buf;
+ 	int			natts = slot->tts_tupleDescriptor->natts;
+ 	int			i;
+ 
+ 	initStringInfo(&buf);
+ 	appendStringInfoString(&buf, "(");
+ 
+ 	for (i = 0; i < natts; i++)
+ 	{
+ 		Form_pg_attribute att_tup = slot->tts_tupleDescriptor->attrs[i];
+ 		char	   *val;
+ 		bool		isnull;
+ 		Datum		datum;
+ 
+ 		datum = slot_getattr(slot, i+1, &isnull);
+ 
+ 		if (isnull)
+ 			val = "null";
+ 		else
+ 		{
+ 			Oid			foutoid;
+ 			bool		typisvarlena;
+ 
+ 			getTypeOutputInfo(att_tup->atttypid,
+ 							  &foutoid, &typisvarlena);
+ 			val = OidOutputFunctionCall(foutoid, datum);
+ 		}
+ 
+ 		if (i > 0)
+ 			appendStringInfoString(&buf, ", ");
+ 		appendStringInfoString(&buf, NameStr(att_tup->attname));
+ 		appendStringInfoString(&buf, "=");
+ 		appendStringInfoString(&buf, val);
+ 	}
+ 	appendStringInfoString(&buf, ")");
+ 
+ 	return buf.data;
+ }
+ 
  /* ----------------------------------------------------------------
   *		ExecInsert
   *
***************
*** 167,172 ****
--- 227,233 ----
  	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 ****
--- 240,246 ----
  	 */
  	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;
--- 257,272 ----
  	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;
--- 307,326 ----
  	 * Note: heap_insert returns the tid (location) of the new tuple in the
  	 * t_self field.
  	 */
! 	if (resultRelIsView)
! 	{
! 		ereport(NOTICE,
! 				(errmsg("Trigger would INSERT into view \"%s\" NEW=%s",
! 						RelationGetRelationName(resultRelationDesc),
! 						xxx_SlotToString(slot))));
! 
! 		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);
  
--- 333,345 ----
  		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);
  
***************
*** 275,280 ****
--- 362,368 ----
   */
  static TupleTableSlot *
  ExecDelete(ItemPointer tupleid,
+ 		   HeapTupleHeader old_tuple,
  		   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;
--- 372,394 ----
  	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:
--- 409,432 ----
  	 * referential integrity updates in serializable transactions.
  	 */
  ldelete:;
! 	if (resultRelIsView)
! 	{
! 		ereport(NOTICE,
! 				(errmsg("Trigger would DELETE from view \"%s\" OLD=%s",
! 						RelationGetRelationName(resultRelationDesc),
! 						xxx_TupleToString(old_tuple))));
! 
! 		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)
--- 476,488 ----
  	 * 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));
--- 496,515 ----
  		HeapTupleData deltuple;
  		Buffer		delbuffer;
  
! 		if (resultRelIsView)
! 		{
! 			deltuple.t_data = old_tuple;
! 			deltuple.t_len = HeapTupleHeaderGetDatumLength(old_tuple);
! 			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;
  	}
--- 519,526 ----
  									 slot, planSlot);
  
  		ExecClearTuple(slot);
! 		if (!resultRelIsView)
! 			ReleaseBuffer(delbuffer);
  
  		return rslot;
  	}
***************
*** 418,423 ****
--- 543,549 ----
   */
  static TupleTableSlot *
  ExecUpdate(ItemPointer tupleid,
+ 		   HeapTupleHeader old_tuple,
  		   TupleTableSlot *slot,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
***************
*** 430,435 ****
--- 556,562 ----
  	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;
--- 575,590 ----
  	 */
  	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:
--- 635,659 ----
  	 * serialize error if not.	This is a special-case behavior needed for
  	 * referential integrity updates in serializable transactions.
  	 */
! 	if (resultRelIsView)
! 	{
! 		ereport(NOTICE,
! 				(errmsg("Trigger would UDPATE view \"%s\" OLD=%s NEW=%s",
! 						RelationGetRelationName(resultRelationDesc),
! 						xxx_TupleToString(old_tuple),
! 						xxx_SlotToString(slot))));
! 
! 		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);
  
--- 716,729 ----
  		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 ****
--- 807,813 ----
  	TupleTableSlot *planSlot;
  	ItemPointer tupleid = NULL;
  	ItemPointerData tuple_ctid;
+ 	HeapTupleHeader	old_tuple;
  
  	/*
  	 * 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;
  			}
  
  			/*
--- 859,895 ----
  
  		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");
! 
! 					old_tuple = 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:
--- 905,915 ----
  				slot = ExecInsert(slot, planSlot, estate);
  				break;
  			case CMD_UPDATE:
! 				slot = ExecUpdate(tupleid, old_tuple, slot, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			case CMD_DELETE:
! 				slot = ExecDelete(tupleid, old_tuple, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			default:
***************
*** 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;
--- 1148,1171 ----
  
  				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/optimizer/plan/planmain.c.orig	2010-07-26 22:47:52.000000000 +0100
--- ./src/backend/optimizer/plan/planmain.c	2010-07-26 22:50:11.000000000 +0100
***************
*** 176,184 ****
--- 176,191 ----
  	 * appendrel list, rather than just scanning the rangetable, is that the
  	 * rangetable may contain RTEs for rels not actively part of the query,
  	 * for example views.  We don't want to make RelOptInfos for them.
+ 	 *
+ 	 * However, if the query target is a view, it won't be in the jointree,
+ 	 * but it is convenient to build a dummy RelOptInfo node for it.
  	 */
  	add_base_rels_to_query(root, (Node *) parse->jointree);
  
+ 	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
*** ./src/backend/optimizer/prep/preptlist.c.orig	2010-07-26 22:03:20.000000000 +0100
--- ./src/backend/optimizer/prep/preptlist.c	2010-08-03 14:55:02.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-26 22:03:20.000000000 +0100
--- ./src/backend/optimizer/util/plancat.c	2010-07-26 22:03:20.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 query tree.
! 			 */
  			*pages = 0;
  			*tuples = 0;
  			break;
*** ./src/backend/rewrite/rewriteHandler.c.orig	2010-07-26 22:03:21.000000000 +0100
--- ./src/backend/rewrite/rewriteHandler.c	2010-08-03 15:09:31.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;
  }