Commit 1b7f3cc0 authored by Bruce Momjian's avatar Bruce Momjian

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

-hackers a couple days ago.

Notes/caveats:

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

        - added pg_dump support

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

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

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

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

Neil Conway
parent ea29b327
<!--
$Header: /cvsroot/pgsql/doc/src/sgml/plpgsql.sgml,v 1.11 2002/11/15 03:22:30 momjian Exp $
$Header: /cvsroot/pgsql/doc/src/sgml/plpgsql.sgml,v 1.12 2002/11/23 03:59:05 momjian Exp $
-->
<chapter id="plpgsql">
......@@ -674,24 +674,25 @@ RENAME this_var TO that_var;
<title>Expressions</title>
<para>
All expressions used in <application>PL/pgSQL</application> 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. <literal>'now'</literal> for the
<type>timestamp</type> type) so
it is impossible for the <application>PL/pgSQL</application> parser
to identify real constant values other than the NULL keyword. All
expressions are evaluated internally by executing a query
All expressions used in <application>PL/pgSQL</application>
statements are processed using the server's regular
<acronym>SQL</acronym> executor. Expressions that appear to
contain constants may in fact require run-time evaluation
(e.g. <literal>'now'</literal> for the <type>timestamp</type>
type) so it is impossible for the
<application>PL/pgSQL</application> parser to identify real
constant values other than the NULL keyword. All expressions are
evaluated internally by executing a query
<synopsis>
SELECT <replaceable>expression</replaceable>
</synopsis>
using the <acronym>SPI</acronym> manager. In the expression, occurrences
of <application>PL/pgSQL</application> variable
using the <acronym>SPI</acronym> manager. In the expression,
occurrences of <application>PL/pgSQL</application> 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 <command>SELECT</command> to
be prepared just once and then re-used for subsequent
evaluations.
</para>
<para>
......@@ -1100,41 +1101,43 @@ GET DIAGNOSTICS <replaceable>variable</replaceable> = <replaceable>item</replace
<itemizedlist>
<listitem>
<para>
A SELECT INTO statement sets <literal>FOUND</literal>
true if it returns a row, false if no row is returned.
A <command>SELECT INTO</command> statement sets
<literal>FOUND</literal> true if it returns a row, false if no
row is returned.
</para>
</listitem>
<listitem>
<para>
A PERFORM statement sets <literal>FOUND</literal>
A <command>PERFORM</> statement sets <literal>FOUND</literal>
true if it produces (discards) a row, false if no row is
produced.
</para>
</listitem>
<listitem>
<para>
UPDATE, INSERT, and DELETE statements set
<literal>FOUND</literal> true if at least one row is
affected, false if no row is affected.
<command>UPDATE</>, <command>INSERT</>, and <command>DELETE</>
statements set <literal>FOUND</literal> true if at least one
row is affected, false if no row is affected.
</para>
</listitem>
<listitem>
<para>
A FETCH statement sets <literal>FOUND</literal>
A <command>FETCH</> statement sets <literal>FOUND</literal>
true if it returns a row, false if no row is returned.
</para>
</listitem>
<listitem>
<para>
A FOR statement sets <literal>FOUND</literal>
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). <literal>FOUND</literal> is only set
when the FOR loop exits: inside the execution of the loop,
<literal>FOUND</literal> is not modified by the FOR statement,
although it may be changed by the execution of other
statements within the loop body.
A <command>FOR</> statement sets <literal>FOUND</literal> true
if it iterates one or more times, else false. This applies to
all three variants of the <command>FOR</> statement (integer
<command>FOR</> loops, record-set <command>FOR</> loops, and
dynamic record-set <command>FOR</>
loops). <literal>FOUND</literal> is only set when the
<command>FOR</> loop exits: inside the execution of the loop,
<literal>FOUND</literal> is not modified by the
<command>FOR</> statement, although it may be changed by the
execution of other statements within the loop body.
</para>
</listitem>
</itemizedlist>
......@@ -1975,7 +1978,7 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id;
<application>PL/pgSQL</application> can be used to define trigger
procedures. A trigger procedure is created with the
<command>CREATE FUNCTION</> command as a function with no
arguments and a return type of <type>TRIGGER</type>. Note that
arguments and a return type of <type>trigger</type>. Note that
the function must be declared with no arguments even if it expects
to receive arguments specified in <command>CREATE TRIGGER</> ---
trigger arguments are passed via <varname>TG_ARGV</>, as described
......@@ -1992,8 +1995,9 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id;
<term><varname>NEW</varname></term>
<listitem>
<para>
Data type <type>RECORD</type>; variable holding the new database row for INSERT/UPDATE
operations in ROW level triggers.
Data type <type>RECORD</type>; variable holding the new
database row for INSERT/UPDATE operations in ROW level
triggers. This variable is NULL in STATEMENT level triggers.
</para>
</listitem>
</varlistentry>
......@@ -2002,8 +2006,9 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id;
<term><varname>OLD</varname></term>
<listitem>
<para>
Data type <type>RECORD</type>; variable holding the old database row for UPDATE/DELETE
operations in ROW level triggers.
Data type <type>RECORD</type>; variable holding the old
database row for UPDATE/DELETE operations in ROW level
triggers. This variable is NULL in STATEMENT level triggers.
</para>
</listitem>
</varlistentry>
......@@ -2098,22 +2103,23 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id;
<para>
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.
</para>
<para>
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.
</para>
<example>
......@@ -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;
......
......@@ -153,8 +153,8 @@ ALTER TRIGGER emp_stamp ON emp RENAME TO emp_track_chgs;
</refsect2info>
<title>SQL92</title>
<para>
The clause to rename triggers is a
<productname>PostgreSQL</productname> extension from SQL92.
<command>ALTER TRIGGER</command> is a <productname>PostgreSQL</>
extension of SQL92.
</para>
</refsect2>
</refsect1>
......
<!--
$Header: /cvsroot/pgsql/doc/src/sgml/ref/create_trigger.sgml,v 1.29 2002/11/21 23:34:43 petere Exp $
$Header: /cvsroot/pgsql/doc/src/sgml/ref/create_trigger.sgml,v 1.30 2002/11/23 03:59:06 momjian Exp $
PostgreSQL documentation
-->
......@@ -21,8 +21,9 @@ PostgreSQL documentation
<date>2000-03-25</date>
</refsynopsisdivinfo>
<synopsis>
CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTER } { <replaceable class="PARAMETER">event</replaceable> [OR ...] }
ON <replaceable class="PARAMETER">table</replaceable> FOR EACH { ROW | STATEMENT }
CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> {
BEFORE | AFTER } { <replaceable class="PARAMETER">event</replaceable> [ OR ... ] }
ON <replaceable class="PARAMETER">table</replaceable> [ FOR EACH { ROW | STATEMENT } ]
EXECUTE PROCEDURE <replaceable class="PARAMETER">func</replaceable> ( <replaceable class="PARAMETER">arguments</replaceable> )
</synopsis>
......@@ -45,11 +46,26 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>BEFORE</term>
<term>AFTER</term>
<listitem>
<para>
Determines whether the function is called before or after the
event.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="parameter">event</replaceable></term>
<listitem>
<para>
One of INSERT, DELETE or UPDATE.
One of <command>INSERT</command>, <command>DELETE</command> or
<command>UPDATE</command>; this specifies the event that will
fire the trigger. Multiple events can be specified using
<literal>OR</literal>.
</para>
</listitem>
</varlistentry>
......@@ -57,10 +73,26 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
<term><replaceable class="parameter">table</replaceable></term>
<listitem>
<para>
The name (optionally schema-qualified) of the table the trigger is for.
The name (optionally schema-qualified) of the table the
trigger is for.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>FOR EACH ROW</term>
<term>FOR EACH STATEMENT</term>
<listitem>
<para>
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, <literal>FOR EACH
STATEMENT</literal> is the default.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="parameter">func</replaceable></term>
<listitem>
......@@ -74,11 +106,15 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
<term><replaceable class="parameter">arguments</replaceable></term>
<listitem>
<para>
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
<literal>TG_ARGV</literal> array.
</para>
</listitem>
</varlistentry>
......@@ -121,7 +157,7 @@ CREATE TRIGGER
<para>
<command>CREATE TRIGGER</command> 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
<replaceable class="parameter">table</replaceable> and will execute
the specified function <replaceable class="parameter">func</replaceable>.
</para>
......@@ -141,15 +177,27 @@ CREATE TRIGGER
or deletion, are <quote>visible</quote> to the trigger.
</para>
<para>
A trigger that executes <literal>FOR EACH ROW</literal> of the
specified operation is called once for every row that the operation
modifies. For example, a <command>DELETE</command> that affects 10
rows will cause any <literal>ON DELETE</literal> triggers on the
target relation to be called 10 separate times, once for each
deleted tuple. In contrast, a trigger that executes <literal>FOR
EACH STATEMENT</literal> of the specified operation only executes
once for any given operation, regardless of how many rows it
modifies.
</para>
<para>
If multiple triggers of the same kind are defined for the same event,
they will be fired in alphabetical order by name.
</para>
<para>
<command>SELECT</command> does not modify any rows so you can not
create <command>SELECT</command> triggers. Rules and views are more
appropriate in such cases.
<command>SELECT</command> does not modify any rows so you can not
create <command>SELECT</command> triggers. Rules and views are more
appropriate in such cases.
</para>
<para>
......@@ -176,10 +224,6 @@ CREATE TRIGGER
change the function's declared return type to <type>trigger</>.
</para>
<para>
As of the current release, <literal>STATEMENT</literal> triggers are not implemented.
</para>
<para>
Refer to the <xref linkend="sql-droptrigger" endterm="sql-droptrigger-title"> command for
information on how to remove triggers.
......@@ -268,13 +312,6 @@ CREATE TABLE distributors (
</para>
</listitem>
<listitem>
<para>
<productname>PostgreSQL</productname> only has row-level
triggers, no statement-level triggers.
</para>
</listitem>
<listitem>
<para>
<productname>PostgreSQL</productname> only allows the
......
<!--
$Header: /cvsroot/pgsql/doc/src/sgml/release.sgml,v 1.166 2002/11/23 02:41:03 tgl Exp $
$Header: /cvsroot/pgsql/doc/src/sgml/release.sgml,v 1.167 2002/11/23 03:59:06 momjian Exp $
-->
<appendix id="release">
......@@ -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
......
This diff is collapsed.
......@@ -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
* --------------------------------
......
......@@ -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);
......
......@@ -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;
......
This diff is collapsed.
......@@ -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 *
......
......@@ -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;
......
......@@ -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)
......
......@@ -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); }
......
/* ----------
* 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!
*/
......
......@@ -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.
......
......@@ -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 (",
......
......@@ -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,
......
......@@ -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 */
......
/* ----------
* 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;
......
......@@ -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];
......
......@@ -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)
......
......@@ -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
{
......
......@@ -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)
......@@ -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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment