iot-demo.patch
text/x-patch
Filename: iot-demo.patch
Type: text/x-patch
Part: 0
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;
}