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