diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml index 1486ee8e31..43d00d68f0 100644 --- a/doc/src/sgml/plpgsql.sgml +++ b/doc/src/sgml/plpgsql.sgml @@ -1,5 +1,5 @@ @@ -674,24 +674,25 @@ RENAME this_var TO that_var; Expressions - All expressions used in PL/pgSQL statements - are processed using the server's regular SQL executor. Expressions that - appear to contain - constants may in fact require run-time evaluation - (e.g. 'now' for the - timestamp type) so - it is impossible for the PL/pgSQL parser - to identify real constant values other than the NULL keyword. All - expressions are evaluated internally by executing a query + All expressions used in PL/pgSQL + statements are processed using the server's regular + SQL executor. Expressions that appear to + contain constants may in fact require run-time evaluation + (e.g. 'now' for the timestamp + type) so it is impossible for the + PL/pgSQL parser to identify real + constant values other than the NULL keyword. All expressions are + evaluated internally by executing a query SELECT expression - using the SPI manager. In the expression, occurrences - of PL/pgSQL variable + using the SPI manager. In the expression, + occurrences of PL/pgSQL variable identifiers are replaced by parameters and the actual values from the variables are passed to the executor in the parameter array. - This allows the query plan for the SELECT to be prepared just once - and then re-used for subsequent evaluations. + This allows the query plan for the SELECT to + be prepared just once and then re-used for subsequent + evaluations. @@ -1100,41 +1101,43 @@ GET DIAGNOSTICS variable = item - A SELECT INTO statement sets FOUND - true if it returns a row, false if no row is returned. + A SELECT INTO statement sets + FOUND true if it returns a row, false if no + row is returned. - A PERFORM statement sets FOUND + A PERFORM statement sets FOUND true if it produces (discards) a row, false if no row is produced. - UPDATE, INSERT, and DELETE statements set - FOUND true if at least one row is - affected, false if no row is affected. + UPDATE, INSERT, and DELETE + statements set FOUND true if at least one + row is affected, false if no row is affected. - A FETCH statement sets FOUND + A FETCH statement sets FOUND true if it returns a row, false if no row is returned. - A FOR statement sets FOUND - true if it iterates one or more times, else false. - This applies to all three variants of the FOR statement - (integer FOR loops, record-set FOR loops, and dynamic - record-set FOR loops). FOUND is only set - when the FOR loop exits: inside the execution of the loop, - FOUND is not modified by the FOR statement, - although it may be changed by the execution of other - statements within the loop body. + A FOR statement sets FOUND true + if it iterates one or more times, else false. This applies to + all three variants of the FOR statement (integer + FOR loops, record-set FOR loops, and + dynamic record-set FOR + loops). FOUND is only set when the + FOR loop exits: inside the execution of the loop, + FOUND is not modified by the + FOR statement, although it may be changed by the + execution of other statements within the loop body. @@ -1975,7 +1978,7 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id; PL/pgSQL can be used to define trigger procedures. A trigger procedure is created with the CREATE FUNCTION command as a function with no - arguments and a return type of TRIGGER. Note that + arguments and a return type of trigger. Note that the function must be declared with no arguments even if it expects to receive arguments specified in CREATE TRIGGER --- trigger arguments are passed via TG_ARGV, as described @@ -1992,8 +1995,9 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id; NEW - Data type RECORD; variable holding the new database row for INSERT/UPDATE - operations in ROW level triggers. + Data type RECORD; variable holding the new + database row for INSERT/UPDATE operations in ROW level + triggers. This variable is NULL in STATEMENT level triggers. @@ -2002,8 +2006,9 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id; OLD - Data type RECORD; variable holding the old database row for UPDATE/DELETE - operations in ROW level triggers. + Data type RECORD; variable holding the old + database row for UPDATE/DELETE operations in ROW level + triggers. This variable is NULL in STATEMENT level triggers. @@ -2098,22 +2103,23 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id; A trigger function must return either NULL or a record/row value - having exactly the structure of the table the trigger was fired for. - Triggers fired BEFORE may return NULL to signal the trigger manager - to skip the rest of the operation for this row (ie, subsequent triggers - are not fired, and the INSERT/UPDATE/DELETE does not occur for this - row). If a non-NULL value is returned then the operation proceeds with - that row value. Note that returning a row value different from the - original value of NEW alters the row that will be inserted or updated. - It is possible to replace single values directly - in NEW and return that, or to build a complete new record/row to - return. + having exactly the structure of the table the trigger was fired + for. The return value of a BEFORE or AFTER STATEMENT level + trigger, or an AFTER ROW level trigger is ignored; it may as well + return NULL. However, any of these types of triggers can still + abort the entire trigger operation by raising an error. - The return value of a trigger fired AFTER is ignored; it may as well - always return a NULL value. But an AFTER trigger can still abort the - operation by raising an error. + ROW level triggers fired BEFORE may return NULL to signal the + trigger manager to skip the rest of the operation for this row + (ie, subsequent triggers are not fired, and the + INSERT/UPDATE/DELETE does not occur for this row). If a non-NULL + value is returned then the operation proceeds with that row value. + Note that returning a row value different from the original value + of NEW alters the row that will be inserted or updated. It is + possible to replace single values directly in NEW and return that, + or to build a complete new record/row to return. @@ -2143,7 +2149,7 @@ CREATE FUNCTION emp_stamp () RETURNS TRIGGER AS ' RAISE EXCEPTION ''% cannot have NULL salary'', NEW.empname; END IF; - -- Who works for us when she must pay for? + -- Who works for us when she must pay for it? IF NEW.salary < 0 THEN RAISE EXCEPTION ''% cannot have a negative salary'', NEW.empname; END IF; diff --git a/doc/src/sgml/ref/alter_trigger.sgml b/doc/src/sgml/ref/alter_trigger.sgml index cdfbb792c7..4dfe945d2b 100644 --- a/doc/src/sgml/ref/alter_trigger.sgml +++ b/doc/src/sgml/ref/alter_trigger.sgml @@ -153,8 +153,8 @@ ALTER TRIGGER emp_stamp ON emp RENAME TO emp_track_chgs; SQL92 - The clause to rename triggers is a - PostgreSQL extension from SQL92. + ALTER TRIGGER is a PostgreSQL + extension of SQL92. diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml index 67481c19a3..ac8309af2e 100644 --- a/doc/src/sgml/ref/create_trigger.sgml +++ b/doc/src/sgml/ref/create_trigger.sgml @@ -1,5 +1,5 @@ @@ -21,8 +21,9 @@ PostgreSQL documentation 2000-03-25 -CREATE TRIGGER name { BEFORE | AFTER } { event [OR ...] } - ON table FOR EACH { ROW | STATEMENT } +CREATE TRIGGER name { + BEFORE | AFTER } { event [ OR ... ] } + ON table [ FOR EACH { ROW | STATEMENT } ] EXECUTE PROCEDURE func ( arguments ) @@ -45,11 +46,26 @@ CREATE TRIGGER name { BEFORE | AFTE + + + BEFORE + AFTER + + + Determines whether the function is called before or after the + event. + + + + event - One of INSERT, DELETE or UPDATE. + One of INSERT, DELETE or + UPDATE; this specifies the event that will + fire the trigger. Multiple events can be specified using + OR. @@ -57,10 +73,26 @@ CREATE TRIGGER name { BEFORE | AFTE table - The name (optionally schema-qualified) of the table the trigger is for. + The name (optionally schema-qualified) of the table the + trigger is for. + + + FOR EACH ROW + FOR EACH STATEMENT + + + + This specifies whether the trigger procedure should be fired + once for every row affected by the trigger event, or just once + per SQL statement. If neither is specified, FOR EACH + STATEMENT is the default. + + + + func @@ -74,11 +106,15 @@ CREATE TRIGGER name { BEFORE | AFTE arguments - An optional comma-separated list of arguments to be provided to the - function when the trigger is executed, along with the standard trigger - data such as old and new tuple contents. The arguments are literal - string constants. Simple names and numeric constants may be written - here too, but they will all be converted to strings. + An optional comma-separated list of arguments to be provided to + the function when the trigger is executed, along with the standard + trigger data such as old and new tuple contents. The arguments + are literal string constants. Simple names and numeric constants + may be written here too, but they will all be converted to + strings. Note that these arguments are not provided as normal + function parameters (since a trigger procedure must be declared to + take zero parameters), but are instead accessed through the + TG_ARGV array. @@ -121,7 +157,7 @@ CREATE TRIGGER CREATE TRIGGER will enter a new trigger into the current - data base. The trigger will be associated with the relation + database. The trigger will be associated with the relation table and will execute the specified function func. @@ -141,15 +177,27 @@ CREATE TRIGGER or deletion, are visible to the trigger. + + A trigger that executes FOR EACH ROW of the + specified operation is called once for every row that the operation + modifies. For example, a DELETE that affects 10 + rows will cause any ON DELETE triggers on the + target relation to be called 10 separate times, once for each + deleted tuple. In contrast, a trigger that executes FOR + EACH STATEMENT of the specified operation only executes + once for any given operation, regardless of how many rows it + modifies. + + If multiple triggers of the same kind are defined for the same event, they will be fired in alphabetical order by name. - SELECT does not modify any rows so you can not - create SELECT triggers. Rules and views are more - appropriate in such cases. + SELECT does not modify any rows so you can not + create SELECT triggers. Rules and views are more + appropriate in such cases. @@ -176,10 +224,6 @@ CREATE TRIGGER change the function's declared return type to trigger. - - As of the current release, STATEMENT triggers are not implemented. - - Refer to the command for information on how to remove triggers. @@ -268,13 +312,6 @@ CREATE TABLE distributors ( - - - PostgreSQL only has row-level - triggers, no statement-level triggers. - - - PostgreSQL only allows the diff --git a/doc/src/sgml/release.sgml b/doc/src/sgml/release.sgml index de439f3713..0c5c03beb3 100644 --- a/doc/src/sgml/release.sgml +++ b/doc/src/sgml/release.sgml @@ -1,5 +1,5 @@ @@ -4619,7 +4619,7 @@ Enhancements * pg_dump now output the schema and/or the data, with many fixes to enhance completeness. * psql used in place of monitor in administration shell scripts. - monitor to be depreciated in next release. + monitor to be deprecated in next release. * date/time functions enhanced * NULL insert/update/comparison fixed/enhanced * TCL/TK lib and shell fixed to work with both tck7.4/tk4.0 and tcl7.5/tk4.1 diff --git a/doc/src/sgml/trigger.sgml b/doc/src/sgml/trigger.sgml index fa3e149acc..b24663aa7a 100644 --- a/doc/src/sgml/trigger.sgml +++ b/doc/src/sgml/trigger.sgml @@ -1,5 +1,5 @@ @@ -7,21 +7,24 @@ $Header: /cvsroot/pgsql/doc/src/sgml/trigger.sgml,v 1.25 2002/09/21 18:32:54 pet PostgreSQL has various server-side - function interfaces. Server-side functions can be written in SQL, - C, or any defined procedural language. Trigger functions can be - written in C and most procedural languages, but not in SQL. Note that - statement-level trigger events are not supported in the current - version. You can currently specify BEFORE or AFTER on INSERT, - DELETE or UPDATE of a tuple as a trigger event. + function interfaces. Server-side functions can be written in + SQL, C, or any defined procedural + language. Trigger functions can be written in C and most procedural + languages, but not in SQL. Both per-row and + per-statement triggers are supported. A trigger procedure can + execute BEFORE or AFTER a INSERT, + DELETE or UPDATE, either once + per modified row, or once per SQL statement. Trigger Definition - If a trigger event occurs, the trigger manager (called by the Executor) - sets up a TriggerData information structure (described below) and calls - the trigger function to handle the event. + If a trigger event occurs, the trigger manager (called by the + Executor) sets up a TriggerData information + structure (described below) and calls the trigger function to + handle the event. @@ -35,116 +38,13 @@ $Header: /cvsroot/pgsql/doc/src/sgml/trigger.sgml,v 1.25 2002/09/21 18:32:54 pet - The syntax for creating triggers is: - - -CREATE TRIGGER trigger [ BEFORE | AFTER ] [ INSERT | DELETE | UPDATE [ OR ... ] ] - ON relation FOR EACH [ ROW | STATEMENT ] - EXECUTE PROCEDURE procedure - (args); - - - where the arguments are: - - - - - trigger - - - - The trigger must have a name distinct from all other triggers on - the same table. The name is needed - if you ever have to delete the trigger. - - - - - - BEFORE - AFTER - - - Determines whether the function is called before or after - the event. - - - - - - INSERT - DELETE - UPDATE - - - The next element of the command determines what event(s) will trigger - the function. Multiple events can be specified separated by OR. - - - - - - relation - - - The relation name indicates which table the event applies to. - - - - - - ROW - STATEMENT - - - The FOR EACH clause determines whether the trigger is fired for each - affected row or before (or after) the entire statement has completed. - Currently only the ROW case is supported. - - - - - - procedure - - - The procedure name is the function to be called. - - - - - - args - - - The arguments passed to the function in the TriggerData structure. - This is either empty or a list of one or more simple literal - constants (which will be passed to the function as strings). - - - - The purpose of including arguments in the trigger definition - is to allow different - triggers with similar requirements to call the same function. - As an example, there could be a generalized trigger - function that takes as its arguments two field names and puts the - current user in one and the current time stamp in the other. - Properly written, this trigger function would be independent of - the specific table it is triggering on. So the same function - could be used for INSERT events on any table with suitable fields, - to automatically track creation of records in a transaction table for - example. It could also be used to track last-update events if - defined as an UPDATE trigger. - - - - + The syntax for creating triggers is described in &cite-reference;. - Trigger functions return a HeapTuple to the calling executor. The return - value is ignored for triggers fired AFTER an operation, - but it allows BEFORE triggers to: + Trigger functions return a HeapTuple to the calling + executor. The return value is ignored for triggers fired AFTER an + operation, but it allows BEFORE triggers to: @@ -157,9 +57,10 @@ CREATE TRIGGER trigger [ BEFORE | AFTER ] [ INSERT | - For INSERT and UPDATE triggers only, the returned tuple becomes the - tuple which will be inserted or will replace the tuple being updated. - This allows the trigger function to modify the row being inserted or + For INSERT and UPDATE + triggers only, the returned tuple becomes the tuple which will + be inserted or will replace the tuple being updated. This + allows the trigger function to modify the row being inserted or updated. @@ -170,8 +71,9 @@ CREATE TRIGGER trigger [ BEFORE | AFTER ] [ INSERT | - Note that there is no initialization performed by the CREATE TRIGGER - handler. This may be changed in the future. + Note that there is no initialization performed by the + CREATE TRIGGER handler. This may be changed in + the future. @@ -184,15 +86,34 @@ CREATE TRIGGER trigger [ BEFORE | AFTER ] [ INSERT | - If a trigger function executes SQL-queries (using SPI) then these queries - may fire triggers again. This is known as cascading triggers. There is no - direct limitation on the number of cascade levels. It is possible for - cascades to cause recursive invocation of the same trigger --- for - example, an INSERT trigger might execute a query that inserts an - additional tuple into the same table, causing the INSERT trigger to be - fired again. It is the trigger programmer's - responsibility to avoid infinite recursion in such scenarios. + If a trigger function executes SQL-queries (using SPI) then these + queries may fire triggers again. This is known as cascading + triggers. There is no direct limitation on the number of cascade + levels. It is possible for cascades to cause recursive invocation + of the same trigger --- for example, an INSERT + trigger might execute a query that inserts an additional tuple + into the same table, causing the INSERT trigger + to be fired again. It is the trigger programmer's responsibility + to avoid infinite recursion in such scenarios. + + + When a trigger is defined, a number of arguments can be + specified. The purpose of including arguments in the trigger + definition is to allow different triggers with similar + requirements to call the same function. As an example, there + could be a generalized trigger function that takes as its + arguments two field names and puts the current user in one and the + current time stamp in the other. Properly written, this trigger + function would be independent of the specific table it is + triggering on. So the same function could be used for + INSERT events on any table with suitable + fields, to automatically track creation of records in a + transaction table for example. It could also be used to track + last-update events if defined as an UPDATE + trigger. + + @@ -215,18 +136,20 @@ CREATE TRIGGER trigger [ BEFORE | AFTER ] [ INSERT | - When a function is called by the trigger manager, it is not passed any - normal parameters, but it is passed a context pointer pointing to a - TriggerData structure. C functions can check whether they were called - from the trigger manager or not by executing the macro + When a function is called by the trigger manager, it is not passed + any normal parameters, but it is passed a context + pointer pointing to a TriggerData structure. C + functions can check whether they were called from the trigger + manager or not by executing the macro CALLED_AS_TRIGGER(fcinfo), which expands to ((fcinfo)->context != NULL && IsA((fcinfo)->context, TriggerData)) - If this returns true, then it is safe to cast fcinfo->context to type - TriggerData * and make use of the pointed-to - TriggerData structure. - The function must not alter the TriggerData + If this returns true, then it is safe to cast + fcinfo->context to type TriggerData + * and make use of the pointed-to + TriggerData structure. The function must + not alter the TriggerData structure or any of the data it points to. @@ -288,8 +211,7 @@ typedef struct TriggerData TRIGGER_FIRED_FOR_ROW(event) - Returns TRUE if trigger fired for - a ROW-level event. + Returns TRUE if trigger fired for a ROW-level event. @@ -298,8 +220,7 @@ typedef struct TriggerData TRIGGER_FIRED_FOR_STATEMENT(event) - Returns TRUE if trigger fired for - STATEMENT-level event. + Returns TRUE if trigger fired for STATEMENT-level event. @@ -308,7 +229,7 @@ typedef struct TriggerData TRIGGER_FIRED_BY_INSERT(event) - Returns TRUE if trigger fired by INSERT. + Returns TRUE if trigger fired by INSERT. @@ -317,7 +238,7 @@ typedef struct TriggerData TRIGGER_FIRED_BY_DELETE(event) - Returns TRUE if trigger fired by DELETE. + Returns TRUE if trigger fired by DELETE. @@ -326,7 +247,7 @@ typedef struct TriggerData TRIGGER_FIRED_BY_UPDATE(event) - Returns TRUE if trigger fired by UPDATE. + Returns TRUE if trigger fired by UPDATE. @@ -356,11 +277,15 @@ typedef struct TriggerData tg_trigtuple - is a pointer to the tuple for which the trigger is fired. This is the tuple - being inserted (if INSERT), deleted (if DELETE) or updated (if UPDATE). - If INSERT/DELETE then this is what you are to return to Executor if - you don't want to replace tuple with another one (INSERT) or skip the - operation. + is a pointer to the tuple for which the trigger is fired. This is + the tuple being inserted (if INSERT), deleted + (if DELETE) or updated (if + UPDATE). If this trigger was fired for an + INSERT or DELETE then this + is what you should return to the Executor if you don't want to + replace the tuple with a different one (in the case of + INSERT) or skip the operation (in the case of + DELETE). @@ -369,9 +294,11 @@ typedef struct TriggerData tg_newtuple - is a pointer to the new version of tuple if UPDATE and NULL if this is - for an INSERT or a DELETE. This is what you are to return to Executor if - UPDATE and you don't want to replace this tuple with another one or skip + is a pointer to the new version of tuple if + UPDATE and NULL if this is for an + INSERT or a DELETE. This is + what you are to return to Executor if UPDATE + and you don't want to replace this tuple with another one or skip the operation. @@ -404,8 +331,9 @@ typedef struct Trigger where tgname is the trigger's name, tgnargs is number of arguments in tgargs, tgargs is an array of - pointers to the arguments specified in the CREATE TRIGGER - statement. Other members are for internal use only. + pointers to the arguments specified in the CREATE + TRIGGER statement. Other members are for internal use + only. @@ -460,10 +388,12 @@ execution of Q) or after Q is done. - Here is a very simple example of trigger usage. Function trigf reports - the number of tuples in the triggered relation ttest and skips the - operation if the query attempts to insert a null value into x (i.e - it acts as a - not-null constraint but doesn't abort the transaction). + Here is a very simple example of trigger usage. Function + trigf reports the number of tuples in the triggered + relation ttest and skips the operation if the query + attempts to insert a null value into x (i.e - it acts as a + NOT NULL constraint but doesn't abort the + transaction). #include "executor/spi.h" /* this is what you need to work with SPI */ diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index 607a47f124..0f30e13c84 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 8dbde72be4..b0dd47f945 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 cda8687e44..e3c3d0c290 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 5c56a7ccfc..c9e2d87ff9 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 65afe08203..779d44a8e0 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 @@ -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 * diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 2c345b9f78..eb9bbe9365 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 61e314ff18..12781797c3 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 0b3bb279d5..29cba53f9f 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 c16e59038e..a22c57cb4c 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 dd6636bfad..3fa0b904ec 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 dc2bdd83d5..09b9e4ac69 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 219b2251f5..497a3622bf 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 92501196f9..0d33b56d1f 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 862790cfb7..24e4fae4a4 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 ed06e1861a..a9ff7325c1 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 6ae63b9b68..78ca61ade5 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 0f99d85462..549264107f 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 b5a62dace1..b8e49452b6 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 6358249116..214ffff446 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