diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/access/transam/xact.c | 14 | ||||
-rw-r--r-- | src/backend/commands/copy.c | 19 | ||||
-rw-r--r-- | src/backend/commands/tablecmds.c | 15 | ||||
-rw-r--r-- | src/backend/commands/trigger.c | 260 | ||||
-rw-r--r-- | src/backend/executor/execMain.c | 63 | ||||
-rw-r--r-- | src/backend/nodes/copyfuncs.c | 10 | ||||
-rw-r--r-- | src/backend/nodes/equalfuncs.c | 10 | ||||
-rw-r--r-- | src/backend/parser/gram.y | 30 | ||||
-rw-r--r-- | src/backend/utils/adt/pg_lzcompress.c | 12 | ||||
-rw-r--r-- | src/bin/pg_dump/pg_backup_archiver.c | 4 | ||||
-rw-r--r-- | src/bin/pg_dump/pg_dump.c | 24 | ||||
-rw-r--r-- | src/include/commands/trigger.h | 14 | ||||
-rw-r--r-- | src/include/nodes/parsenodes.h | 8 | ||||
-rw-r--r-- | src/include/utils/pg_lzcompress.h | 8 | ||||
-rw-r--r-- | src/include/utils/rel.h | 4 | ||||
-rw-r--r-- | src/interfaces/python/pgdb.py | 2 | ||||
-rw-r--r-- | src/pl/plpgsql/src/pl_exec.c | 61 | ||||
-rw-r--r-- | src/test/regress/expected/triggers.out | 54 | ||||
-rw-r--r-- | src/test/regress/sql/triggers.sql | 54 |
19 files changed, 492 insertions, 174 deletions
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index 607a47f1246..0f30e13c848 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -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 * -------------------------------- diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 8dbde72be46..b0dd47f945a 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -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); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index cda8687e448..e3c3d0c2906 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -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; diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 5c56a7ccfc9..c9e2d87ff9e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -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. diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 65afe082031..779d44a8e01 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -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 @@ -928,6 +928,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. */ @@ -1125,6 +1143,24 @@ lnext: ; } /* + * 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 * diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 2c345b9f785..eb9bbe93658 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -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; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 61e314ff186..12781797c3d 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -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) diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 0b3bb279d57..29cba53f9fc 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -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); } diff --git a/src/backend/utils/adt/pg_lzcompress.c b/src/backend/utils/adt/pg_lzcompress.c index c16e59038ee..a22c57cb4c8 100644 --- a/src/backend/utils/adt/pg_lzcompress.c +++ b/src/backend/utils/adt/pg_lzcompress.c @@ -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! */ diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c index dd6636bfad9..3fa0b904ecc 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -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. diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index dc2bdd83d5b..09b9e4ac697 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -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 (", diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 219b2251f5e..497a3622bf7 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -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, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 92501196f93..0d33b56d1fe 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -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 */ diff --git a/src/include/utils/pg_lzcompress.h b/src/include/utils/pg_lzcompress.h index 862790cfb78..24e4fae4a4f 100644 --- a/src/include/utils/pg_lzcompress.h +++ b/src/include/utils/pg_lzcompress.h @@ -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; diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index ed06e1861a2..a9ff7325c1e 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -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]; diff --git a/src/interfaces/python/pgdb.py b/src/interfaces/python/pgdb.py index 6ae63b9b681..78ca61ade52 100644 --- a/src/interfaces/python/pgdb.py +++ b/src/interfaces/python/pgdb.py @@ -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) diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 0f99d854624..549264107fa 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -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 { diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out index b5a62dace1b..b8e49452b6c 100644 --- a/src/test/regress/expected/triggers.out +++ b/src/test/regress/expected/triggers.out @@ -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) + diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql index 6358249116e..214ffff4469 100644 --- a/src/test/regress/sql/triggers.sql +++ b/src/test/regress/sql/triggers.sql @@ -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;
\ No newline at end of file |