Commit 1547ee01 authored by Jan Wieck's avatar Jan Wieck

This is part #1 for of the DEFERRED CONSTRAINT TRIGGER support.

Implements the CREATE CONSTRAINT TRIGGER and SET CONSTRAINTS commands.

TODO:
    Generic builtin trigger procedures
    Automatic execution of appropriate CREATE CONSTRAINT... at CREATE TABLE
    Support of new trigger type in pg_dump
    Swapping of huge # of events to disk

Jan
parent d810338d
......@@ -7,7 +7,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/access/transam/xact.c,v 1.54 1999/09/28 11:41:03 vadim Exp $
* $Header: /cvsroot/pgsql/src/backend/access/transam/xact.c,v 1.55 1999/09/29 16:05:55 wieck Exp $
*
* NOTES
* Transaction aborts can now occur two ways:
......@@ -149,6 +149,7 @@
#include "commands/async.h"
#include "commands/sequence.h"
#include "commands/vacuum.h"
#include "commands/trigger.h"
#include "libpq/be-fsstubs.h"
#include "storage/proc.h"
#include "storage/sinval.h"
......@@ -865,6 +866,12 @@ StartTransaction()
*/
InitNoNameRelList();
/* ----------------
* Tell the trigger manager to we're starting a transaction
* ----------------
*/
DeferredTriggerBeginXact();
/* ----------------
* done with start processing, set current transaction
* state to "in progress"
......@@ -904,6 +911,14 @@ CommitTransaction()
if (s->state != TRANS_INPROGRESS)
elog(NOTICE, "CommitTransaction and not in in-progress state ");
/* ----------------
* Tell the trigger manager that this transaction is about to be
* committed. He'll invoke all trigger deferred until XACT before
* we really start on committing the transaction.
* ----------------
*/
DeferredTriggerEndXact();
/* ----------------
* set the current transaction state information
* appropriately during the abort processing
......@@ -992,6 +1007,13 @@ AbortTransaction()
if (s->state != TRANS_INPROGRESS)
elog(NOTICE, "AbortTransaction and not in in-progress state ");
/* ----------------
* Tell the trigger manager that this transaction is about to be
* aborted.
* ----------------
*/
DeferredTriggerAbortXact();
/* ----------------
* set the current transaction state information
* appropriately during the abort processing
......
......@@ -7,7 +7,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/catalog/heap.c,v 1.98 1999/09/24 00:24:11 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/catalog/heap.c,v 1.99 1999/09/29 16:05:56 wieck Exp $
*
*
* INTERFACE ROUTINES
......@@ -1468,7 +1468,6 @@ heap_destroy_with_catalog(char *relname)
RelationRemoveRules(rid);
/* triggers */
if (rel->rd_rel->reltriggers > 0)
RelationRemoveTriggers(rel);
/* ----------------
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/catalog/indexing.c,v 1.45 1999/09/18 19:06:33 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/catalog/indexing.c,v 1.46 1999/09/29 16:05:56 wieck Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -52,7 +52,9 @@ char *Name_pg_attrdef_indices[Num_pg_attrdef_indices] = {AttrDefaultIndex};
char *Name_pg_relcheck_indices[Num_pg_relcheck_indices] = {RelCheckIndex};
char *Name_pg_trigger_indices[Num_pg_trigger_indices] = {TriggerRelidIndex};
char *Name_pg_trigger_indices[Num_pg_trigger_indices] = {TriggerRelidIndex,
TriggerConstrNameIndex,
TriggerConstrRelidIndex};
static HeapTuple CatalogIndexFetchTuple(Relation heapRelation,
......
......@@ -54,6 +54,9 @@ CreateTrigger(CreateTrigStmt *stmt)
Oid fargtypes[8];
int found = 0;
int i;
char constrtrigname[NAMEDATALEN];
char *constrname = "";
Oid constrrelid = 0;
if (!allowSystemTableMods && IsSystemRelationName(stmt->relname))
elog(ERROR, "CreateTrigger: can't create trigger for system relation %s", stmt->relname);
......@@ -63,6 +66,30 @@ CreateTrigger(CreateTrigStmt *stmt)
elog(ERROR, "%s: %s", stmt->relname, aclcheck_error_strings[ACLCHECK_NOT_OWNER]);
#endif
/* ----------
* If trigger is a constraint, user trigger name as constraint
* name and build a unique trigger name instead.
* ----------
*/
if (stmt->isconstraint)
{
constrname = stmt->trigname;
stmt->trigname = constrtrigname;
sprintf(constrtrigname, "RI_ConstraintTrigger_%d", newoid());
if (strcmp(stmt->constrrelname, "") == 0)
constrrelid = 0;
else
{
rel = heap_openr(stmt->constrrelname, NoLock);
if (rel == NULL)
elog(ERROR, "table \"%s\" does not exist",
stmt->constrrelname);
constrrelid = rel->rd_id;
heap_close(rel, NoLock);
}
}
rel = heap_openr(stmt->relname, AccessExclusiveLock);
TRIGGER_CLEAR_TYPE(tgtype);
......@@ -148,6 +175,14 @@ CreateTrigger(CreateTrigStmt *stmt)
values[Anum_pg_trigger_tgname - 1] = NameGetDatum(namein(stmt->trigname));
values[Anum_pg_trigger_tgfoid - 1] = ObjectIdGetDatum(tuple->t_data->t_oid);
values[Anum_pg_trigger_tgtype - 1] = Int16GetDatum(tgtype);
values[Anum_pg_trigger_tgenabled - 1] = true;
values[Anum_pg_trigger_tgisconstraint - 1] = stmt->isconstraint;
values[Anum_pg_trigger_tgconstrname - 1] = PointerGetDatum(constrname);;
values[Anum_pg_trigger_tgconstrrelid - 1] = constrrelid;
values[Anum_pg_trigger_tgdeferrable - 1] = stmt->deferrable;
values[Anum_pg_trigger_tginitdeferred - 1] = stmt->initdeferred;
if (stmt->args)
{
List *le;
......@@ -311,6 +346,7 @@ RelationRemoveTriggers(Relation rel)
HeapScanDesc tgscan;
ScanKeyData key;
HeapTuple tup;
Form_pg_trigger pg_trigger;
tgrel = heap_openr(TriggerRelationName, RowExclusiveLock);
ScanKeyEntryInitialize(&key, 0, Anum_pg_trigger_tgrelid,
......@@ -322,6 +358,36 @@ RelationRemoveTriggers(Relation rel)
heap_delete(tgrel, &tup->t_self, NULL);
heap_endscan(tgscan);
/* ----------
* Also drop all constraint triggers referencing this relation
* ----------
*/
ScanKeyEntryInitialize(&key, 0, Anum_pg_trigger_tgconstrrelid,
F_OIDEQ, RelationGetRelid(rel));
tgscan = heap_beginscan(tgrel, 0, SnapshotNow, 1, &key);
while (HeapTupleIsValid(tup = heap_getnext(tgscan, 0)))
{
Relation refrel;
DropTrigStmt stmt;
pg_trigger = (Form_pg_trigger) GETSTRUCT(tup);
refrel = heap_open(pg_trigger->tgrelid, NoLock);
stmt.relname = nameout(&(refrel->rd_rel->relname));
stmt.trigname = nameout(&(pg_trigger->tgname));
DropTrigger(&stmt);
pfree(stmt.relname);
pfree(stmt.trigname);
heap_close(refrel, NoLock);
}
heap_endscan(tgscan);
heap_close(tgrel, RowExclusiveLock);
}
......@@ -379,10 +445,15 @@ RelationBuildTriggers(Relation relation)
triggers = (Trigger *) repalloc(triggers, (found + 1) * sizeof(Trigger));
build = &(triggers[found]);
build->tgoid = tuple.t_data->t_oid;
build->tgname = nameout(&(pg_trigger->tgname));
build->tgfoid = pg_trigger->tgfoid;
build->tgfunc.fn_addr = NULL;
build->tgtype = pg_trigger->tgtype;
build->tgenabled = pg_trigger->tgenabled;
build->tgisconstraint = pg_trigger->tgisconstraint;
build->tgdeferrable = pg_trigger->tgdeferrable;
build->tginitdeferred = pg_trigger->tginitdeferred;
build->tgnargs = pg_trigger->tgnargs;
memcpy(build->tgattr, &(pg_trigger->tgattr), 8 * sizeof(int16));
val = (struct varlena *) fastgetattr(&tuple,
......@@ -592,6 +663,8 @@ ExecBRInsertTriggers(Relation rel, HeapTuple trigtuple)
SaveTriggerData->tg_newtuple = NULL;
for (i = 0; i < ntrigs; i++)
{
if (!trigger[i]->tgenabled)
continue;
CurrentTriggerData = SaveTriggerData;
CurrentTriggerData->tg_trigtuple = oldtuple = newtuple;
CurrentTriggerData->tg_trigger = trigger[i];
......@@ -609,24 +682,7 @@ ExecBRInsertTriggers(Relation rel, HeapTuple trigtuple)
void
ExecARInsertTriggers(Relation rel, HeapTuple trigtuple)
{
TriggerData *SaveTriggerData;
int ntrigs = rel->trigdesc->n_after_row[TRIGGER_EVENT_INSERT];
Trigger **trigger = rel->trigdesc->tg_after_row[TRIGGER_EVENT_INSERT];
int i;
SaveTriggerData = (TriggerData *) palloc(sizeof(TriggerData));
SaveTriggerData->tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW;
SaveTriggerData->tg_relation = rel;
SaveTriggerData->tg_newtuple = NULL;
for (i = 0; i < ntrigs; i++)
{
CurrentTriggerData = SaveTriggerData;
CurrentTriggerData->tg_trigtuple = trigtuple;
CurrentTriggerData->tg_trigger = trigger[i];
ExecCallTriggerFunc(trigger[i]);
}
CurrentTriggerData = NULL;
pfree(SaveTriggerData);
DeferredTriggerSaveEvent(rel, TRIGGER_EVENT_INSERT, NULL, trigtuple);
return;
}
......@@ -652,6 +708,8 @@ ExecBRDeleteTriggers(EState *estate, ItemPointer tupleid)
SaveTriggerData->tg_newtuple = NULL;
for (i = 0; i < ntrigs; i++)
{
if (!trigger[i]->tgenabled)
continue;
CurrentTriggerData = SaveTriggerData;
CurrentTriggerData->tg_trigtuple = trigtuple;
CurrentTriggerData->tg_trigger = trigger[i];
......@@ -672,29 +730,9 @@ void
ExecARDeleteTriggers(EState *estate, ItemPointer tupleid)
{
Relation rel = estate->es_result_relation_info->ri_RelationDesc;
TriggerData *SaveTriggerData;
int ntrigs = rel->trigdesc->n_after_row[TRIGGER_EVENT_DELETE];
Trigger **trigger = rel->trigdesc->tg_after_row[TRIGGER_EVENT_DELETE];
HeapTuple trigtuple;
int i;
HeapTuple trigtuple = GetTupleForTrigger(estate, tupleid, NULL);
trigtuple = GetTupleForTrigger(estate, tupleid, NULL);
Assert(trigtuple != NULL);
SaveTriggerData = (TriggerData *) palloc(sizeof(TriggerData));
SaveTriggerData->tg_event = TRIGGER_EVENT_DELETE | TRIGGER_EVENT_ROW;
SaveTriggerData->tg_relation = rel;
SaveTriggerData->tg_newtuple = NULL;
for (i = 0; i < ntrigs; i++)
{
CurrentTriggerData = SaveTriggerData;
CurrentTriggerData->tg_trigtuple = trigtuple;
CurrentTriggerData->tg_trigger = trigger[i];
ExecCallTriggerFunc(trigger[i]);
}
CurrentTriggerData = NULL;
pfree(SaveTriggerData);
pfree(trigtuple);
DeferredTriggerSaveEvent(rel, TRIGGER_EVENT_DELETE, trigtuple, NULL);
return;
}
......@@ -727,6 +765,8 @@ ExecBRUpdateTriggers(EState *estate, ItemPointer tupleid, HeapTuple newtuple)
SaveTriggerData->tg_relation = rel;
for (i = 0; i < ntrigs; i++)
{
if (!trigger[i]->tgenabled)
continue;
CurrentTriggerData = SaveTriggerData;
CurrentTriggerData->tg_trigtuple = trigtuple;
CurrentTriggerData->tg_newtuple = oldtuple = newtuple;
......@@ -747,29 +787,9 @@ void
ExecARUpdateTriggers(EState *estate, ItemPointer tupleid, HeapTuple newtuple)
{
Relation rel = estate->es_result_relation_info->ri_RelationDesc;
TriggerData *SaveTriggerData;
int ntrigs = rel->trigdesc->n_after_row[TRIGGER_EVENT_UPDATE];
Trigger **trigger = rel->trigdesc->tg_after_row[TRIGGER_EVENT_UPDATE];
HeapTuple trigtuple;
int i;
trigtuple = GetTupleForTrigger(estate, tupleid, NULL);
Assert(trigtuple != NULL);
HeapTuple trigtuple = GetTupleForTrigger(estate, tupleid, NULL);
SaveTriggerData = (TriggerData *) palloc(sizeof(TriggerData));
SaveTriggerData->tg_event = TRIGGER_EVENT_UPDATE | TRIGGER_EVENT_ROW;
SaveTriggerData->tg_relation = rel;
for (i = 0; i < ntrigs; i++)
{
CurrentTriggerData = SaveTriggerData;
CurrentTriggerData->tg_trigtuple = trigtuple;
CurrentTriggerData->tg_newtuple = newtuple;
CurrentTriggerData->tg_trigger = trigger[i];
ExecCallTriggerFunc(trigger[i]);
}
CurrentTriggerData = NULL;
pfree(SaveTriggerData);
pfree(trigtuple);
DeferredTriggerSaveEvent(rel, TRIGGER_EVENT_UPDATE, trigtuple, newtuple);
return;
}
......@@ -858,3 +878,998 @@ ltrmark:;
return result;
}
/* ----------
* Deferred trigger stuff
* ----------
*/
/* ----------
* Internal data to the deferred trigger mechanism is held
* during entire session in a global memor created at startup and
* over statements/commands in a separate global memory which
* is created at transaction start and destroyed at transaction
* end.
* ----------
*/
static GlobalMemory deftrig_gcxt = NULL;
static GlobalMemory deftrig_cxt = NULL;
/* ----------
* Global data that tells which triggers are actually in
* state IMMEDIATE or DEFERRED.
* ----------
*/
static bool deftrig_dfl_all_isset = false;
static bool deftrig_dfl_all_isdeferred = false;
static List *deftrig_dfl_trigstates = NIL;
static bool deftrig_all_isset;
static bool deftrig_all_isdeferred;
static List *deftrig_trigstates;
/* ----------
* The list of events during the entire transaction.
*
* XXX This must finally be held in a file because of the huge
* number of events that could occur in the real world.
* ----------
*/
static int deftrig_n_events;
static List *deftrig_events;
/* ----------
* deferredTriggerCheckState()
*
* Returns true if the trigger identified by tgoid is actually
* in state DEFERRED.
* ----------
*/
static bool
deferredTriggerCheckState(Oid tgoid, int32 itemstate)
{
MemoryContext oldcxt;
List *sl;
DeferredTriggerStatus trigstate;
/* ----------
* Not deferrable triggers (i.e. normal AFTER ROW triggers
* and constraints declared NOT DEFERRABLE, the state is
* allways false.
* ----------
*/
if ((itemstate & TRIGGER_DEFERRED_DEFERRABLE) == 0)
return false;
/* ----------
* Lookup if we know an individual state for this trigger
* ----------
*/
foreach (sl, deftrig_trigstates)
{
trigstate = (DeferredTriggerStatus) lfirst(sl);
if (trigstate->dts_tgoid == tgoid)
return trigstate->dts_tgisdeferred;
}
/* ----------
* No individual state known - so if the user issued a
* SET CONSTRAINT ALL ..., we return that instead of the
* triggers default state.
* ----------
*/
if (deftrig_all_isset)
return deftrig_all_isdeferred;
/* ----------
* No ALL state known either, remember the default state
* as the current and return that.
* ----------
*/
oldcxt = MemoryContextSwitchTo((MemoryContext) deftrig_cxt);
trigstate = (DeferredTriggerStatus)
palloc(sizeof(DeferredTriggerStatusData));
trigstate->dts_tgoid = tgoid;
trigstate->dts_tgisdeferred =
((itemstate & TRIGGER_DEFERRED_INITDEFERRED) != 0);
deftrig_trigstates = lappend(deftrig_trigstates, trigstate);
MemoryContextSwitchTo(oldcxt);
return trigstate->dts_tgisdeferred;
}
/* ----------
* deferredTriggerAddEvent()
*
* Add a new trigger event to the queue.
* ----------
*/
static void
deferredTriggerAddEvent(DeferredTriggerEvent event)
{
deftrig_events = lappend(deftrig_events, event);
deftrig_n_events++;
return;
}
/* ----------
* deferredTriggerGetPreviousEvent()
*
* Backward scan the eventlist to find the event a given OLD tuple
* resulted from in the same transaction.
* ----------
*/
static DeferredTriggerEvent
deferredTriggerGetPreviousEvent(Oid relid, ItemPointer ctid)
{
DeferredTriggerEvent previous;
int n;
for (n = deftrig_n_events - 1; n >= 0; n--)
{
previous = (DeferredTriggerEvent) nth(n, deftrig_events);
if (previous->dte_relid != relid)
continue;
if (previous->dte_event & TRIGGER_DEFERRED_CANCELED)
continue;
if (ItemPointerGetBlockNumber(ctid) ==
ItemPointerGetBlockNumber(&(previous->dte_newctid)) &&
ItemPointerGetOffsetNumber(ctid) ==
ItemPointerGetOffsetNumber(&(previous->dte_newctid)))
return previous;
}
elog(ERROR,
"deferredTriggerGetPreviousEvent(): event for tuple %s not found",
tidout(ctid));
return NULL;
}
/* ----------
* deferredTriggerCancelEvent()
*
* Mark an event in the eventlist as cancelled because it isn't
* required anymore (replaced by anoter event).
* ----------
*/
static void
deferredTriggerCancelEvent(DeferredTriggerEvent event)
{
event->dte_event |= TRIGGER_DEFERRED_CANCELED;
}
/* ----------
* deferredTriggerExecute()
*
* Fetch the required tuples back from the heap and fire one
* single trigger function.
* ----------
*/
static void
deferredTriggerExecute(DeferredTriggerEvent event, int itemno)
{
Relation rel;
TriggerData SaveTriggerData;
HeapTupleData oldtuple;
HeapTupleData newtuple;
HeapTuple rettuple;
Buffer oldbuffer;
Buffer newbuffer;
/* ----------
* Open the heap and fetch the required OLD and NEW tuples.
* ----------
*/
rel = heap_open(event->dte_relid, NoLock);
if (ItemPointerIsValid(&(event->dte_oldctid)))
{
ItemPointerCopy(&(event->dte_oldctid), &(oldtuple.t_self));
heap_fetch(rel, SnapshotAny, &oldtuple, &oldbuffer);
if (!oldtuple.t_data)
elog(ERROR, "deferredTriggerExecute(): failed to fetch old tuple");
}
if (ItemPointerIsValid(&(event->dte_newctid)))
{
ItemPointerCopy(&(event->dte_newctid), &(newtuple.t_self));
heap_fetch(rel, SnapshotAny, &newtuple, &newbuffer);
if (!newtuple.t_data)
elog(ERROR, "deferredTriggerExecute(): failed to fetch new tuple");
}
/* ----------
* Setup the trigger information
* ----------
*/
SaveTriggerData.tg_event = event->dte_event | TRIGGER_EVENT_ROW;
SaveTriggerData.tg_relation = rel;
switch (event->dte_event)
{
case TRIGGER_EVENT_INSERT:
SaveTriggerData.tg_trigtuple = &newtuple;
SaveTriggerData.tg_newtuple = NULL;
SaveTriggerData.tg_trigger =
rel->trigdesc->tg_after_row[TRIGGER_EVENT_INSERT][itemno];
break;
case TRIGGER_EVENT_UPDATE:
SaveTriggerData.tg_trigtuple = &oldtuple;
SaveTriggerData.tg_newtuple = &newtuple;
SaveTriggerData.tg_trigger =
rel->trigdesc->tg_after_row[TRIGGER_EVENT_UPDATE][itemno];
break;
case TRIGGER_EVENT_DELETE:
SaveTriggerData.tg_trigtuple = &oldtuple;
SaveTriggerData.tg_newtuple = NULL;
SaveTriggerData.tg_trigger =
rel->trigdesc->tg_after_row[TRIGGER_EVENT_DELETE][itemno];
break;
default:
}
/* ----------
* Call the trigger and throw away an eventually returned
* updated tuple.
* ----------
*/
CurrentTriggerData = &SaveTriggerData;
rettuple = ExecCallTriggerFunc(SaveTriggerData.tg_trigger);
CurrentTriggerData = NULL;
if (rettuple != NULL && rettuple != &oldtuple && rettuple != &newtuple)
pfree(rettuple);
/* ----------
* Release buffers and close the relation
* ----------
*/
if (ItemPointerIsValid(&(event->dte_oldctid)))
ReleaseBuffer(oldbuffer);
if (ItemPointerIsValid(&(event->dte_newctid)))
ReleaseBuffer(newbuffer);
heap_close(rel, NoLock);
return;
}
/* ----------
* deferredTriggerInvokeEvents()
*
* Scan the event queue for not yet invoked triggers. Check if they
* should be invoked now and do so.
* ----------
*/
static void
deferredTriggerInvokeEvents(bool immediate_only)
{
List *el;
DeferredTriggerEvent event;
int still_deferred_ones;
int eventno = -1;
int i;
/* ----------
* For now we process all events - to speedup transaction blocks
* we need to remember the actual end of the queue at EndQuery
* and process only events that are newer. On state changes we
* simply reset the position to the beginning of the queue and
* process all events once with the new states when the
* SET CONSTRAINTS ... command finishes and calls EndQuery.
* ----------
*/
foreach (el, deftrig_events)
{
eventno++;
/* ----------
* Get the event and check if it is completely done.
* ----------
*/
event = (DeferredTriggerEvent) lfirst(el);
if (event->dte_event & (TRIGGER_DEFERRED_DONE |
TRIGGER_DEFERRED_CANCELED))
continue;
/* ----------
* Check each trigger item in the event.
* ----------
*/
still_deferred_ones = false;
for (i = 0; i < event->dte_n_items; i++)
{
if (event->dte_item[i].dti_state & TRIGGER_DEFERRED_DONE)
continue;
/* ----------
* This trigger item hasn't been called yet. Check if
* we should call it now.
* ----------
*/
if (immediate_only && deferredTriggerCheckState(
event->dte_item[i].dti_tgoid,
event->dte_item[i].dti_state))
{
still_deferred_ones = true;
continue;
}
/* ----------
* So let's fire it...
* ----------
*/
deferredTriggerExecute(event, i);
event->dte_item[i].dti_state |= TRIGGER_DEFERRED_DONE;
}
/* ----------
* Remember in the event itself if all trigger items are
* done.
* ----------
*/
if (!still_deferred_ones)
event->dte_event |= TRIGGER_DEFERRED_DONE;
}
}
/* ----------
* DeferredTriggerInit()
*
* Initialize the deferred trigger mechanism. This is called during
* backend startup and is guaranteed to be before the first of all
* transactions.
* ----------
*/
int
DeferredTriggerInit(void)
{
deftrig_gcxt = CreateGlobalMemory("DeferredTriggerSession");
return 0;
}
/* ----------
* DeferredTriggerBeginXact()
*
* Called at transaction start (either BEGIN or implicit for single
* statement outside of transaction block).
* ----------
*/
void
DeferredTriggerBeginXact(void)
{
MemoryContext oldcxt;
List *l;
DeferredTriggerStatus dflstat;
DeferredTriggerStatus stat;
if (deftrig_cxt != NULL)
elog(FATAL,
"DeferredTriggerBeginXact() called while inside transaction");
/* ----------
* Create the per transaction memory context and copy all states
* from the per session context to here.
* ----------
*/
deftrig_cxt = CreateGlobalMemory("DeferredTriggerXact");
oldcxt = MemoryContextSwitchTo((MemoryContext)deftrig_cxt);
deftrig_all_isset = deftrig_dfl_all_isset;
deftrig_all_isdeferred = deftrig_dfl_all_isdeferred;
deftrig_trigstates = NIL;
foreach (l, deftrig_dfl_trigstates)
{
dflstat = (DeferredTriggerStatus) lfirst(l);
stat = (DeferredTriggerStatus)
palloc(sizeof(DeferredTriggerStatusData));
stat->dts_tgoid = dflstat->dts_tgoid;
stat->dts_tgisdeferred = dflstat->dts_tgisdeferred;
deftrig_trigstates = lappend(deftrig_trigstates, stat);
}
MemoryContextSwitchTo(oldcxt);
deftrig_n_events = 0;
deftrig_events = NIL;
}
/* ----------
* DeferredTriggerEndQuery()
*
* Called after one query sent down by the user has completely been
* processed. At this time we invoke all outstanding IMMEDIATE triggers.
* ----------
*/
void
DeferredTriggerEndQuery(void)
{
/* ----------
* Ignore call if we aren't in a transaction.
* ----------
*/
if (deftrig_cxt == NULL)
return;
deferredTriggerInvokeEvents(true);
}
/* ----------
* DeferredTriggerEndXact()
*
* Called just before the current transaction is committed. At this
* time we invoke all DEFERRED triggers and tidy up.
* ----------
*/
void
DeferredTriggerEndXact(void)
{
/* ----------
* Ignore call if we aren't in a transaction.
* ----------
*/
if (deftrig_cxt == NULL)
return;
deferredTriggerInvokeEvents(false);
GlobalMemoryDestroy(deftrig_cxt);
deftrig_cxt = NULL;
}
/* ----------
* DeferredTriggerAbortXact()
*
* The current transaction has entered the abort state.
* All outstanding triggers are canceled so we simply throw
* away anything we know.
* ----------
*/
void
DeferredTriggerAbortXact(void)
{
/* ----------
* Ignore call if we aren't in a transaction.
* ----------
*/
if (deftrig_cxt == NULL)
return;
GlobalMemoryDestroy(deftrig_cxt);
deftrig_cxt = NULL;
}
/* ----------
* DeferredTriggerSetState()
*
* Called for the users SET CONSTRAINTS ... utility command.
* ----------
*/
void
DeferredTriggerSetState(ConstraintsSetStmt *stmt)
{
Relation tgrel;
Relation irel;
List *l;
List *ls;
List *lnext;
List *loid = NIL;
MemoryContext oldcxt;
bool found;
DeferredTriggerStatus state;
/* ----------
* Handle SET CONSTRAINTS ALL ...
* ----------
*/
if (stmt->constraints == NIL) {
if (!IsTransactionBlock())
{
/* ----------
* ... outside of a transaction block
* ----------
*/
oldcxt = MemoryContextSwitchTo((MemoryContext) deftrig_gcxt);
/* ----------
* Drop all information about individual trigger states per
* session.
* ----------
*/
l = deftrig_dfl_trigstates;
while (l != NIL)
{
lnext = lnext(l);
pfree(lfirst(l));
pfree(l);
l = lnext;
}
deftrig_dfl_trigstates = NIL;
/* ----------
* Set the session ALL state to known.
* ----------
*/
deftrig_dfl_all_isset = true;
deftrig_dfl_all_isdeferred = stmt->deferred;
MemoryContextSwitchTo(oldcxt);
return;
} else {
/* ----------
* ... inside of a transaction block
* ----------
*/
oldcxt = MemoryContextSwitchTo((MemoryContext) deftrig_cxt);
/* ----------
* Drop all information about individual trigger states per
* transaction.
* ----------
*/
l = deftrig_trigstates;
while (l != NIL)
{
lnext = lnext(l);
pfree(lfirst(l));
pfree(l);
l = lnext;
}
deftrig_trigstates = NIL;
/* ----------
* Set the per transaction ALL state to known.
* ----------
*/
deftrig_all_isset = true;
deftrig_all_isdeferred = stmt->deferred;
MemoryContextSwitchTo(oldcxt);
return;
}
}
/* ----------
* Handle SET CONSTRAINTS constraint-name [, ...]
* First lookup all trigger Oid's for the constraint names.
* ----------
*/
tgrel = heap_openr(TriggerRelationName, AccessShareLock);
irel = index_openr(TriggerConstrNameIndex);
foreach (l, stmt->constraints)
{
ScanKeyData skey;
HeapTupleData tuple;
IndexScanDesc sd;
RetrieveIndexResult indexRes;
Buffer buffer;
Form_pg_trigger pg_trigger;
Oid constr_oid;
/* ----------
* Check that only named constraints are set explicitly
* ----------
*/
if (strcmp((char *)lfirst(l), "") == 0)
elog(ERROR, "unnamed constraints cannot be set explicitly");
/* ----------
* Setup to scan pg_trigger by tgconstrname ...
* ----------
*/
ScanKeyEntryInitialize(&skey,
(bits16) 0x0,
(AttrNumber) 1,
(RegProcedure) F_NAMEEQ,
PointerGetDatum((char *)lfirst(l)));
sd = index_beginscan(irel, false, 1, &skey);
/* ----------
* ... and search for the constraint trigger row
* ----------
*/
found = false;
for (;;)
{
indexRes = index_getnext(sd, ForwardScanDirection);
if (!indexRes)
break;
tuple.t_self = indexRes->heap_iptr;
heap_fetch(tgrel, SnapshotNow, &tuple, &buffer);
pfree(indexRes);
if (!tuple.t_data)
{
ReleaseBuffer(buffer);
continue;
}
/* ----------
* If we found some, check that they fit the deferrability
* ----------
*/
pg_trigger = (Form_pg_trigger) GETSTRUCT(&tuple);
if (stmt->deferred & !pg_trigger->tgdeferrable)
elog(ERROR, "Constraint '%s' is not deferrable",
(char *)lfirst(l));
constr_oid = tuple.t_data->t_oid;
loid = lappend(loid, (Node *)constr_oid);
found = true;
ReleaseBuffer(buffer);
}
/* ----------
* Not found ?
* ----------
*/
if (!found)
elog(ERROR, "Constraint '%s' does not exist", (char *)lfirst(l));
index_endscan(sd);
}
index_close(irel);
heap_close(tgrel, AccessShareLock);
if (!IsTransactionBlock())
{
/* ----------
* Outside of a transaction block set the trigger
* states of individual triggers on session level.
* ----------
*/
oldcxt = MemoryContextSwitchTo((MemoryContext) deftrig_gcxt);
foreach (l, loid)
{
found = false;
foreach (ls, deftrig_dfl_trigstates)
{
state = (DeferredTriggerStatus) lfirst(ls);
if (state->dts_tgoid == (Oid) lfirst(l))
{
state->dts_tgisdeferred = stmt->deferred;
found = true;
break;
}
}
if (!found)
{
state = (DeferredTriggerStatus)
palloc(sizeof(DeferredTriggerStatusData));
state->dts_tgoid = (Oid) lfirst(l);
state->dts_tgisdeferred = stmt->deferred;
deftrig_dfl_trigstates =
lappend(deftrig_dfl_trigstates, state);
}
}
MemoryContextSwitchTo(oldcxt);
return;
} else {
/* ----------
* Inside of a transaction block set the trigger
* states of individual triggers on transaction level.
* ----------
*/
oldcxt = MemoryContextSwitchTo((MemoryContext) deftrig_cxt);
foreach (l, loid)
{
found = false;
foreach (ls, deftrig_trigstates)
{
state = (DeferredTriggerStatus) lfirst(ls);
if (state->dts_tgoid == (Oid) lfirst(l))
{
state->dts_tgisdeferred = stmt->deferred;
found = true;
break;
}
}
if (!found)
{
state = (DeferredTriggerStatus)
palloc(sizeof(DeferredTriggerStatusData));
state->dts_tgoid = (Oid) lfirst(l);
state->dts_tgisdeferred = stmt->deferred;
deftrig_trigstates =
lappend(deftrig_trigstates, state);
}
}
MemoryContextSwitchTo(oldcxt);
return;
}
}
/* ----------
* DeferredTriggerSaveEvent()
*
* Called by ExecAR...Triggers() to add the event to the queue.
* ----------
*/
void
DeferredTriggerSaveEvent(Relation rel, int event,
HeapTuple oldtup, HeapTuple newtup)
{
MemoryContext oldcxt;
DeferredTriggerEvent new_event;
DeferredTriggerEvent prev_event;
bool prev_done = false;
int new_size;
int i;
int ntriggers;
Trigger **triggers;
ItemPointerData oldctid;
ItemPointerData newctid;
if (deftrig_cxt == NULL)
elog(ERROR,
"DeferredTriggerSaveEvent() called outside of transaction");
/* ----------
* Check if we're interested in this row at all
* ----------
*/
if (rel->trigdesc->n_after_row[TRIGGER_EVENT_INSERT] == 0 &&
rel->trigdesc->n_after_row[TRIGGER_EVENT_UPDATE] == 0 &&
rel->trigdesc->n_after_row[TRIGGER_EVENT_DELETE] == 0 &&
rel->trigdesc->n_before_row[TRIGGER_EVENT_INSERT] == 0 &&
rel->trigdesc->n_before_row[TRIGGER_EVENT_UPDATE] == 0 &&
rel->trigdesc->n_before_row[TRIGGER_EVENT_DELETE] == 0)
return;
/* ----------
* Get the CTID's of OLD and NEW
* ----------
*/
if (oldtup != NULL)
ItemPointerCopy(&(oldtup->t_self), &(oldctid));
else
ItemPointerSetInvalid(&(oldctid));
if (newtup != NULL)
ItemPointerCopy(&(newtup->t_self), &(newctid));
else
ItemPointerSetInvalid(&(newctid));
/* ----------
* Eventually modify the event and do some general RI violation checks
* ----------
*/
switch (event)
{
case TRIGGER_EVENT_INSERT:
/* ----------
* Don't know how to (surely) check if another tuple with
* this meaning (from all FK's point of view) got deleted
* in the same transaction. Thus not handled yet.
* ----------
*/
break;
case TRIGGER_EVENT_UPDATE:
/* ----------
* On UPDATE check if the tuple updated is a result
* of the same transaction.
* ----------
*/
if (oldtup->t_data->t_xmin != GetCurrentTransactionId())
break;
/* ----------
* Look at the previous event to the same tuple if
* any of it's triggers has already been executed.
* Such a situation would potentially violate RI
* so we abort the transaction.
* ----------
*/
prev_event = deferredTriggerGetPreviousEvent(rel->rd_id, &oldctid);
if (prev_event->dte_event & TRIGGER_DEFERRED_HAS_BEFORE ||
(prev_event->dte_n_items != 0 &&
prev_event->dte_event & TRIGGER_DEFERRED_DONE))
prev_done = true;
else
for (i = 0; i < prev_event->dte_n_items; i++)
{
if (prev_event->dte_item[i].dti_state &
TRIGGER_DEFERRED_DONE)
{
prev_done = true;
break;
}
}
if (prev_done)
{
elog(NOTICE, "UPDATE of row inserted/updated in same "
"transaction violates");
elog(NOTICE, "referential integrity semantics. Other "
"triggers or IMMEDIATE ");
elog(ERROR, " constraints have already been executed.");
}
/* ----------
* Anything's fine so far - i.e. none of the previous
* events triggers has been executed up to now. Let's
* the REAL event that happened so far.
* ----------
*/
switch (prev_event->dte_event & TRIGGER_EVENT_OPMASK)
{
case TRIGGER_EVENT_INSERT:
/* ----------
* The previous operation was an insert.
* So the REAL new event is an INSERT of
* the new tuple.
* ----------
*/
event = TRIGGER_EVENT_INSERT;
ItemPointerSetInvalid(&oldctid);
deferredTriggerCancelEvent(prev_event);
break;
case TRIGGER_EVENT_UPDATE:
/* ----------
* The previous operation was an UPDATE.
* So the REAL new event is still an UPDATE
* but from the original tuple to this new one.
* ----------
*/
event = TRIGGER_EVENT_UPDATE;
ItemPointerCopy(&(prev_event->dte_oldctid), &oldctid);
deferredTriggerCancelEvent(prev_event);
break;
}
break;
case TRIGGER_EVENT_DELETE:
/* ----------
* On DELETE check if the tuple updated is a result
* of the same transaction.
* ----------
*/
if (oldtup->t_data->t_xmin != GetCurrentTransactionId())
break;
/* ----------
* Look at the previous event to the same tuple if
* any of it's triggers has already been executed.
* Such a situation would potentially violate RI
* so we abort the transaction.
* ----------
*/
prev_event = deferredTriggerGetPreviousEvent(rel->rd_id, &oldctid);
if (prev_event->dte_event & TRIGGER_DEFERRED_HAS_BEFORE ||
(prev_event->dte_n_items != 0 &&
prev_event->dte_event & TRIGGER_DEFERRED_DONE))
prev_done = true;
else
for (i = 0; i < prev_event->dte_n_items; i++)
{
if (prev_event->dte_item[i].dti_state &
TRIGGER_DEFERRED_DONE)
{
prev_done = true;
break;
}
}
if (prev_done)
{
elog(NOTICE, "DELETE of row inserted/updated in same "
"transaction violates");
elog(NOTICE, "referential integrity semantics. Other "
"triggers or IMMEDIATE ");
elog(ERROR, " constraints have already been executed.");
}
/* ----------
* Anything's fine so far - i.e. none of the previous
* events triggers has been executed up to now. Let's
* the REAL event that happened so far.
* ----------
*/
switch (prev_event->dte_event & TRIGGER_EVENT_OPMASK)
{
case TRIGGER_EVENT_INSERT:
/* ----------
* The previous operation was an insert.
* So the REAL new event is nothing.
* ----------
*/
deferredTriggerCancelEvent(prev_event);
return;
case TRIGGER_EVENT_UPDATE:
/* ----------
* The previous operation was an UPDATE.
* So the REAL new event is a DELETE
* but from the original tuple.
* ----------
*/
event = TRIGGER_EVENT_DELETE;
ItemPointerCopy(&(prev_event->dte_oldctid), &oldctid);
deferredTriggerCancelEvent(prev_event);
break;
}
break;
}
/* ----------
* Create a new event and save it.
* ----------
*/
oldcxt = MemoryContextSwitchTo((MemoryContext) deftrig_cxt);
ntriggers = rel->trigdesc->n_after_row[event];
triggers = rel->trigdesc->tg_after_row[event];
new_size = sizeof(DeferredTriggerEventData) +
ntriggers * sizeof(DeferredTriggerEventItem);
new_event = (DeferredTriggerEvent) palloc(new_size);
new_event->dte_event = event;
new_event->dte_relid = rel->rd_id;
ItemPointerCopy(&oldctid, &(new_event->dte_oldctid));
ItemPointerCopy(&newctid, &(new_event->dte_newctid));
new_event->dte_n_items = ntriggers;
new_event->dte_item[ntriggers].dti_state = new_size;
for (i = 0; i < ntriggers; i++)
{
new_event->dte_item[i].dti_tgoid = triggers[i]->tgoid;
new_event->dte_item[i].dti_state =
((triggers[i]->tgdeferrable) ?
TRIGGER_DEFERRED_DEFERRABLE : 0) |
((triggers[i]->tginitdeferred) ?
TRIGGER_DEFERRED_INITDEFERRED : 0) |
((rel->trigdesc->n_before_row[event] > 0) ?
TRIGGER_DEFERRED_HAS_BEFORE : 0);
}
deferredTriggerAddEvent(new_event);
MemoryContextSwitchTo(oldcxt);
return;
}
......@@ -26,7 +26,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.95 1999/09/24 00:24:23 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.96 1999/09/29 16:06:02 wieck Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -1190,8 +1190,7 @@ ExecAppend(TupleTableSlot *slot,
estate->es_lastoid = newId;
/* AFTER ROW INSERT Triggers */
if (resultRelationDesc->trigdesc &&
resultRelationDesc->trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
if (resultRelationDesc->trigdesc)
ExecARInsertTriggers(resultRelationDesc, tuple);
}
......@@ -1277,8 +1276,7 @@ ldelete:;
*/
/* AFTER ROW DELETE Triggers */
if (resultRelationDesc->trigdesc &&
resultRelationDesc->trigdesc->n_after_row[TRIGGER_EVENT_DELETE] > 0)
if (resultRelationDesc->trigdesc)
ExecARDeleteTriggers(estate, tupleid);
}
......@@ -1420,8 +1418,7 @@ lreplace:;
ExecInsertIndexTuples(slot, &(tuple->t_self), estate, true);
/* AFTER ROW UPDATE Triggers */
if (resultRelationDesc->trigdesc &&
resultRelationDesc->trigdesc->n_after_row[TRIGGER_EVENT_UPDATE] > 0)
if (resultRelationDesc->trigdesc)
ExecARUpdateTriggers(estate, tupleid, tuple);
}
......
......@@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.103 1999/09/28 14:49:36 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.104 1999/09/29 16:06:06 wieck Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
......@@ -132,7 +132,8 @@ Oid param_type(int t); /* used in parse_expr.c */
CreatedbStmt, DestroydbStmt, VacuumStmt, CursorStmt, SubSelect,
UpdateStmt, InsertStmt, select_clause, SelectStmt, NotifyStmt, DeleteStmt,
ClusterStmt, ExplainStmt, VariableSetStmt, VariableShowStmt, VariableResetStmt,
CreateUserStmt, AlterUserStmt, DropUserStmt, RuleActionStmt
CreateUserStmt, AlterUserStmt, DropUserStmt, RuleActionStmt,
ConstraintsSetStmt,
%type <str> opt_database1, opt_database2, location, encoding
......@@ -146,6 +147,9 @@ Oid param_type(int t); /* used in parse_expr.c */
%type <boolean> TriggerActionTime, TriggerForSpec, PLangTrusted
%type <ival> OptConstrTrigDeferrable, OptConstrTrigInitdeferred
%type <str> OptConstrFromTable
%type <str> TriggerEvents, TriggerFuncArg
%type <str> relation_name, copy_file_name, copy_delimiter, def_name,
......@@ -254,6 +258,10 @@ Oid param_type(int t); /* used in parse_expr.c */
%type <list> key_actions, key_action
%type <str> key_match, key_reference
%type <list> constraints_set_list
%type <list> constraints_set_namelist
%type <boolean> constraints_set_mode
/*
* If you make any token changes, remember to:
* - use "yacc -d" and update parse.h
......@@ -274,8 +282,8 @@ Oid param_type(int t); /* used in parse_expr.c */
BEGIN_TRANS, BETWEEN, BOTH, BY,
CASCADE, CASE, CAST, CHAR, CHARACTER, CHECK, CLOSE,
COALESCE, COLLATE, COLUMN, COMMIT,
CONSTRAINT, CREATE, CROSS, CURRENT, CURRENT_DATE, CURRENT_TIME,
CURRENT_TIMESTAMP, CURRENT_USER, CURSOR,
CONSTRAINT, CONSTRAINTS, CREATE, CROSS, CURRENT, CURRENT_DATE,
CURRENT_TIME, CURRENT_TIMESTAMP, CURRENT_USER, CURSOR,
DAY_P, DECIMAL, DECLARE, DEFAULT, DELETE, DESC, DISTINCT, DOUBLE, DROP,
ELSE, END_TRANS, EXCEPT, EXECUTE, EXISTS, EXTRACT,
FALSE_P, FETCH, FLOAT, FOR, FOREIGN, FROM, FULL,
......@@ -295,7 +303,11 @@ Oid param_type(int t); /* used in parse_expr.c */
WHEN, WHERE, WITH, WORK, YEAR_P, ZONE
/* Keywords (in SQL3 reserved words) */
%token TRIGGER
%token DEFERRABLE, DEFERRED,
IMMEDIATE, INITIALLY,
PENDANT,
RESTRICT,
TRIGGER
/* Keywords (in SQL92 non-reserved words) */
%token COMMITTED, SERIALIZABLE, TYPE_P
......@@ -415,6 +427,7 @@ stmt : AddAttrStmt
| VariableSetStmt
| VariableShowStmt
| VariableResetStmt
| ConstraintsSetStmt
;
/*****************************************************************************
......@@ -630,6 +643,49 @@ VariableResetStmt: RESET ColId
;
ConstraintsSetStmt: SET CONSTRAINTS constraints_set_list constraints_set_mode
{
ConstraintsSetStmt *n = makeNode(ConstraintsSetStmt);
n->constraints = $3;
n->deferred = $4;
$$ = (Node *) n;
}
;
constraints_set_list: ALL
{
$$ = NIL;
}
| constraints_set_namelist
{
$$ = $1;
}
;
constraints_set_namelist: IDENT
{
$$ = lappend(NIL, $1);
}
| constraints_set_namelist ',' IDENT
{
$$ = lappend($1, $3);
}
;
constraints_set_mode: DEFERRED
{
$$ = true;
}
| IMMEDIATE
{
$$ = false;
}
;
/*****************************************************************************
*
* QUERY :
......@@ -1434,6 +1490,54 @@ CreateTrigStmt: CREATE TRIGGER name TriggerActionTime TriggerEvents ON
n->before = $4;
n->row = $8;
memcpy (n->actions, $5, 4);
n->lang = NULL; /* unused */
n->text = NULL; /* unused */
n->attr = NULL; /* unused */
n->when = NULL; /* unused */
n->isconstraint = false;
n->deferrable = false;
n->initdeferred = false;
n->constrrelname = NULL;
$$ = (Node *)n;
}
| CREATE CONSTRAINT TRIGGER name AFTER TriggerOneEvent ON
relation_name OptConstrFromTable
OptConstrTrigDeferrable OptConstrTrigInitdeferred
FOR EACH ROW EXECUTE PROCEDURE name '(' TriggerFuncArgs ')'
{
CreateTrigStmt *n = makeNode(CreateTrigStmt);
n->trigname = $4;
n->relname = $8;
n->funcname = $17;
n->args = $19;
n->before = false;
n->row = true;
n->actions[0] = $6;
n->actions[1] = '\0';
n->lang = NULL; /* unused */
n->text = NULL; /* unused */
n->attr = NULL; /* unused */
n->when = NULL; /* unused */
/*
* Check that the DEFERRABLE and INITIALLY combination
* makes sense
*/
n->isconstraint = true;
if ($11 == 1)
{
if ($10 == 0)
elog(ERROR, "INITIALLY DEFERRED constraint "
"cannot be NOT DEFERRABLE");
n->deferrable = true;
n->initdeferred = true;
} else {
n->deferrable = ($10 == 1);
n->initdeferred = false;
}
n->constrrelname = $9;
$$ = (Node *)n;
}
;
......@@ -1503,6 +1607,44 @@ TriggerFuncArg: ICONST
| IDENT { $$ = $1; }
;
OptConstrFromTable: /* Empty */
{
$$ = "";
}
| FROM relation_name
{
$$ = $2;
}
;
OptConstrTrigDeferrable: /* Empty */
{
$$ = -1;
}
| DEFERRABLE
{
$$ = 1;
}
| NOT DEFERRABLE
{
$$ = 0;
}
;
OptConstrTrigInitdeferred: /* Empty */
{
$$ = -1;
}
| INITIALLY DEFERRED
{
$$ = 1;
}
| INITIALLY IMMEDIATE
{
$$ = 0;
}
;
DropTrigStmt: DROP TRIGGER name ON relation_name
{
DropTrigStmt *n = makeNode(DropTrigStmt);
......@@ -5080,10 +5222,13 @@ ColId: IDENT { $$ = $1; }
| BEFORE { $$ = "before"; }
| CACHE { $$ = "cache"; }
| COMMITTED { $$ = "committed"; }
| CONSTRAINTS { $$ = "constraints"; }
| CREATEDB { $$ = "createdb"; }
| CREATEUSER { $$ = "createuser"; }
| CYCLE { $$ = "cycle"; }
| DATABASE { $$ = "database"; }
| DEFERRABLE { $$ = "deferrable"; }
| DEFERRED { $$ = "deferred"; }
| DELIMITERS { $$ = "delimiters"; }
| DOUBLE { $$ = "double"; }
| EACH { $$ = "each"; }
......@@ -5092,9 +5237,11 @@ ColId: IDENT { $$ = $1; }
| FORWARD { $$ = "forward"; }
| FUNCTION { $$ = "function"; }
| HANDLER { $$ = "handler"; }
| IMMEDIATE { $$ = "immediate"; }
| INCREMENT { $$ = "increment"; }
| INDEX { $$ = "index"; }
| INHERITS { $$ = "inherits"; }
| INITIALLY { $$ = "initially"; }
| INSENSITIVE { $$ = "insensitive"; }
| INSTEAD { $$ = "instead"; }
| ISNULL { $$ = "isnull"; }
......@@ -5119,12 +5266,14 @@ ColId: IDENT { $$ = $1; }
| OPERATOR { $$ = "operator"; }
| OPTION { $$ = "option"; }
| PASSWORD { $$ = "password"; }
| PENDANT { $$ = "pendant"; }
| PRIOR { $$ = "prior"; }
| PRIVILEGES { $$ = "privileges"; }
| PROCEDURAL { $$ = "procedural"; }
| READ { $$ = "read"; }
| RELATIVE { $$ = "relative"; }
| RENAME { $$ = "rename"; }
| RESTRICT { $$ = "restrict"; }
| RETURNS { $$ = "returns"; }
| ROW { $$ = "row"; }
| RULE { $$ = "rule"; }
......
......@@ -7,7 +7,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.61 1999/09/23 17:02:46 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.62 1999/09/29 16:06:08 wieck Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -63,6 +63,7 @@ static ScanKeyword ScanKeywords[] = {
{"commit", COMMIT},
{"committed", COMMITTED},
{"constraint", CONSTRAINT},
{"constraints", CONSTRAINTS},
{"copy", COPY},
{"create", CREATE},
{"createdb", CREATEDB},
......@@ -79,6 +80,8 @@ static ScanKeyword ScanKeywords[] = {
{"decimal", DECIMAL},
{"declare", DECLARE},
{"default", DEFAULT},
{"deferrable", DEFERRABLE},
{"deferred", DEFERRED},
{"delete", DELETE},
{"delimiters", DELIMITERS},
{"desc", DESC},
......@@ -112,10 +115,12 @@ static ScanKeyword ScanKeywords[] = {
{"handler", HANDLER},
{"having", HAVING},
{"hour", HOUR_P},
{"immediate", IMMEDIATE},
{"in", IN},
{"increment", INCREMENT},
{"index", INDEX},
{"inherits", INHERITS},
{"initially", INITIALLY},
{"inner", INNER_P},
{"insensitive", INSENSITIVE},
{"insert", INSERT},
......@@ -177,6 +182,7 @@ static ScanKeyword ScanKeywords[] = {
{"outer", OUTER_P},
{"partial", PARTIAL},
{"password", PASSWORD},
{"pendant", PENDANT},
{"position", POSITION},
{"precision", PRECISION},
{"primary", PRIMARY},
......@@ -190,6 +196,7 @@ static ScanKeyword ScanKeywords[] = {
{"relative", RELATIVE},
{"rename", RENAME},
{"reset", RESET},
{"restrict", RESTRICT},
{"returns", RETURNS},
{"revoke", REVOKE},
{"right", RIGHT},
......
......@@ -7,7 +7,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.129 1999/09/24 00:24:52 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.130 1999/09/29 16:06:10 wieck Exp $
*
* NOTES
* this is the "main" module of the postgres backend and
......@@ -44,6 +44,7 @@
#endif
#include "commands/async.h"
#include "commands/trigger.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "libpq/pqsignal.h"
......@@ -1484,9 +1485,16 @@ PostgresMain(int argc, char *argv[], int real_argc, char *real_argv[])
if (!IsUnderPostmaster)
{
puts("\nPOSTGRES backend interactive interface ");
puts("$Revision: 1.129 $ $Date: 1999/09/24 00:24:52 $\n");
puts("$Revision: 1.130 $ $Date: 1999/09/29 16:06:10 $\n");
}
/* ----------------
* Initialize the deferred trigger manager
* ----------------
*/
if (DeferredTriggerInit() != 0)
ExitPostgres(1);
/* ----------------
* POSTGRES main processing loop begins here
*
......@@ -1609,6 +1617,12 @@ PostgresMain(int argc, char *argv[], int real_argc, char *real_argv[])
pg_exec_query(parser_input->data);
/*
* Invoke IMMEDIATE constraint triggers
*
*/
DeferredTriggerEndQuery();
if (ShowStats)
ShowUsage();
}
......
......@@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.67 1999/09/27 15:47:54 vadim Exp $
* $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.68 1999/09/29 16:06:11 wieck Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -785,6 +785,13 @@ ProcessUtility(Node *parsetree,
LockTableCommand((LockStmt *) parsetree);
break;
case T_ConstraintsSetStmt:
PS_SET_STATUS(commandTag = "SET CONSTRAINTS");
CHECK_IF_ABORTED();
DeferredTriggerSetState((ConstraintsSetStmt *) parsetree);
break;
/*
* ******************************** default ********************************
......
......@@ -7,7 +7,7 @@
*
* Copyright (c) 1994, Regents of the University of California
*
* $Id: indexing.h,v 1.23 1999/07/20 17:14:07 momjian Exp $
* $Id: indexing.h,v 1.24 1999/09/29 16:06:14 wieck Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -25,7 +25,7 @@
#define Num_pg_class_indices 2
#define Num_pg_attrdef_indices 1
#define Num_pg_relcheck_indices 1
#define Num_pg_trigger_indices 1
#define Num_pg_trigger_indices 3
#define Num_pg_description_indices 1
......@@ -45,6 +45,8 @@
#define AttrDefaultIndex "pg_attrdef_adrelid_index"
#define RelCheckIndex "pg_relcheck_rcrelid_index"
#define TriggerRelidIndex "pg_trigger_tgrelid_index"
#define TriggerConstrNameIndex "pg_trigger_tgconstrname_index"
#define TriggerConstrRelidIndex "pg_trigger_tgconstrrelid_index"
#define DescriptionObjIndex "pg_description_objoid_index"
extern char *Name_pg_attr_indices[];
......@@ -114,6 +116,8 @@ DECLARE_INDEX(pg_attrdef_adrelid_index on pg_attrdef using btree(adrelid oid_ops
DECLARE_INDEX(pg_relcheck_rcrelid_index on pg_relcheck using btree(rcrelid oid_ops));
DECLARE_INDEX(pg_trigger_tgrelid_index on pg_trigger using btree(tgrelid oid_ops));
DECLARE_INDEX(pg_trigger_tgconstrname_index on pg_trigger using btree(tgconstrname name_ops));
DECLARE_INDEX(pg_trigger_tgconstrrelid_index on pg_trigger using btree(tgconstrrelid oid_ops));
DECLARE_INDEX(pg_description_objoid_index on pg_description using btree(objoid oid_ops));
......
......@@ -7,7 +7,7 @@
*
* Copyright (c) 1994, Regents of the University of California
*
* $Id: pg_attribute.h,v 1.49 1999/08/09 02:45:56 tgl Exp $
* $Id: pg_attribute.h,v 1.50 1999/09/29 16:06:16 wieck Exp $
*
* NOTES
* the genbki.sh script reads this file and generates .bki
......@@ -447,9 +447,16 @@ DATA(insert OID = 0 ( 1219 tgrelid 26 0 4 1 0 -1 -1 t f i f f));
DATA(insert OID = 0 ( 1219 tgname 19 0 NAMEDATALEN 2 0 -1 -1 f f i f f));
DATA(insert OID = 0 ( 1219 tgfoid 26 0 4 3 0 -1 -1 t f i f f));
DATA(insert OID = 0 ( 1219 tgtype 21 0 2 4 0 -1 -1 t f s f f));
DATA(insert OID = 0 ( 1219 tgnargs 21 0 2 5 0 -1 -1 t f s f f));
DATA(insert OID = 0 ( 1219 tgattr 22 0 16 6 0 -1 -1 f f i f f));
DATA(insert OID = 0 ( 1219 tgargs 17 0 -1 7 0 -1 -1 f f i f f));
DATA(insert OID = 0 ( 1219 tgenabled 16 0 1 5 0 -1 -1 t f c f f));
DATA(insert OID = 0 ( 1219 tgisconstraint 16 0 1 6 0 -1 -1 t f c f f));
DATA(insert OID = 0 ( 1219 tgconstrname 19 0 NAMEDATALEN 7 0 -1 -1 f f i f f));
DATA(insert OID = 0 ( 1219 tgconstrrelid 26 0 4 8 0 -1 -1 t f i f f));
DATA(insert OID = 0 ( 1219 tgdeferrable 16 0 1 9 0 -1 -1 t f c f f));
DATA(insert OID = 0 ( 1219 tginitdeferred 16 0 1 10 0 -1 -1 t f c f f));
DATA(insert OID = 0 ( 1219 tgnargs 21 0 2 11 0 -1 -1 t f s f f));
DATA(insert OID = 0 ( 1219 tgattr 22 0 16 12 0 -1 -1 f f i f f));
DATA(insert OID = 0 ( 1219 tgargs 17 0 -1 13 0 -1 -1 f f i f f));
DATA(insert OID = 0 ( 1219 ctid 27 0 6 -1 0 -1 -1 f f i f f));
DATA(insert OID = 0 ( 1219 oid 26 0 4 -2 0 -1 -1 t f i f f));
DATA(insert OID = 0 ( 1219 xmin 28 0 4 -3 0 -1 -1 t f i f f));
......
......@@ -7,7 +7,7 @@
*
* Copyright (c) 1994, Regents of the University of California
*
* $Id: pg_class.h,v 1.29 1999/05/25 16:13:44 momjian Exp $
* $Id: pg_class.h,v 1.30 1999/09/29 16:06:16 wieck Exp $
*
* NOTES
* ``pg_relation'' is being replaced by ``pg_class''. currently
......@@ -150,7 +150,7 @@ DATA(insert OID = 1215 ( pg_attrdef 109 PGUID 0 0 0 t t r 4 0 0 0 0 0 f f _n
DESCR("");
DATA(insert OID = 1216 ( pg_relcheck 110 PGUID 0 0 0 t t r 4 0 0 0 0 0 f f _null_ ));
DESCR("");
DATA(insert OID = 1219 ( pg_trigger 111 PGUID 0 0 0 t t r 7 0 0 0 0 0 f f _null_ ));
DATA(insert OID = 1219 ( pg_trigger 111 PGUID 0 0 0 t t r 13 0 0 0 0 0 f f _null_ ));
DESCR("");
#define RelOid_pg_type 1247
......
......@@ -33,6 +33,13 @@ CATALOG(pg_trigger) BOOTSTRAP
Oid tgfoid; /* OID of function to be called */
int2 tgtype; /* BEFORE/AFTER UPDATE/DELETE/INSERT
* ROW/STATEMENT */
bool tgenabled; /* trigger is enabled/disabled */
bool tgisconstraint; /* trigger is a RI constraint */
NameData tgconstrname; /* RI constraint name */
Oid tgconstrrelid; /* RI table of foreign key definition */
/* in the case of ON DELETE or ON UPDATE */
bool tgdeferrable; /* RI trigger is deferrable */
bool tginitdeferred; /* RI trigger is deferred initially */
int2 tgnargs; /* # of extra arguments in tgargs */
int28 tgattr; /* UPDATE of attr1, attr2 ... (NI) */
bytea tgargs; /* first\000second\000tgnargs\000 */
......@@ -49,14 +56,20 @@ typedef FormData_pg_trigger *Form_pg_trigger;
* compiler constants for pg_trigger
* ----------------
*/
#define Natts_pg_trigger 7
#define Natts_pg_trigger 13
#define Anum_pg_trigger_tgrelid 1
#define Anum_pg_trigger_tgname 2
#define Anum_pg_trigger_tgfoid 3
#define Anum_pg_trigger_tgtype 4
#define Anum_pg_trigger_tgnargs 5
#define Anum_pg_trigger_tgattr 6
#define Anum_pg_trigger_tgargs 7
#define Anum_pg_trigger_tgenabled 5
#define Anum_pg_trigger_tgisconstraint 6
#define Anum_pg_trigger_tgconstrname 7
#define Anum_pg_trigger_tgconstrrelid 8
#define Anum_pg_trigger_tgdeferrable 9
#define Anum_pg_trigger_tginitdeferred 10
#define Anum_pg_trigger_tgnargs 11
#define Anum_pg_trigger_tgattr 12
#define Anum_pg_trigger_tgargs 13
#define TRIGGER_TYPE_ROW (1 << 0)
#define TRIGGER_TYPE_BEFORE (1 << 1)
......
......@@ -32,6 +32,13 @@ extern DLLIMPORT TriggerData *CurrentTriggerData;
#define TRIGGER_EVENT_ROW 0x00000004
#define TRIGGER_EVENT_BEFORE 0x00000008
#define TRIGGER_DEFERRED_DONE 0x00000010
#define TRIGGER_DEFERRED_CANCELED 0x00000020
#define TRIGGER_DEFERRED_DEFERRABLE 0x00000040
#define TRIGGER_DEFERRED_INITDEFERRED 0x00000080
#define TRIGGER_DEFERRED_HAS_BEFORE 0x00000100
#define TRIGGER_DEFERRED_MASK 0x000001F0
#define TRIGGER_FIRED_BY_INSERT(event) \
(((TriggerEvent) (event) & TRIGGER_EVENT_OPMASK) == \
TRIGGER_EVENT_INSERT)
......@@ -68,4 +75,46 @@ extern void ExecARDeleteTriggers(EState *estate, ItemPointer tupleid);
extern HeapTuple ExecBRUpdateTriggers(EState *estate, ItemPointer tupleid, HeapTuple tuple);
extern void ExecARUpdateTriggers(EState *estate, ItemPointer tupleid, HeapTuple tuple);
/* ----------
* Deferred trigger stuff
* ----------
*/
typedef struct DeferredTriggerStatusData {
Oid dts_tgoid;
bool dts_tgisdeferred;
} DeferredTriggerStatusData;
typedef struct DeferredTriggerStatusData *DeferredTriggerStatus;
typedef struct DeferredTriggerEventItem {
Oid dti_tgoid;
int32 dti_state;
} DeferredTriggerEventItem;
typedef struct DeferredTriggerEventData {
int32 dte_event;
Oid dte_relid;
ItemPointerData dte_oldctid;
ItemPointerData dte_newctid;
int32 dte_n_items;
DeferredTriggerEventItem dte_item[1];
} DeferredTriggerEventData;
typedef struct DeferredTriggerEventData *DeferredTriggerEvent;
extern int DeferredTriggerInit(void);
extern void DeferredTriggerBeginXact(void);
extern void DeferredTriggerEndQuery(void);
extern void DeferredTriggerEndXact(void);
extern void DeferredTriggerAbortXact(void);
extern void DeferredTriggerSetState(ConstraintsSetStmt *stmt);
extern void DeferredTriggerSaveEvent(Relation rel, int event,
HeapTuple oldtup, HeapTuple newtup);
#endif /* TRIGGER_H */
......@@ -6,7 +6,7 @@
*
* Copyright (c) 1994, Regents of the University of California
*
* $Id: nodes.h,v 1.52 1999/09/23 17:03:21 momjian Exp $
* $Id: nodes.h,v 1.53 1999/09/29 16:06:23 wieck Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -184,6 +184,7 @@ typedef enum NodeTag
T_AlterUserStmt,
T_DropUserStmt,
T_LockStmt,
T_ConstraintsSetStmt,
T_A_Expr = 700,
T_Attr,
......
......@@ -6,7 +6,7 @@
*
* Copyright (c) 1994, Regents of the University of California
*
* $Id: parsenodes.h,v 1.80 1999/09/28 04:34:50 momjian Exp $
* $Id: parsenodes.h,v 1.81 1999/09/29 16:06:23 wieck Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -180,6 +180,13 @@ typedef struct CreateTrigStmt
char *text; /* AS 'text' */
List *attr; /* UPDATE OF a, b,... (NI) or NULL */
char *when; /* WHEN 'a > 10 ...' (NI) or NULL */
/* The following are used for referential */
/* integrity constraint triggers */
bool isconstraint; /* This is an RI trigger */
bool deferrable; /* [NOT] DEFERRABLE */
bool initdeferred; /* INITIALLY {DEFERRED|IMMEDIATE} */
char *constrrelname; /* opposite relation */
} CreateTrigStmt;
typedef struct DropTrigStmt
......@@ -602,6 +609,19 @@ typedef struct LockStmt
int mode; /* lock mode */
} LockStmt;
/* ----------------------
* SET CONSTRAINTS Statement
* ----------------------
*/
typedef struct ConstraintsSetStmt
{
NodeTag type;
List *constraints;
bool deferred;
} ConstraintsSetStmt;
/*****************************************************************************
* Optimizable Statements
*****************************************************************************/
......
......@@ -6,7 +6,7 @@
*
* Copyright (c) 1994, Regents of the University of California
*
* $Id: rel.h,v 1.26 1999/09/18 19:08:25 tgl Exp $
* $Id: rel.h,v 1.27 1999/09/29 16:06:28 wieck Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -42,10 +42,15 @@ typedef LockInfoData *LockInfo;
typedef struct Trigger
{
Oid tgoid;
char *tgname;
Oid tgfoid;
FmgrInfo tgfunc;
int16 tgtype;
bool tgenabled;
bool tgisconstraint;
bool tgdeferrable;
bool tginitdeferred;
int16 tgnargs;
int16 tgattr[8];
char **tgargs;
......
......@@ -7,7 +7,7 @@
*
* Copyright (c) 1994, Regents of the University of California
*
* $Id: tqual.h,v 1.24 1999/07/15 23:04:24 momjian Exp $
* $Id: tqual.h,v 1.25 1999/09/29 16:06:28 wieck Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -30,6 +30,7 @@ typedef SnapshotData *Snapshot;
#define SnapshotNow ((Snapshot) 0x0)
#define SnapshotSelf ((Snapshot) 0x1)
#define SnapshotAny ((Snapshot) 0x2)
extern Snapshot SnapshotDirty;
extern Snapshot QuerySnapshot;
......@@ -37,6 +38,7 @@ extern Snapshot SerializableSnapshot;
#define IsSnapshotNow(snapshot) ((Snapshot) snapshot == SnapshotNow)
#define IsSnapshotSelf(snapshot) ((Snapshot) snapshot == SnapshotSelf)
#define IsSnapshotAny(snapshot) ((Snapshot) snapshot == SnapshotAny)
#define IsSnapshotDirty(snapshot) ((Snapshot) snapshot == SnapshotDirty)
extern TransactionId HeapSpecialTransactionId;
......@@ -55,7 +57,10 @@ extern CommandId HeapSpecialCommandId;
false \
: \
( \
(IsSnapshotSelf(snapshot) || heapisoverride()) ? \
(IsSnapshotAny(snapshot) || heapisoverride()) ? \
true \
: \
((IsSnapshotSelf(snapshot) || heapisoverride()) ? \
HeapTupleSatisfiesItself((tuple)->t_data) \
: \
((IsSnapshotDirty(snapshot)) ? \
......@@ -68,6 +73,7 @@ extern CommandId HeapSpecialCommandId;
) \
) \
) \
) \
)
#define heapisoverride() \
......
......@@ -64,101 +64,6 @@ NOTICE: check_pkeys_fkey_cascade: 1 tuple(s) of fkeys2 are deleted
QUERY: DROP TABLE pkeys;
QUERY: DROP TABLE fkeys;
QUERY: DROP TABLE fkeys2;
QUERY: create table dup17 (x int4);
QUERY: create trigger dup17_before
before insert on dup17
for each row
execute procedure
funny_dup17 ()
;
QUERY: insert into dup17 values (17);
NOTICE: funny_dup17 (fired BEFORE) on level 1: 0/0 tuples inserted/selected
QUERY: select count(*) from dup17;
count
-----
1
(1 row)
QUERY: insert into dup17 values (17);
NOTICE: funny_dup17 (fired BEFORE) on level 17: 1/2 tuples inserted/selected
NOTICE: funny_dup17 (fired BEFORE) on level 16: 1/3 tuples inserted/selected
NOTICE: funny_dup17 (fired BEFORE) on level 15: 1/4 tuples inserted/selected
NOTICE: funny_dup17 (fired BEFORE) on level 14: 1/5 tuples inserted/selected
NOTICE: funny_dup17 (fired BEFORE) on level 13: 1/6 tuples inserted/selected
NOTICE: funny_dup17 (fired BEFORE) on level 12: 1/7 tuples inserted/selected
NOTICE: funny_dup17 (fired BEFORE) on level 11: 1/8 tuples inserted/selected
NOTICE: funny_dup17 (fired BEFORE) on level 10: 1/9 tuples inserted/selected
NOTICE: funny_dup17 (fired BEFORE) on level 9: 1/10 tuples inserted/selected
NOTICE: funny_dup17 (fired BEFORE) on level 8: 1/11 tuples inserted/selected
NOTICE: funny_dup17 (fired BEFORE) on level 7: 1/12 tuples inserted/selected
NOTICE: funny_dup17 (fired BEFORE) on level 6: 1/13 tuples inserted/selected
NOTICE: funny_dup17 (fired BEFORE) on level 5: 1/14 tuples inserted/selected
NOTICE: funny_dup17 (fired BEFORE) on level 4: 1/15 tuples inserted/selected
NOTICE: funny_dup17 (fired BEFORE) on level 3: 1/16 tuples inserted/selected
NOTICE: funny_dup17 (fired BEFORE) on level 2: 1/17 tuples inserted/selected
NOTICE: funny_dup17 (fired BEFORE) on level 1: 1/18 tuples inserted/selected
QUERY: select count(*) from dup17;
count
-----
19
(1 row)
QUERY: drop trigger dup17_before on dup17;
QUERY: create trigger dup17_after
after insert on dup17
for each row
execute procedure
funny_dup17 ()
;
QUERY: insert into dup17 values (13);
NOTICE: funny_dup17 (fired AFTER ) on level 17: 17/34 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 16: 16/49 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 15: 15/63 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 14: 14/76 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 13: 13/88 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 12: 12/99 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 11: 11/109 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 10: 10/118 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 9: 9/126 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 8: 8/133 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 7: 7/139 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 6: 6/144 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 5: 5/148 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 4: 4/151 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 3: 3/153 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 2: 2/154 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 1: 1/154 tuples inserted/selected
QUERY: select count(*) from dup17 where x = 13;
count
-----
154
(1 row)
QUERY: insert into dup17 values (13);
NOTICE: funny_dup17 (fired AFTER ) on level 17: 171/342 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 16: 170/511 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 15: 169/679 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 14: 168/846 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 13: 167/1012 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 12: 166/1177 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 11: 165/1341 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 10: 164/1504 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 9: 163/1666 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 8: 162/1827 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 7: 161/1987 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 6: 160/2146 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 5: 159/2304 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 4: 158/2461 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 3: 157/2617 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 2: 156/2772 tuples inserted/selected
NOTICE: funny_dup17 (fired AFTER ) on level 1: 155/2926 tuples inserted/selected
QUERY: select count(*) from dup17 where x = 13;
count
-----
2926
(1 row)
QUERY: DROP TABLE dup17;
QUERY: create sequence ttdummy_seq increment 10 start 0 minvalue 0;
QUERY: create table tttest (
price_id int4,
......
......@@ -88,34 +88,40 @@ DROP TABLE pkeys;
DROP TABLE fkeys;
DROP TABLE fkeys2;
create table dup17 (x int4);
create trigger dup17_before
before insert on dup17
for each row
execute procedure
funny_dup17 ()
;
insert into dup17 values (17);
select count(*) from dup17;
insert into dup17 values (17);
select count(*) from dup17;
drop trigger dup17_before on dup17;
create trigger dup17_after
after insert on dup17
for each row
execute procedure
funny_dup17 ()
;
insert into dup17 values (13);
select count(*) from dup17 where x = 13;
insert into dup17 values (13);
select count(*) from dup17 where x = 13;
DROP TABLE dup17;
-- -- I've disabled the funny_dup17 test because the new semantics
-- -- of AFTER ROW triggers, which get now fired at the end of a
-- -- query allways, cause funny_dup17 to enter an endless loop.
-- --
-- -- Jan
--
-- create table dup17 (x int4);
--
-- create trigger dup17_before
-- before insert on dup17
-- for each row
-- execute procedure
-- funny_dup17 ()
-- ;
--
-- insert into dup17 values (17);
-- select count(*) from dup17;
-- insert into dup17 values (17);
-- select count(*) from dup17;
--
-- drop trigger dup17_before on dup17;
--
-- create trigger dup17_after
-- after insert on dup17
-- for each row
-- execute procedure
-- funny_dup17 ()
-- ;
-- insert into dup17 values (13);
-- select count(*) from dup17 where x = 13;
-- insert into dup17 values (13);
-- select count(*) from dup17 where x = 13;
--
-- DROP TABLE dup17;
create sequence ttdummy_seq increment 10 start 0 minvalue 0;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment