*** ./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; }