-hackers a couple days ago.
Notes/caveats:
- added regression tests for the new functionality, all
regression tests pass on my machine
- added pg_dump support
- updated PL/PgSQL to support per-statement triggers; didn't
look at the other procedural languages.
- there's (even) more code duplication in trigger.c than there
was previously. Any suggestions on how to refactor the
ExecXXXTriggers() functions to reuse more code would be
welcome -- I took a brief look at it, but couldn't see an
easy way to do it (there are several subtly-different
versions of the code in question)
- updated the documentation. I also took the liberty of
removing a big chunk of duplicated syntax documentation in
the Programmer's Guide on triggers, and moving that
information to the CREATE TRIGGER reference page.
- I also included some spelling fixes and similar small
cleanups I noticed while making the changes. If you'd like
me to split those into a separate patch, let me know.
Neil Conway
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
+ using the
SPI manager. In the expression,
+ o
ccurrences 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.
- 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.
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
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.
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.
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.
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;
SQL92
- The clause to rename triggers is a
-
PostgreSQL extension from SQL92.
+
ALTER TRIGGER is a
PostgreSQL>
+ extension of SQL92.
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 )
+
+
+ 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.
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
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.
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.
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.
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.
-
-
PostgreSQL only has row-level
- triggers, no statement-level triggers.
-
-
-
PostgreSQL only allows the
* 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
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.
- 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:
- 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.
- 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.
- 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.
+
+
- 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.
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.
TRIGGER_FIRED_FOR_STATEMENT(event)
- Returns TRUE if trigger fired for
- STATEMENT-level event.
+ Returns TRUE if trigger fired for STATEMENT-level event.
TRIGGER_FIRED_BY_INSERT(event)
- Returns TRUE if trigger fired by INSERT.
+ Returns TRUE if trigger fired by INSERT.
TRIGGER_FIRED_BY_DELETE(event)
- Returns TRUE if trigger fired by DELETE.
+ Returns TRUE if trigger fired by DELETE.
TRIGGER_FIRED_BY_UPDATE(event)
- Returns TRUE if trigger fired by UPDATE.
+ Returns TRUE if trigger fired by UPDATE.
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).
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.
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.
- 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 */
*
*
* 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:
}
-#ifdef NOT_USED
-/* ---------------
- * Tell me if we are currently in progress
- * ---------------
- */
-bool
-CurrentXactInProgress(void)
-{
- return CurrentTransactionState->state == TRANS_INPROGRESS;
-}
-#endif
-
/* --------------------------------
* CommitTransaction
* --------------------------------
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
}
}
+ /*
+ * 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... */
ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
/* AFTER ROW INSERT Triggers */
- if (resultRelInfo->ri_TrigDesc)
- ExecARInsertTriggers(estate, resultRelInfo, tuple);
+ ExecARInsertTriggers(estate, resultRelInfo, tuple);
}
}
*/
copy_lineno = 0;
+ /*
+ * Execute AFTER STATEMENT insertion triggers
+ */
+ ExecASInsertTriggers(estate, resultRelInfo);
+
MemoryContextSwitchTo(oldcontext);
pfree(values);
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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;
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;
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;
* 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 $
*
*-------------------------------------------------------------------------
*/
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);
{
/* 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));
}
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));
}
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])
{
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)
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++)
{
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
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++)
{
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)
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);
{
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);
}
}
case HeapTupleSelfUpdated:
/* treat it as deleted; do not process */
ReleaseBuffer(buffer);
- return (NULL);
+ return NULL;
case HeapTupleMayBeUpdated:
break;
* 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
/*
* 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;
*/
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;
* ----------
*/
static void
-DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
+DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
HeapTuple oldtup, HeapTuple newtup)
{
Relation rel = relinfo->ri_RelationDesc;
int *tgindx;
ItemPointerData oldctid;
ItemPointerData newctid;
- TriggerData LocTriggerData;
if (deftrig_cxt == NULL)
elog(ERROR,
*/
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));
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);
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
bool is_ri_trigger;
bool key_unchanged;
+ TriggerData LocTriggerData;
/*
* We are interested in RI_FKEY triggers only.
*
* 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
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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
*/
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.
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.
/* 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;
ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
/* AFTER ROW INSERT Triggers */
- if (resultRelInfo->ri_TrigDesc)
- ExecARInsertTriggers(estate, resultRelInfo, tuple);
+ ExecARInsertTriggers(estate, resultRelInfo, tuple);
}
/* ----------------------------------------------------------------
*/
/* AFTER ROW DELETE Triggers */
- if (resultRelInfo->ri_TrigDesc)
- ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
+ ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
}
/* ----------------------------------------------------------------
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 *
* 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 $
*
*-------------------------------------------------------------------------
*/
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;
* 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 $
*
*-------------------------------------------------------------------------
*/
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)
*
*
* 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
/*****************************************************************************
*
* QUERY :
- * CREATE relname
+ * CREATE TABLE relname
*
*****************************************************************************/
n->before = $4;
n->row = $8;
memcpy (n->actions, $5, 4);
- n->lang = NULL; /* unused */
- n->text = NULL; /* unused */
- n->attr = NULL; /* unused */
- n->when = NULL; /* unused */
-
n->isconstraint = FALSE;
n->deferrable = FALSE;
n->initdeferred = FALSE;
n->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;
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;
}
{
$$ = $3;
}
+ | /* EMPTY */
+ {
+ /*
+ * If ROW/STATEMENT not specified, default to
+ * STATEMENT, per SQL
+ */
+ $$ = FALSE;
+ }
;
TriggerForOpt:
ICONST
{
char buf[64];
- snprintf (buf, sizeof(buf), "%d", $1);
+ snprintf(buf, sizeof(buf), "%d", $1);
$$ = makeString(pstrdup(buf));
}
| FCONST { $$ = makeString($1); }
/* ----------
* 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
* 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.
*
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
* 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 = {
0, /* */
0, /* */
0, /* Zero indicates "store uncompressed
- * allways" */
+ * always" */
0 /* */
};
PGLZ_Strategy *PGLZ_strategy_never = &strategy_never_data;
/*
* 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!
*/
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
/*
* 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.
* 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 $
*
*-------------------------------------------------------------------------
*/
}
- 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 (",
* 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 $
*
*-------------------------------------------------------------------------
*/
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,
* 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 $
*
*-------------------------------------------------------------------------
*/
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 */
/* ----------
* 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
* ----------
* 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
* 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.
* ----------
*/
extern PGLZ_Strategy *PGLZ_strategy_default;
-extern PGLZ_Strategy *PGLZ_strategy_allways;
+extern PGLZ_Strategy *PGLZ_strategy_always;
extern PGLZ_Strategy *PGLZ_strategy_never;
* 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 $
*
*-------------------------------------------------------------------------
*/
* 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];
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)
* 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.
*
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;
/*
}
/*
- * 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;
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))
{
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))
{
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;
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;
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;
/*
* 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
{
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
--
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)
+
-- -- 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
--
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