1
0
mirror of https://github.com/postgres/postgres.git synced 2025-09-03 15:22:11 +03:00

This patch implements FOR EACH STATEMENT triggers, per my email to

-hackers a couple days ago.

Notes/caveats:

        - added regression tests for the new functionality, all
          regression tests pass on my machine

        - added pg_dump support

        - updated PL/PgSQL to support per-statement triggers; didn't
          look at the other procedural languages.

        - there's (even) more code duplication in trigger.c than there
          was previously. Any suggestions on how to refactor the
          ExecXXXTriggers() functions to reuse more code would be
          welcome -- I took a brief look at it, but couldn't see an
          easy way to do it (there are several subtly-different
          versions of the code in question)

        - updated the documentation. I also took the liberty of
          removing a big chunk of duplicated syntax documentation in
          the Programmer's Guide on triggers, and moving that
          information to the CREATE TRIGGER reference page.

        - I also included some spelling fixes and similar small
          cleanups I noticed while making the changes. If you'd like
          me to split those into a separate patch, let me know.

Neil Conway
This commit is contained in:
Bruce Momjian
2002-11-23 03:59:09 +00:00
parent ea29b32758
commit 1b7f3cc02d
24 changed files with 702 additions and 411 deletions

View File

@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/access/transam/xact.c,v 1.139 2002/11/18 01:17:39 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/access/transam/xact.c,v 1.140 2002/11/23 03:59:06 momjian Exp $
*
* NOTES
* Transaction aborts can now occur two ways:
@@ -901,18 +901,6 @@ StartTransaction(void)
}
#ifdef NOT_USED
/* ---------------
* Tell me if we are currently in progress
* ---------------
*/
bool
CurrentXactInProgress(void)
{
return CurrentTransactionState->state == TRANS_INPROGRESS;
}
#endif
/* --------------------------------
* CommitTransaction
* --------------------------------

View File

@@ -7,7 +7,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.180 2002/11/13 00:39:46 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.181 2002/11/23 03:59:07 momjian Exp $
*
*-------------------------------------------------------------------------
*/
@@ -877,6 +877,15 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
}
}
/*
* Check BEFORE STATEMENT insertion triggers. It's debateable
* whether we should do this for COPY, since it's not really an
* "INSERT" statement as such. However, executing these triggers
* maintains consistency with the EACH ROW triggers that we already
* fire on COPY.
*/
ExecBSInsertTriggers(estate, resultRelInfo);
if (!binary)
{
file_has_oids = oids; /* must rely on user to tell us this... */
@@ -1223,8 +1232,7 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
/* AFTER ROW INSERT Triggers */
if (resultRelInfo->ri_TrigDesc)
ExecARInsertTriggers(estate, resultRelInfo, tuple);
ExecARInsertTriggers(estate, resultRelInfo, tuple);
}
}
@@ -1233,6 +1241,11 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
*/
copy_lineno = 0;
/*
* Execute AFTER STATEMENT insertion triggers
*/
ExecASInsertTriggers(estate, resultRelInfo);
MemoryContextSwitchTo(oldcontext);
pfree(values);

View File

@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/commands/tablecmds.c,v 1.54 2002/11/15 02:50:05 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/commands/tablecmds.c,v 1.55 2002/11/23 03:59:07 momjian Exp $
*
*-------------------------------------------------------------------------
*/
@@ -3321,11 +3321,7 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
fk_trigger->actions[0] = 'i';
fk_trigger->actions[1] = 'u';
fk_trigger->actions[2] = '\0';
fk_trigger->lang = NULL;
fk_trigger->text = NULL;
fk_trigger->attr = NIL;
fk_trigger->when = NULL;
fk_trigger->isconstraint = true;
fk_trigger->deferrable = fkconstraint->deferrable;
fk_trigger->initdeferred = fkconstraint->initdeferred;
@@ -3374,11 +3370,7 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
fk_trigger->row = true;
fk_trigger->actions[0] = 'd';
fk_trigger->actions[1] = '\0';
fk_trigger->lang = NULL;
fk_trigger->text = NULL;
fk_trigger->attr = NIL;
fk_trigger->when = NULL;
fk_trigger->isconstraint = true;
fk_trigger->deferrable = fkconstraint->deferrable;
fk_trigger->initdeferred = fkconstraint->initdeferred;
@@ -3445,11 +3437,6 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
fk_trigger->row = true;
fk_trigger->actions[0] = 'u';
fk_trigger->actions[1] = '\0';
fk_trigger->lang = NULL;
fk_trigger->text = NULL;
fk_trigger->attr = NIL;
fk_trigger->when = NULL;
fk_trigger->isconstraint = true;
fk_trigger->deferrable = fkconstraint->deferrable;
fk_trigger->initdeferred = fkconstraint->initdeferred;

View File

@@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.139 2002/11/13 00:39:46 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.140 2002/11/23 03:59:07 momjian Exp $
*
*-------------------------------------------------------------------------
*/
@@ -47,7 +47,7 @@ static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata,
FmgrInfo *finfo,
MemoryContext per_tuple_context);
static void DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
HeapTuple oldtup, HeapTuple newtup);
bool row_trigger, HeapTuple oldtup, HeapTuple newtup);
static void DeferredTriggerExecute(DeferredTriggerEvent event, int itemno,
Relation rel, TriggerDesc *trigdesc, FmgrInfo *finfo,
MemoryContext per_tuple_context);
@@ -147,12 +147,14 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
{
/* foreign key constraint trigger */
aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(), ACL_REFERENCES);
aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
ACL_REFERENCES);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, RelationGetRelationName(rel));
if (constrrelid != InvalidOid)
{
aclresult = pg_class_aclcheck(constrrelid, GetUserId(), ACL_REFERENCES);
aclresult = pg_class_aclcheck(constrrelid, GetUserId(),
ACL_REFERENCES);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, get_rel_name(constrrelid));
}
@@ -160,7 +162,8 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
else
{
/* real trigger */
aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(), ACL_TRIGGER);
aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
ACL_TRIGGER);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, RelationGetRelationName(rel));
}
@@ -195,10 +198,8 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
TRIGGER_SETT_BEFORE(tgtype);
if (stmt->row)
TRIGGER_SETT_ROW(tgtype);
else
elog(ERROR, "CreateTrigger: STATEMENT triggers are unimplemented, yet");
for (i = 0; i < 3 && stmt->actions[i]; i++)
for (i = 0; i < 2 && stmt->actions[i]; i++)
{
switch (stmt->actions[i])
{
@@ -1131,6 +1132,64 @@ ExecCallTriggerFunc(TriggerData *trigdata,
return (HeapTuple) DatumGetPointer(result);
}
void
ExecBSInsertTriggers(EState *estate, ResultRelInfo *relinfo)
{
TriggerDesc *trigdesc;
int ntrigs;
int *tgindx;
int i;
TriggerData LocTriggerData;
trigdesc = relinfo->ri_TrigDesc;
if (trigdesc == NULL)
return;
ntrigs = trigdesc->n_before_statement[TRIGGER_EVENT_INSERT];
tgindx = trigdesc->tg_before_statement[TRIGGER_EVENT_INSERT];
if (ntrigs == 0)
return;
/* Allocate cache space for fmgr lookup info, if not done yet */
if (relinfo->ri_TrigFunctions == NULL)
relinfo->ri_TrigFunctions = (FmgrInfo *)
palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_INSERT |
TRIGGER_EVENT_BEFORE;
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
LocTriggerData.tg_newtuple = NULL;
LocTriggerData.tg_trigtuple = NULL;
for (i = 0; i < ntrigs; i++)
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
if (!trigger->tgenabled)
continue;
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
relinfo->ri_TrigFunctions + tgindx[i],
GetPerTupleMemoryContext(estate));
if (newtuple)
elog(ERROR, "BEFORE STATEMENT trigger cannot return a value.");
}
}
void
ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
false, NULL, NULL);
}
HeapTuple
ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
HeapTuple trigtuple)
@@ -1149,7 +1208,9 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW | TRIGGER_EVENT_BEFORE;
LocTriggerData.tg_event = TRIGGER_EVENT_INSERT |
TRIGGER_EVENT_ROW |
TRIGGER_EVENT_BEFORE;
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
LocTriggerData.tg_newtuple = NULL;
for (i = 0; i < ntrigs; i++)
@@ -1177,9 +1238,67 @@ ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
NULL, trigtuple);
true, NULL, trigtuple);
}
void
ExecBSDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
{
TriggerDesc *trigdesc;
int ntrigs;
int *tgindx;
int i;
TriggerData LocTriggerData;
trigdesc = relinfo->ri_TrigDesc;
if (trigdesc == NULL)
return;
ntrigs = trigdesc->n_before_statement[TRIGGER_EVENT_DELETE];
tgindx = trigdesc->tg_before_statement[TRIGGER_EVENT_DELETE];
if (ntrigs == 0)
return;
/* Allocate cache space for fmgr lookup info, if not done yet */
if (relinfo->ri_TrigFunctions == NULL)
relinfo->ri_TrigFunctions = (FmgrInfo *)
palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_DELETE |
TRIGGER_EVENT_BEFORE;
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
LocTriggerData.tg_newtuple = NULL;
LocTriggerData.tg_trigtuple = NULL;
for (i = 0; i < ntrigs; i++)
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
if (!trigger->tgenabled)
continue;
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
relinfo->ri_TrigFunctions + tgindx[i],
GetPerTupleMemoryContext(estate));
if (newtuple)
elog(ERROR, "BEFORE STATEMENT trigger cannot return a value.");
}
}
void
ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
false, NULL, NULL);
}
bool
@@ -1205,7 +1324,9 @@ ExecBRDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_DELETE | TRIGGER_EVENT_ROW | TRIGGER_EVENT_BEFORE;
LocTriggerData.tg_event = TRIGGER_EVENT_DELETE |
TRIGGER_EVENT_ROW |
TRIGGER_EVENT_BEFORE;
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
LocTriggerData.tg_newtuple = NULL;
for (i = 0; i < ntrigs; i++)
@@ -1235,17 +1356,75 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc->n_after_row[TRIGGER_EVENT_DELETE] > 0)
if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_DELETE] > 0)
{
HeapTuple trigtuple = GetTupleForTrigger(estate, relinfo,
tupleid, NULL);
DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
trigtuple, NULL);
true, trigtuple, NULL);
heap_freetuple(trigtuple);
}
}
void
ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
{
TriggerDesc *trigdesc;
int ntrigs;
int *tgindx;
int i;
TriggerData LocTriggerData;
trigdesc = relinfo->ri_TrigDesc;
if (trigdesc == NULL)
return;
ntrigs = trigdesc->n_before_statement[TRIGGER_EVENT_UPDATE];
tgindx = trigdesc->tg_before_statement[TRIGGER_EVENT_UPDATE];
if (ntrigs == 0)
return;
/* Allocate cache space for fmgr lookup info, if not done yet */
if (relinfo->ri_TrigFunctions == NULL)
relinfo->ri_TrigFunctions = (FmgrInfo *)
palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
TRIGGER_EVENT_BEFORE;
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
LocTriggerData.tg_newtuple = NULL;
LocTriggerData.tg_trigtuple = NULL;
for (i = 0; i < ntrigs; i++)
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
if (!trigger->tgenabled)
continue;
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
relinfo->ri_TrigFunctions + tgindx[i],
GetPerTupleMemoryContext(estate));
if (newtuple)
elog(ERROR, "BEFORE STATEMENT trigger cannot return a value.");
}
}
void
ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
false, NULL, NULL);
}
HeapTuple
ExecBRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
ItemPointer tupleid, HeapTuple newtuple)
@@ -1265,8 +1444,8 @@ ExecBRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
return NULL;
/*
* In READ COMMITTED isolevel it's possible that newtuple was changed
* due to concurrent update.
* In READ COMMITTED isolation level it's possible that newtuple was
* changed due to concurrent update.
*/
if (newSlot != NULL)
intuple = newtuple = ExecRemoveJunk(estate->es_junkFilter, newSlot);
@@ -1306,13 +1485,13 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc->n_after_row[TRIGGER_EVENT_UPDATE] > 0)
if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_UPDATE] > 0)
{
HeapTuple trigtuple = GetTupleForTrigger(estate, relinfo,
tupleid, NULL);
DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
trigtuple, newtuple);
true, trigtuple, newtuple);
heap_freetuple(trigtuple);
}
}
@@ -1344,7 +1523,7 @@ ltrmark:;
case HeapTupleSelfUpdated:
/* treat it as deleted; do not process */
ReleaseBuffer(buffer);
return (NULL);
return NULL;
case HeapTupleMayBeUpdated:
break;
@@ -1371,12 +1550,12 @@ ltrmark:;
* if tuple was deleted or PlanQual failed for updated
* tuple - we have not process this tuple!
*/
return (NULL);
return NULL;
default:
ReleaseBuffer(buffer);
elog(ERROR, "Unknown status %u from heap_mark4update", test);
return (NULL);
return NULL;
}
}
else
@@ -1466,7 +1645,7 @@ deferredTriggerCheckState(Oid tgoid, int32 itemstate)
/*
* Not deferrable triggers (i.e. normal AFTER ROW triggers and
* constraints declared NOT DEFERRABLE, the state is allways false.
* constraints declared NOT DEFERRABLE, the state is always false.
*/
if ((itemstate & TRIGGER_DEFERRED_DEFERRABLE) == 0)
return false;
@@ -1590,7 +1769,7 @@ DeferredTriggerExecute(DeferredTriggerEvent event, int itemno,
*/
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = (event->dte_event & TRIGGER_EVENT_OPMASK) |
TRIGGER_EVENT_ROW;
(event->dte_event & TRIGGER_EVENT_ROW);
LocTriggerData.tg_relation = rel;
LocTriggerData.tg_trigger = NULL;
@@ -2139,7 +2318,7 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
* ----------
*/
static void
DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
HeapTuple oldtup, HeapTuple newtup)
{
Relation rel = relinfo->ri_RelationDesc;
@@ -2152,7 +2331,6 @@ DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
int *tgindx;
ItemPointerData oldctid;
ItemPointerData newctid;
TriggerData LocTriggerData;
if (deftrig_cxt == NULL)
elog(ERROR,
@@ -2175,14 +2353,25 @@ DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
*/
oldcxt = MemoryContextSwitchTo(deftrig_cxt);
ntriggers = trigdesc->n_after_row[event];
tgindx = trigdesc->tg_after_row[event];
if (row_trigger)
{
ntriggers = trigdesc->n_after_row[event];
tgindx = trigdesc->tg_after_row[event];
}
else
{
ntriggers = trigdesc->n_after_statement[event];
tgindx = trigdesc->tg_after_statement[event];
}
new_size = offsetof(DeferredTriggerEventData, dte_item[0]) +
ntriggers * sizeof(DeferredTriggerEventItem);
new_event = (DeferredTriggerEvent) palloc(new_size);
new_event->dte_next = NULL;
new_event->dte_event = event & TRIGGER_EVENT_OPMASK;
if (row_trigger)
new_event->dte_event |= TRIGGER_EVENT_ROW;
new_event->dte_relid = rel->rd_id;
ItemPointerCopy(&oldctid, &(new_event->dte_oldctid));
ItemPointerCopy(&newctid, &(new_event->dte_newctid));
@@ -2190,15 +2379,21 @@ DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
for (i = 0; i < ntriggers; i++)
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
DeferredTriggerEventItem *ev_item = &(new_event->dte_item[i]);
new_event->dte_item[i].dti_tgoid = trigger->tgoid;
new_event->dte_item[i].dti_state =
ev_item->dti_tgoid = trigger->tgoid;
ev_item->dti_state =
((trigger->tgdeferrable) ?
TRIGGER_DEFERRED_DEFERRABLE : 0) |
((trigger->tginitdeferred) ?
TRIGGER_DEFERRED_INITDEFERRED : 0) |
((trigdesc->n_before_row[event] > 0) ?
TRIGGER_DEFERRED_HAS_BEFORE : 0);
TRIGGER_DEFERRED_INITDEFERRED : 0);
if (row_trigger && (trigdesc->n_before_row[event] > 0))
ev_item->dti_state |= TRIGGER_DEFERRED_HAS_BEFORE;
else if (!row_trigger && (trigdesc->n_before_statement[event] > 0))
{
ev_item->dti_state |= TRIGGER_DEFERRED_HAS_BEFORE;
}
}
MemoryContextSwitchTo(oldcxt);
@@ -2219,6 +2414,7 @@ DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
bool is_ri_trigger;
bool key_unchanged;
TriggerData LocTriggerData;
/*
* We are interested in RI_FKEY triggers only.

View File

@@ -13,7 +13,7 @@
*
* These three procedures are the external interfaces to the executor.
* In each case, the query descriptor and the execution state is required
* as arguments
* as arguments
*
* ExecutorStart() must be called at the beginning of any execution of any
* query plan and ExecutorEnd() should always be called at the end of
@@ -27,7 +27,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.186 2002/11/13 00:44:08 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.187 2002/11/23 03:59:07 momjian Exp $
*
*-------------------------------------------------------------------------
*/
@@ -908,12 +908,12 @@ ExecutePlan(EState *estate,
ScanDirection direction,
DestReceiver *destfunc)
{
JunkFilter *junkfilter;
TupleTableSlot *slot;
ItemPointer tupleid = NULL;
ItemPointerData tuple_ctid;
long current_tuple_count;
TupleTableSlot *result;
JunkFilter *junkfilter;
TupleTableSlot *slot;
ItemPointer tupleid = NULL;
ItemPointerData tuple_ctid;
long current_tuple_count;
TupleTableSlot *result;
/*
* initialize local variables
@@ -927,6 +927,24 @@ ExecutePlan(EState *estate,
*/
estate->es_direction = direction;
/*
* Process BEFORE EACH STATEMENT triggers
*/
switch (operation)
{
case CMD_UPDATE:
ExecBSUpdateTriggers(estate, estate->es_result_relation_info);
break;
case CMD_DELETE:
ExecBSDeleteTriggers(estate, estate->es_result_relation_info);
break;
case CMD_INSERT:
ExecBSInsertTriggers(estate, estate->es_result_relation_info);
break;
default:
/* do nothing */
}
/*
* Loop until we've processed the proper number of tuples from the
* plan.
@@ -1124,6 +1142,24 @@ lnext: ;
break;
}
/*
* Process AFTER EACH STATEMENT triggers
*/
switch (operation)
{
case CMD_UPDATE:
ExecASUpdateTriggers(estate, estate->es_result_relation_info);
break;
case CMD_DELETE:
ExecASDeleteTriggers(estate, estate->es_result_relation_info);
break;
case CMD_INSERT:
ExecASInsertTriggers(estate, estate->es_result_relation_info);
break;
default:
/* do nothing */
}
/*
* here, result is either a slot containing a tuple in the case of a
* SELECT or NULL otherwise.
@@ -1205,7 +1241,7 @@ ExecInsert(TupleTableSlot *slot,
/* BEFORE ROW INSERT Triggers */
if (resultRelInfo->ri_TrigDesc &&
resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_INSERT] > 0)
resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_INSERT] > 0)
{
HeapTuple newtuple;
@@ -1256,8 +1292,7 @@ ExecInsert(TupleTableSlot *slot,
ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
/* AFTER ROW INSERT Triggers */
if (resultRelInfo->ri_TrigDesc)
ExecARInsertTriggers(estate, resultRelInfo, tuple);
ExecARInsertTriggers(estate, resultRelInfo, tuple);
}
/* ----------------------------------------------------------------
@@ -1346,8 +1381,7 @@ ldelete:;
*/
/* AFTER ROW DELETE Triggers */
if (resultRelInfo->ri_TrigDesc)
ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
}
/* ----------------------------------------------------------------
@@ -1498,8 +1532,7 @@ lreplace:;
ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
/* AFTER ROW UPDATE Triggers */
if (resultRelInfo->ri_TrigDesc)
ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple);
ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple);
}
static char *

View File

@@ -15,7 +15,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.219 2002/11/19 23:21:58 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.220 2002/11/23 03:59:07 momjian Exp $
*
*-------------------------------------------------------------------------
*/
@@ -2482,14 +2482,6 @@ _copyCreateTrigStmt(CreateTrigStmt *from)
newnode->before = from->before;
newnode->row = from->row;
memcpy(newnode->actions, from->actions, sizeof(from->actions));
if (from->lang)
newnode->lang = pstrdup(from->lang);
if (from->text)
newnode->text = pstrdup(from->text);
Node_Copy(from, newnode, attr);
if (from->when)
newnode->when = pstrdup(from->when);
newnode->isconstraint = from->isconstraint;
newnode->deferrable = from->deferrable;
newnode->initdeferred = from->initdeferred;

View File

@@ -20,7 +20,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.165 2002/11/19 23:21:58 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.166 2002/11/23 03:59:07 momjian Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1291,14 +1291,6 @@ _equalCreateTrigStmt(CreateTrigStmt *a, CreateTrigStmt *b)
return false;
if (strcmp(a->actions, b->actions) != 0)
return false;
if (!equalstr(a->lang, b->lang))
return false;
if (!equalstr(a->text, b->text))
return false;
if (!equal(a->attr, b->attr))
return false;
if (!equalstr(a->when, b->when))
return false;
if (a->isconstraint != b->isconstraint)
return false;
if (a->deferrable != b->deferrable)

View File

@@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.380 2002/11/18 17:12:07 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.381 2002/11/23 03:59:08 momjian Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@@ -1371,7 +1371,7 @@ opt_using:
/*****************************************************************************
*
* QUERY :
* CREATE relname
* CREATE TABLE relname
*
*****************************************************************************/
@@ -2028,11 +2028,6 @@ CreateTrigStmt:
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;
@@ -2053,11 +2048,6 @@ CreateTrigStmt:
n->before = FALSE;
n->row = TRUE;
memcpy (n->actions, $6, 4);
n->lang = NULL; /* unused */
n->text = NULL; /* unused */
n->attr = NULL; /* unused */
n->when = NULL; /* unused */
n->isconstraint = TRUE;
n->deferrable = ($10 & 1) != 0;
n->initdeferred = ($10 & 2) != 0;
@@ -2075,17 +2065,17 @@ TriggerActionTime:
TriggerEvents:
TriggerOneEvent
{
char *e = palloc (4);
char *e = palloc(4);
e[0] = $1; e[1] = 0; $$ = e;
}
| TriggerOneEvent OR TriggerOneEvent
{
char *e = palloc (4);
char *e = palloc(4);
e[0] = $1; e[1] = $3; e[2] = 0; $$ = e;
}
| TriggerOneEvent OR TriggerOneEvent OR TriggerOneEvent
{
char *e = palloc (4);
char *e = palloc(4);
e[0] = $1; e[1] = $3; e[2] = $5; e[3] = 0;
$$ = e;
}
@@ -2102,6 +2092,14 @@ TriggerForSpec:
{
$$ = $3;
}
| /* EMPTY */
{
/*
* If ROW/STATEMENT not specified, default to
* STATEMENT, per SQL
*/
$$ = FALSE;
}
;
TriggerForOpt:
@@ -2124,7 +2122,7 @@ TriggerFuncArg:
ICONST
{
char buf[64];
snprintf (buf, sizeof(buf), "%d", $1);
snprintf(buf, sizeof(buf), "%d", $1);
$$ = makeString(pstrdup(buf));
}
| FCONST { $$ = makeString($1); }

View File

@@ -1,7 +1,7 @@
/* ----------
* pg_lzcompress.c -
*
* $Header: /cvsroot/pgsql/src/backend/utils/adt/pg_lzcompress.c,v 1.15 2002/09/04 20:31:28 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/utils/adt/pg_lzcompress.c,v 1.16 2002/11/23 03:59:08 momjian Exp $
*
* This is an implementation of LZ compression for PostgreSQL.
* It uses a simple history table and generates 2-3 byte tags
@@ -87,7 +87,7 @@
* OOOO LLLL OOOO OOOO
*
* This limits the offset to 1-4095 (12 bits) and the length
* to 3-18 (4 bits) because 3 is allways added to it. To emit
* to 3-18 (4 bits) because 3 is always added to it. To emit
* a tag of 2 bytes with a length of 2 only saves one control
* bit. But we lose one byte in the possible length of a tag.
*
@@ -230,7 +230,7 @@ static PGLZ_Strategy strategy_default_data = {
PGLZ_Strategy *PGLZ_strategy_default = &strategy_default_data;
static PGLZ_Strategy strategy_allways_data = {
static PGLZ_Strategy strategy_always_data = {
0, /* Chunks of any size are compressed */
0, /* */
0, /* We want to save at least one single
@@ -239,7 +239,7 @@ static PGLZ_Strategy strategy_allways_data = {
* bytes is found */
6 /* Look harder for a good match. */
};
PGLZ_Strategy *PGLZ_strategy_allways = &strategy_allways_data;
PGLZ_Strategy *PGLZ_strategy_always = &strategy_always_data;
static PGLZ_Strategy strategy_never_data = {
@@ -247,7 +247,7 @@ static PGLZ_Strategy strategy_never_data = {
0, /* */
0, /* */
0, /* Zero indicates "store uncompressed
* allways" */
* always" */
0 /* */
};
PGLZ_Strategy *PGLZ_strategy_never = &strategy_never_data;
@@ -716,7 +716,7 @@ pglz_decompress(PGLZ_Header *source, char *dest)
/*
* Now we copy the bytes specified by the tag from OUTPUT
* to OUTPUT. It is dangerous and platform dependant to
* to OUTPUT. It is dangerous and platform dependent to
* use memcpy() here, because the copied areas could
* overlap extremely!
*/

View File

@@ -15,7 +15,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/bin/pg_dump/pg_backup_archiver.c,v 1.62 2002/10/27 02:52:10 tgl Exp $
* $Header: /cvsroot/pgsql/src/bin/pg_dump/pg_backup_archiver.c,v 1.63 2002/11/23 03:59:08 momjian Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1458,7 +1458,7 @@ WriteInt(ArchiveHandle *AH, int i)
/*
* This is a bit yucky, but I don't want to make the binary format
* very dependant on representation, and not knowing much about it, I
* very dependent on representation, and not knowing much about it, I
* write out a sign byte. If you change this, don't forget to change
* the file version #, and modify readInt to read the new format AS
* WELL AS the old formats.

View File

@@ -7,22 +7,12 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* pg_dump will read the system catalogs in a database and
* dump out a script that reproduces
* the schema of the database in terms of
* user-defined types
* user-defined functions
* tables
* indexes
* aggregates
* operators
* privileges
*
* the output script is SQL that is understood by PostgreSQL
*
* pg_dump will read the system catalogs in a database and dump out a
* script that reproduces the schema in terms of SQL that is understood
* by PostgreSQL
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/bin/pg_dump/pg_dump.c,v 1.307 2002/11/15 02:52:18 momjian Exp $
* $Header: /cvsroot/pgsql/src/bin/pg_dump/pg_dump.c,v 1.308 2002/11/23 03:59:08 momjian Exp $
*
*-------------------------------------------------------------------------
*/
@@ -6345,7 +6335,11 @@ dumpTriggers(Archive *fout, TableInfo *tblinfo, int numTables)
}
appendPQExpBuffer(query, " FOR EACH ROW\n ");
if (TRIGGER_FOR_ROW(tgtype))
appendPQExpBuffer(query, " FOR EACH ROW\n ");
else
appendPQExpBuffer(query, " FOR EACH STATEMENT\n ");
/* In 7.3, result of regproc is already quoted */
if (g_fout->remoteVersion >= 70300)
appendPQExpBuffer(query, "EXECUTE PROCEDURE %s (",

View File

@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: trigger.h,v 1.39 2002/10/14 16:51:30 tgl Exp $
* $Id: trigger.h,v 1.40 2002/11/23 03:59:09 momjian Exp $
*
*-------------------------------------------------------------------------
*/
@@ -116,18 +116,30 @@ extern TriggerDesc *CopyTriggerDesc(TriggerDesc *trigdesc);
extern void FreeTriggerDesc(TriggerDesc *trigdesc);
extern void ExecBSInsertTriggers(EState *estate,
ResultRelInfo *relinfo);
extern void ExecASInsertTriggers(EState *estate,
ResultRelInfo *relinfo);
extern HeapTuple ExecBRInsertTriggers(EState *estate,
ResultRelInfo *relinfo,
HeapTuple trigtuple);
extern void ExecARInsertTriggers(EState *estate,
ResultRelInfo *relinfo,
HeapTuple trigtuple);
extern void ExecBSDeleteTriggers(EState *estate,
ResultRelInfo *relinfo);
extern void ExecASDeleteTriggers(EState *estate,
ResultRelInfo *relinfo);
extern bool ExecBRDeleteTriggers(EState *estate,
ResultRelInfo *relinfo,
ItemPointer tupleid);
extern void ExecARDeleteTriggers(EState *estate,
ResultRelInfo *relinfo,
ItemPointer tupleid);
extern void ExecBSUpdateTriggers(EState *estate,
ResultRelInfo *relinfo);
extern void ExecASUpdateTriggers(EState *estate,
ResultRelInfo *relinfo);
extern HeapTuple ExecBRUpdateTriggers(EState *estate,
ResultRelInfo *relinfo,
ItemPointer tupleid,

View File

@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: parsenodes.h,v 1.216 2002/11/19 23:21:59 tgl Exp $
* $Id: parsenodes.h,v 1.217 2002/11/23 03:59:09 momjian Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1048,11 +1048,7 @@ typedef struct CreateTrigStmt
List *args; /* list of (T_String) Values or NIL */
bool before; /* BEFORE/AFTER */
bool row; /* ROW/STATEMENT */
char actions[4]; /* Insert, Update, Delete */
char *lang; /* currently not used, always NULL */
char *text; /* AS 'text' */
List *attr; /* UPDATE OF a, b,... (NI) or NULL */
char *when; /* WHEN 'a > 10 ...' (NI) or NULL */
char actions[3]; /* Insert, Update, Delete */
/* The following are used for referential */
/* integrity constraint triggers */

View File

@@ -1,7 +1,7 @@
/* ----------
* pg_lzcompress.h -
*
* $Header: /cvsroot/pgsql/src/include/utils/pg_lzcompress.h,v 1.8 2001/11/05 17:46:36 momjian Exp $
* $Header: /cvsroot/pgsql/src/include/utils/pg_lzcompress.h,v 1.9 2002/11/23 03:59:09 momjian Exp $
*
* Definitions for the builtin LZ compressor
* ----------
@@ -89,7 +89,7 @@ typedef struct PGLZ_Header
* match_size_good The initial GOOD match size when starting history
* lookup. When looking up the history to find a
* match that could be expressed as a tag, the
* algorithm does not allways walk back entirely.
* algorithm does not always walk back entirely.
* A good match fast is usually better than the
* best possible one very late. For each iteration
* in the lookup, this value is lowered so the
@@ -147,7 +147,7 @@ typedef struct PGLZ_DecompState
* This is the default strategy if none
* is given to pglz_compress().
*
* PGLZ_strategy_allways Starts compression on any infinitely
* PGLZ_strategy_always Starts compression on any infinitely
* small input and does fallback to
* uncompressed storage only if output
* would be larger than input.
@@ -158,7 +158,7 @@ typedef struct PGLZ_DecompState
* ----------
*/
extern PGLZ_Strategy *PGLZ_strategy_default;
extern PGLZ_Strategy *PGLZ_strategy_allways;
extern PGLZ_Strategy *PGLZ_strategy_always;
extern PGLZ_Strategy *PGLZ_strategy_never;

View File

@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: rel.h,v 1.63 2002/09/04 20:31:46 momjian Exp $
* $Id: rel.h,v 1.64 2002/11/23 03:59:09 momjian Exp $
*
*-------------------------------------------------------------------------
*/
@@ -71,7 +71,7 @@ typedef struct TriggerDesc
* trigger can appear in more than one class, for each class we
* provide a list of integer indexes into the triggers array.
*/
#define TRIGGER_NUM_EVENT_CLASSES 4
#define TRIGGER_NUM_EVENT_CLASSES 3
uint16 n_before_statement[TRIGGER_NUM_EVENT_CLASSES];
uint16 n_before_row[TRIGGER_NUM_EVENT_CLASSES];

View File

@@ -180,7 +180,7 @@ class pgdbCursor:
def execute(self, operation, params = None):
# "The parameters may also be specified as list of
# tuples to e.g. insert multiple rows in a single
# operation, but this kind of usage is depreciated:
# operation, but this kind of usage is deprecated:
if params and type(params) == types.ListType and \
type(params[0]) == types.TupleType:
self.executemany(operation, params)

View File

@@ -3,7 +3,7 @@
* procedural language
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.69 2002/11/13 00:39:48 momjian Exp $
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.70 2002/11/23 03:59:09 momjian Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
@@ -430,9 +430,9 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
PLpgSQL_function *save_efunc;
PLpgSQL_stmt *save_estmt;
char *save_etext;
PLpgSQL_rec *rec_new;
PLpgSQL_rec *rec_old;
PLpgSQL_var *var;
PLpgSQL_rec *rec_new,
*rec_old;
HeapTuple rettup;
/*
@@ -511,8 +511,7 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
}
/*
* Put the trig and new tuples into the records and set the tg_op
* variable
* Put the OLD and NEW tuples into record variables
*/
rec_new = (PLpgSQL_rec *) (estate.datums[func->new_varno]);
rec_new->freetup = false;
@@ -520,15 +519,23 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
rec_old = (PLpgSQL_rec *) (estate.datums[func->old_varno]);
rec_old->freetup = false;
rec_old->freetupdesc = false;
var = (PLpgSQL_var *) (estate.datums[func->tg_op_varno]);
if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
{
/*
* Per-statement triggers don't use OLD/NEW variables
*/
rec_new->tup = NULL;
rec_new->tupdesc = NULL;
rec_old->tup = NULL;
rec_old->tupdesc = NULL;
}
else if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
{
rec_new->tup = trigdata->tg_trigtuple;
rec_new->tupdesc = trigdata->tg_relation->rd_att;
rec_old->tup = NULL;
rec_old->tupdesc = NULL;
var->value = DirectFunctionCall1(textin, CStringGetDatum("INSERT"));
}
else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
{
@@ -536,7 +543,6 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
rec_new->tupdesc = trigdata->tg_relation->rd_att;
rec_old->tup = trigdata->tg_trigtuple;
rec_old->tupdesc = trigdata->tg_relation->rd_att;
var->value = DirectFunctionCall1(textin, CStringGetDatum("UPDATE"));
}
else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
{
@@ -544,22 +550,27 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
rec_new->tupdesc = NULL;
rec_old->tup = trigdata->tg_trigtuple;
rec_old->tupdesc = trigdata->tg_relation->rd_att;
var->value = DirectFunctionCall1(textin, CStringGetDatum("DELETE"));
}
else
{
rec_new->tup = NULL;
rec_new->tupdesc = NULL;
rec_old->tup = NULL;
rec_old->tupdesc = NULL;
var->value = DirectFunctionCall1(textin, CStringGetDatum("UNKNOWN"));
}
var->isnull = false;
var->freeval = true;
elog(ERROR, "Unknown trigger action: not INSERT, DELETE, or UPDATE");
/*
* Fill all the other special tg_ variables
* Assign the special tg_ variables
*/
var = (PLpgSQL_var *) (estate.datums[func->tg_op_varno]);
var->isnull = false;
var->freeval = false;
if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
var->value = DirectFunctionCall1(textin, CStringGetDatum("INSERT"));
else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
var->value = DirectFunctionCall1(textin, CStringGetDatum("UPDATE"));
else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
var->value = DirectFunctionCall1(textin, CStringGetDatum("DELETE"));
else
elog(ERROR, "Unknown trigger action: not INSERT, DELETE, or UPDATE");
var = (PLpgSQL_var *) (estate.datums[func->tg_name_varno]);
var->isnull = false;
var->freeval = true;
@@ -574,7 +585,7 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
var->value = DirectFunctionCall1(textin, CStringGetDatum("AFTER"));
else
var->value = DirectFunctionCall1(textin, CStringGetDatum("UNKNOWN"));
elog(ERROR, "Unknown trigger execution time: not BEFORE or AFTER");
var = (PLpgSQL_var *) (estate.datums[func->tg_level_varno]);
var->isnull = false;
@@ -584,7 +595,7 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
else if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
var->value = DirectFunctionCall1(textin, CStringGetDatum("STATEMENT"));
else
var->value = DirectFunctionCall1(textin, CStringGetDatum("UNKNOWN"));
elog(ERROR, "Unknown trigger event type: not ROW or STATEMENT");
var = (PLpgSQL_var *) (estate.datums[func->tg_relid_varno]);
var->isnull = false;
@@ -671,13 +682,15 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
/*
* Check that the returned tuple structure has the same attributes,
* the relation that fired the trigger has.
* the relation that fired the trigger has. A per-statement trigger
* always needs to return NULL, so we ignore any return value the
* function itself produces (XXX: is this a good idea?)
*
* XXX This way it is possible, that the trigger returns a tuple where
* attributes don't have the correct atttypmod's length. It's up to
* the trigger's programmer to ensure that this doesn't happen. Jan
*/
if (estate.retisnull)
if (estate.retisnull || TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
rettup = NULL;
else
{

View File

@@ -91,7 +91,7 @@ DROP TABLE fkeys;
DROP TABLE fkeys2;
-- -- 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.
-- -- query always, cause funny_dup17 to enter an endless loop.
-- --
-- -- Jan
--
@@ -260,3 +260,55 @@ select * from tttest where price_on <= 35 and price_off > 35 and price_id = 5;
drop table tttest;
drop sequence ttdummy_seq;
--
-- tests for per-statement triggers
--
CREATE TABLE log_table (tstamp timestamp default timeofday()::timestamp);
CREATE TABLE main_table (a int, b int);
COPY main_table (a,b) FROM stdin;
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE 'plpgsql' AS '
BEGIN
RAISE NOTICE ''trigger_func() called: action = %, when = %, level = %'', TG_OP, TG_WHEN, TG_LEVEL;
RETURN NULL;
END;';
CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
--
-- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
-- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
--
CREATE TRIGGER before_upd_stmt_trig AFTER UPDATE ON main_table
EXECUTE PROCEDURE trigger_func();
CREATE TRIGGER before_upd_row_trig AFTER UPDATE ON main_table
FOR EACH ROW EXECUTE PROCEDURE trigger_func();
INSERT INTO main_table DEFAULT VALUES;
NOTICE: trigger_func() called: action = INSERT, when = BEFORE, level = STATEMENT
NOTICE: trigger_func() called: action = INSERT, when = AFTER, level = STATEMENT
UPDATE main_table SET a = a + 1 WHERE b < 30;
NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = ROW
NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = STATEMENT
-- UPDATE that effects zero rows should still call per-statement trigger
UPDATE main_table SET a = a + 2 WHERE b > 100;
NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = STATEMENT
-- COPY should fire per-row and per-statement INSERT triggers
COPY main_table (a, b) FROM stdin;
NOTICE: trigger_func() called: action = INSERT, when = BEFORE, level = STATEMENT
NOTICE: trigger_func() called: action = INSERT, when = AFTER, level = STATEMENT
SELECT * FROM main_table ORDER BY a;
a | b
----+----
6 | 10
21 | 20
30 | 40
31 | 10
50 | 35
50 | 60
81 | 15
|
(8 rows)

View File

@@ -93,7 +93,7 @@ DROP TABLE fkeys2;
-- -- 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.
-- -- query always, cause funny_dup17 to enter an endless loop.
-- --
-- -- Jan
--
@@ -196,3 +196,55 @@ select * from tttest where price_on <= 35 and price_off > 35 and price_id = 5;
drop table tttest;
drop sequence ttdummy_seq;
--
-- tests for per-statement triggers
--
CREATE TABLE log_table (tstamp timestamp default timeofday()::timestamp);
CREATE TABLE main_table (a int, b int);
COPY main_table (a,b) FROM stdin;
5 10
20 20
30 10
50 35
80 15
\.
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE 'plpgsql' AS '
BEGIN
RAISE NOTICE ''trigger_func() called: action = %, when = %, level = %'', TG_OP, TG_WHEN, TG_LEVEL;
RETURN NULL;
END;';
CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
--
-- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
-- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
--
CREATE TRIGGER before_upd_stmt_trig AFTER UPDATE ON main_table
EXECUTE PROCEDURE trigger_func();
CREATE TRIGGER before_upd_row_trig AFTER UPDATE ON main_table
FOR EACH ROW EXECUTE PROCEDURE trigger_func();
INSERT INTO main_table DEFAULT VALUES;
UPDATE main_table SET a = a + 1 WHERE b < 30;
-- UPDATE that effects zero rows should still call per-statement trigger
UPDATE main_table SET a = a + 2 WHERE b > 100;
-- COPY should fire per-row and per-statement INSERT triggers
COPY main_table (a, b) FROM stdin;
30 40
50 60
\.
SELECT * FROM main_table ORDER BY a;