This patch implements FOR EACH STATEMENT triggers, per my email to
-hackers a couple days ago. Notes/caveats: - added regression tests for the new functionality, all regression tests pass on my machine - added pg_dump support - updated PL/PgSQL to support per-statement triggers; didn't look at the other procedural languages. - there's (even) more code duplication in trigger.c than there was previously. Any suggestions on how to refactor the ExecXXXTriggers() functions to reuse more code would be welcome -- I took a brief look at it, but couldn't see an easy way to do it (there are several subtly-different versions of the code in question) - updated the documentation. I also took the liberty of removing a big chunk of duplicated syntax documentation in the Programmer's Guide on triggers, and moving that information to the CREATE TRIGGER reference page. - I also included some spelling fixes and similar small cleanups I noticed while making the changes. If you'd like me to split those into a separate patch, let me know. Neil Conway
This commit is contained in:
parent
ea29b32758
commit
1b7f3cc02d
@ -1,5 +1,5 @@
|
|||||||
<!--
|
<!--
|
||||||
$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">
|
<chapter id="plpgsql">
|
||||||
@ -674,24 +674,25 @@ RENAME this_var TO that_var;
|
|||||||
<title>Expressions</title>
|
<title>Expressions</title>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
All expressions used in <application>PL/pgSQL</application> statements
|
All expressions used in <application>PL/pgSQL</application>
|
||||||
are processed using the server's regular SQL executor. Expressions that
|
statements are processed using the server's regular
|
||||||
appear to contain
|
<acronym>SQL</acronym> executor. Expressions that appear to
|
||||||
constants may in fact require run-time evaluation
|
contain constants may in fact require run-time evaluation
|
||||||
(e.g. <literal>'now'</literal> for the
|
(e.g. <literal>'now'</literal> for the <type>timestamp</type>
|
||||||
<type>timestamp</type> type) so
|
type) so it is impossible for the
|
||||||
it is impossible for the <application>PL/pgSQL</application> parser
|
<application>PL/pgSQL</application> parser to identify real
|
||||||
to identify real constant values other than the NULL keyword. All
|
constant values other than the NULL keyword. All expressions are
|
||||||
expressions are evaluated internally by executing a query
|
evaluated internally by executing a query
|
||||||
<synopsis>
|
<synopsis>
|
||||||
SELECT <replaceable>expression</replaceable>
|
SELECT <replaceable>expression</replaceable>
|
||||||
</synopsis>
|
</synopsis>
|
||||||
using the <acronym>SPI</acronym> manager. In the expression, occurrences
|
using the <acronym>SPI</acronym> manager. In the expression,
|
||||||
of <application>PL/pgSQL</application> variable
|
occurrences of <application>PL/pgSQL</application> variable
|
||||||
identifiers are replaced by parameters and the actual values from
|
identifiers are replaced by parameters and the actual values from
|
||||||
the variables are passed to the executor in the parameter array.
|
the variables are passed to the executor in the parameter array.
|
||||||
This allows the query plan for the SELECT to be prepared just once
|
This allows the query plan for the <command>SELECT</command> to
|
||||||
and then re-used for subsequent evaluations.
|
be prepared just once and then re-used for subsequent
|
||||||
|
evaluations.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
@ -1100,41 +1101,43 @@ GET DIAGNOSTICS <replaceable>variable</replaceable> = <replaceable>item</replace
|
|||||||
<itemizedlist>
|
<itemizedlist>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
A SELECT INTO statement sets <literal>FOUND</literal>
|
A <command>SELECT INTO</command> statement sets
|
||||||
true if it returns a row, false if no row is returned.
|
<literal>FOUND</literal> true if it returns a row, false if no
|
||||||
|
row is returned.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<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
|
true if it produces (discards) a row, false if no row is
|
||||||
produced.
|
produced.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
UPDATE, INSERT, and DELETE statements set
|
<command>UPDATE</>, <command>INSERT</>, and <command>DELETE</>
|
||||||
<literal>FOUND</literal> true if at least one row is
|
statements set <literal>FOUND</literal> true if at least one
|
||||||
affected, false if no row is affected.
|
row is affected, false if no row is affected.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<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.
|
true if it returns a row, false if no row is returned.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
A FOR statement sets <literal>FOUND</literal>
|
A <command>FOR</> statement sets <literal>FOUND</literal> true
|
||||||
true if it iterates one or more times, else false.
|
if it iterates one or more times, else false. This applies to
|
||||||
This applies to all three variants of the FOR statement
|
all three variants of the <command>FOR</> statement (integer
|
||||||
(integer FOR loops, record-set FOR loops, and dynamic
|
<command>FOR</> loops, record-set <command>FOR</> loops, and
|
||||||
record-set FOR loops). <literal>FOUND</literal> is only set
|
dynamic record-set <command>FOR</>
|
||||||
when the FOR loop exits: inside the execution of the loop,
|
loops). <literal>FOUND</literal> is only set when the
|
||||||
<literal>FOUND</literal> is not modified by the FOR statement,
|
<command>FOR</> loop exits: inside the execution of the loop,
|
||||||
although it may be changed by the execution of other
|
<literal>FOUND</literal> is not modified by the
|
||||||
statements within the loop body.
|
<command>FOR</> statement, although it may be changed by the
|
||||||
|
execution of other statements within the loop body.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</itemizedlist>
|
</itemizedlist>
|
||||||
@ -1975,7 +1978,7 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id;
|
|||||||
<application>PL/pgSQL</application> can be used to define trigger
|
<application>PL/pgSQL</application> can be used to define trigger
|
||||||
procedures. A trigger procedure is created with the
|
procedures. A trigger procedure is created with the
|
||||||
<command>CREATE FUNCTION</> command as a function with no
|
<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
|
the function must be declared with no arguments even if it expects
|
||||||
to receive arguments specified in <command>CREATE TRIGGER</> ---
|
to receive arguments specified in <command>CREATE TRIGGER</> ---
|
||||||
trigger arguments are passed via <varname>TG_ARGV</>, as described
|
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>
|
<term><varname>NEW</varname></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Data type <type>RECORD</type>; variable holding the new database row for INSERT/UPDATE
|
Data type <type>RECORD</type>; variable holding the new
|
||||||
operations in ROW level triggers.
|
database row for INSERT/UPDATE operations in ROW level
|
||||||
|
triggers. This variable is NULL in STATEMENT level triggers.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -2002,8 +2006,9 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id;
|
|||||||
<term><varname>OLD</varname></term>
|
<term><varname>OLD</varname></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Data type <type>RECORD</type>; variable holding the old database row for UPDATE/DELETE
|
Data type <type>RECORD</type>; variable holding the old
|
||||||
operations in ROW level triggers.
|
database row for UPDATE/DELETE operations in ROW level
|
||||||
|
triggers. This variable is NULL in STATEMENT level triggers.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -2098,22 +2103,23 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id;
|
|||||||
|
|
||||||
<para>
|
<para>
|
||||||
A trigger function must return either NULL or a record/row value
|
A trigger function must return either NULL or a record/row value
|
||||||
having exactly the structure of the table the trigger was fired for.
|
having exactly the structure of the table the trigger was fired
|
||||||
Triggers fired BEFORE may return NULL to signal the trigger manager
|
for. The return value of a BEFORE or AFTER STATEMENT level
|
||||||
to skip the rest of the operation for this row (ie, subsequent triggers
|
trigger, or an AFTER ROW level trigger is ignored; it may as well
|
||||||
are not fired, and the INSERT/UPDATE/DELETE does not occur for this
|
return NULL. However, any of these types of triggers can still
|
||||||
row). If a non-NULL value is returned then the operation proceeds with
|
abort the entire trigger operation by raising an error.
|
||||||
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>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
The return value of a trigger fired AFTER is ignored; it may as well
|
ROW level triggers fired BEFORE may return NULL to signal the
|
||||||
always return a NULL value. But an AFTER trigger can still abort the
|
trigger manager to skip the rest of the operation for this row
|
||||||
operation by raising an error.
|
(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>
|
</para>
|
||||||
|
|
||||||
<example>
|
<example>
|
||||||
@ -2143,7 +2149,7 @@ CREATE FUNCTION emp_stamp () RETURNS TRIGGER AS '
|
|||||||
RAISE EXCEPTION ''% cannot have NULL salary'', NEW.empname;
|
RAISE EXCEPTION ''% cannot have NULL salary'', NEW.empname;
|
||||||
END IF;
|
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
|
IF NEW.salary < 0 THEN
|
||||||
RAISE EXCEPTION ''% cannot have a negative salary'', NEW.empname;
|
RAISE EXCEPTION ''% cannot have a negative salary'', NEW.empname;
|
||||||
END IF;
|
END IF;
|
||||||
|
@ -153,8 +153,8 @@ ALTER TRIGGER emp_stamp ON emp RENAME TO emp_track_chgs;
|
|||||||
</refsect2info>
|
</refsect2info>
|
||||||
<title>SQL92</title>
|
<title>SQL92</title>
|
||||||
<para>
|
<para>
|
||||||
The clause to rename triggers is a
|
<command>ALTER TRIGGER</command> is a <productname>PostgreSQL</>
|
||||||
<productname>PostgreSQL</productname> extension from SQL92.
|
extension of SQL92.
|
||||||
</para>
|
</para>
|
||||||
</refsect2>
|
</refsect2>
|
||||||
</refsect1>
|
</refsect1>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<!--
|
<!--
|
||||||
$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
|
PostgreSQL documentation
|
||||||
-->
|
-->
|
||||||
|
|
||||||
@ -21,8 +21,9 @@ PostgreSQL documentation
|
|||||||
<date>2000-03-25</date>
|
<date>2000-03-25</date>
|
||||||
</refsynopsisdivinfo>
|
</refsynopsisdivinfo>
|
||||||
<synopsis>
|
<synopsis>
|
||||||
CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTER } { <replaceable class="PARAMETER">event</replaceable> [OR ...] }
|
CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> {
|
||||||
ON <replaceable class="PARAMETER">table</replaceable> FOR EACH { ROW | STATEMENT }
|
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> )
|
EXECUTE PROCEDURE <replaceable class="PARAMETER">func</replaceable> ( <replaceable class="PARAMETER">arguments</replaceable> )
|
||||||
</synopsis>
|
</synopsis>
|
||||||
|
|
||||||
@ -45,11 +46,26 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
|
|||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</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>
|
<varlistentry>
|
||||||
<term><replaceable class="parameter">event</replaceable></term>
|
<term><replaceable class="parameter">event</replaceable></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<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>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -57,10 +73,26 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
|
|||||||
<term><replaceable class="parameter">table</replaceable></term>
|
<term><replaceable class="parameter">table</replaceable></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<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>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</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>
|
<varlistentry>
|
||||||
<term><replaceable class="parameter">func</replaceable></term>
|
<term><replaceable class="parameter">func</replaceable></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
@ -74,11 +106,15 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
|
|||||||
<term><replaceable class="parameter">arguments</replaceable></term>
|
<term><replaceable class="parameter">arguments</replaceable></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
An optional comma-separated list of arguments to be provided to the
|
An optional comma-separated list of arguments to be provided to
|
||||||
function when the trigger is executed, along with the standard trigger
|
the function when the trigger is executed, along with the standard
|
||||||
data such as old and new tuple contents. The arguments are literal
|
trigger data such as old and new tuple contents. The arguments
|
||||||
string constants. Simple names and numeric constants may be written
|
are literal string constants. Simple names and numeric constants
|
||||||
here too, but they will all be converted to strings.
|
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>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -121,7 +157,7 @@ CREATE TRIGGER
|
|||||||
|
|
||||||
<para>
|
<para>
|
||||||
<command>CREATE TRIGGER</command> will enter a new trigger into the current
|
<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
|
<replaceable class="parameter">table</replaceable> and will execute
|
||||||
the specified function <replaceable class="parameter">func</replaceable>.
|
the specified function <replaceable class="parameter">func</replaceable>.
|
||||||
</para>
|
</para>
|
||||||
@ -141,15 +177,27 @@ CREATE TRIGGER
|
|||||||
or deletion, are <quote>visible</quote> to the trigger.
|
or deletion, are <quote>visible</quote> to the trigger.
|
||||||
</para>
|
</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>
|
<para>
|
||||||
If multiple triggers of the same kind are defined for the same event,
|
If multiple triggers of the same kind are defined for the same event,
|
||||||
they will be fired in alphabetical order by name.
|
they will be fired in alphabetical order by name.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
<command>SELECT</command> does not modify any rows so you can not
|
<command>SELECT</command> does not modify any rows so you can not
|
||||||
create <command>SELECT</command> triggers. Rules and views are more
|
create <command>SELECT</command> triggers. Rules and views are more
|
||||||
appropriate in such cases.
|
appropriate in such cases.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
@ -176,10 +224,6 @@ CREATE TRIGGER
|
|||||||
change the function's declared return type to <type>trigger</>.
|
change the function's declared return type to <type>trigger</>.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
|
||||||
As of the current release, <literal>STATEMENT</literal> triggers are not implemented.
|
|
||||||
</para>
|
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
Refer to the <xref linkend="sql-droptrigger" endterm="sql-droptrigger-title"> command for
|
Refer to the <xref linkend="sql-droptrigger" endterm="sql-droptrigger-title"> command for
|
||||||
information on how to remove triggers.
|
information on how to remove triggers.
|
||||||
@ -268,13 +312,6 @@ CREATE TABLE distributors (
|
|||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
|
||||||
<listitem>
|
|
||||||
<para>
|
|
||||||
<productname>PostgreSQL</productname> only has row-level
|
|
||||||
triggers, no statement-level triggers.
|
|
||||||
</para>
|
|
||||||
</listitem>
|
|
||||||
|
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
<productname>PostgreSQL</productname> only allows the
|
<productname>PostgreSQL</productname> only allows the
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<!--
|
<!--
|
||||||
$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">
|
<appendix id="release">
|
||||||
@ -4619,7 +4619,7 @@ Enhancements
|
|||||||
* pg_dump now output the schema and/or the data, with many fixes to
|
* pg_dump now output the schema and/or the data, with many fixes to
|
||||||
enhance completeness.
|
enhance completeness.
|
||||||
* psql used in place of monitor in administration shell scripts.
|
* 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
|
* date/time functions enhanced
|
||||||
* NULL insert/update/comparison fixed/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
|
* TCL/TK lib and shell fixed to work with both tck7.4/tk4.0 and tcl7.5/tk4.1
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<!--
|
<!--
|
||||||
$Header: /cvsroot/pgsql/doc/src/sgml/trigger.sgml,v 1.25 2002/09/21 18:32:54 petere Exp $
|
$Header: /cvsroot/pgsql/doc/src/sgml/trigger.sgml,v 1.26 2002/11/23 03:59:06 momjian Exp $
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<chapter id="triggers">
|
<chapter id="triggers">
|
||||||
@ -7,21 +7,24 @@ $Header: /cvsroot/pgsql/doc/src/sgml/trigger.sgml,v 1.25 2002/09/21 18:32:54 pet
|
|||||||
|
|
||||||
<para>
|
<para>
|
||||||
<productname>PostgreSQL</productname> has various server-side
|
<productname>PostgreSQL</productname> has various server-side
|
||||||
function interfaces. Server-side functions can be written in SQL,
|
function interfaces. Server-side functions can be written in
|
||||||
C, or any defined procedural language. Trigger functions can be
|
<acronym>SQL</acronym>, C, or any defined procedural
|
||||||
written in C and most procedural languages, but not in SQL. Note that
|
language. Trigger functions can be written in C and most procedural
|
||||||
statement-level trigger events are not supported in the current
|
languages, but not in <acronym>SQL</acronym>. Both per-row and
|
||||||
version. You can currently specify BEFORE or AFTER on INSERT,
|
per-statement triggers are supported. A trigger procedure can
|
||||||
DELETE or UPDATE of a tuple as a trigger event.
|
execute BEFORE or AFTER a <command>INSERT</command>,
|
||||||
|
<command>DELETE</command> or <command>UPDATE</command>, either once
|
||||||
|
per modified row, or once per <acronym>SQL</acronym> statement.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<sect1 id="trigger-definition">
|
<sect1 id="trigger-definition">
|
||||||
<title>Trigger Definition</title>
|
<title>Trigger Definition</title>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
If a trigger event occurs, the trigger manager (called by the Executor)
|
If a trigger event occurs, the trigger manager (called by the
|
||||||
sets up a <structname>TriggerData</> information structure (described below) and calls
|
Executor) sets up a <structname>TriggerData</> information
|
||||||
the trigger function to handle the event.
|
structure (described below) and calls the trigger function to
|
||||||
|
handle the event.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
@ -35,116 +38,13 @@ $Header: /cvsroot/pgsql/doc/src/sgml/trigger.sgml,v 1.25 2002/09/21 18:32:54 pet
|
|||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
The syntax for creating triggers is:
|
The syntax for creating triggers is described in &cite-reference;.
|
||||||
|
|
||||||
<programlisting>
|
|
||||||
CREATE TRIGGER <replaceable>trigger</replaceable> [ BEFORE | AFTER ] [ INSERT | DELETE | UPDATE [ OR ... ] ]
|
|
||||||
ON <replaceable>relation</replaceable> FOR EACH [ ROW | STATEMENT ]
|
|
||||||
EXECUTE PROCEDURE <replaceable>procedure</replaceable>
|
|
||||||
(<replaceable>args</replaceable>);
|
|
||||||
</programlisting>
|
|
||||||
|
|
||||||
where the arguments are:
|
|
||||||
|
|
||||||
<variablelist>
|
|
||||||
<varlistentry>
|
|
||||||
<term>
|
|
||||||
<replaceable>trigger</replaceable>
|
|
||||||
</term>
|
|
||||||
<listitem>
|
|
||||||
<para>
|
|
||||||
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.
|
|
||||||
</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>INSERT</term>
|
|
||||||
<term>DELETE</term>
|
|
||||||
<term>UPDATE</term>
|
|
||||||
<listitem>
|
|
||||||
<para>
|
|
||||||
The next element of the command determines what event(s) will trigger
|
|
||||||
the function. Multiple events can be specified separated by OR.
|
|
||||||
</para>
|
|
||||||
</listitem>
|
|
||||||
</varlistentry>
|
|
||||||
|
|
||||||
<varlistentry>
|
|
||||||
<term><replaceable>relation</replaceable></term>
|
|
||||||
<listitem>
|
|
||||||
<para>
|
|
||||||
The relation name indicates which table the event applies to.
|
|
||||||
</para>
|
|
||||||
</listitem>
|
|
||||||
</varlistentry>
|
|
||||||
|
|
||||||
<varlistentry>
|
|
||||||
<term>ROW</term>
|
|
||||||
<term>STATEMENT</term>
|
|
||||||
<listitem>
|
|
||||||
<para>
|
|
||||||
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.
|
|
||||||
</para>
|
|
||||||
</listitem>
|
|
||||||
</varlistentry>
|
|
||||||
|
|
||||||
<varlistentry>
|
|
||||||
<term><replaceable>procedure</replaceable></term>
|
|
||||||
<listitem>
|
|
||||||
<para>
|
|
||||||
The procedure name is the function to be called.
|
|
||||||
</para>
|
|
||||||
</listitem>
|
|
||||||
</varlistentry>
|
|
||||||
|
|
||||||
<varlistentry>
|
|
||||||
<term><replaceable>args</replaceable></term>
|
|
||||||
<listitem>
|
|
||||||
<para>
|
|
||||||
The arguments passed to the function in the <structname>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).
|
|
||||||
</para>
|
|
||||||
|
|
||||||
<para>
|
|
||||||
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.
|
|
||||||
</para>
|
|
||||||
</listitem>
|
|
||||||
</varlistentry>
|
|
||||||
</variablelist>
|
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
Trigger functions return a <structname>HeapTuple</> to the calling executor. The return
|
Trigger functions return a <structname>HeapTuple</> to the calling
|
||||||
value is ignored for triggers fired AFTER an operation,
|
executor. The return value is ignored for triggers fired AFTER an
|
||||||
but it allows BEFORE triggers to:
|
operation, but it allows BEFORE triggers to:
|
||||||
|
|
||||||
<itemizedlist>
|
<itemizedlist>
|
||||||
<listitem>
|
<listitem>
|
||||||
@ -157,9 +57,10 @@ CREATE TRIGGER <replaceable>trigger</replaceable> [ BEFORE | AFTER ] [ INSERT |
|
|||||||
|
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
For INSERT and UPDATE triggers only, the returned tuple becomes the
|
For <command>INSERT</command> and <command>UPDATE</command>
|
||||||
tuple which will be inserted or will replace the tuple being updated.
|
triggers only, the returned tuple becomes the tuple which will
|
||||||
This allows the trigger function to modify the row being inserted or
|
be inserted or will replace the tuple being updated. This
|
||||||
|
allows the trigger function to modify the row being inserted or
|
||||||
updated.
|
updated.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
@ -170,8 +71,9 @@ CREATE TRIGGER <replaceable>trigger</replaceable> [ BEFORE | AFTER ] [ INSERT |
|
|||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
Note that there is no initialization performed by the CREATE TRIGGER
|
Note that there is no initialization performed by the
|
||||||
handler. This may be changed in the future.
|
<command>CREATE TRIGGER</command> handler. This may be changed in
|
||||||
|
the future.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
@ -184,15 +86,34 @@ CREATE TRIGGER <replaceable>trigger</replaceable> [ BEFORE | AFTER ] [ INSERT |
|
|||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
If a trigger function executes SQL-queries (using SPI) then these queries
|
If a trigger function executes SQL-queries (using SPI) then these
|
||||||
may fire triggers again. This is known as cascading triggers. There is no
|
queries may fire triggers again. This is known as cascading
|
||||||
direct limitation on the number of cascade levels. It is possible for
|
triggers. There is no direct limitation on the number of cascade
|
||||||
cascades to cause recursive invocation of the same trigger --- for
|
levels. It is possible for cascades to cause recursive invocation
|
||||||
example, an INSERT trigger might execute a query that inserts an
|
of the same trigger --- for example, an <command>INSERT</command>
|
||||||
additional tuple into the same table, causing the INSERT trigger to be
|
trigger might execute a query that inserts an additional tuple
|
||||||
fired again. It is the trigger programmer's
|
into the same table, causing the <command>INSERT</command> trigger
|
||||||
responsibility to avoid infinite recursion in such scenarios.
|
to be fired again. It is the trigger programmer's responsibility
|
||||||
|
to avoid infinite recursion in such scenarios.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
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
|
||||||
|
<command>INSERT</command> 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 <command>UPDATE</command>
|
||||||
|
trigger.
|
||||||
|
</para>
|
||||||
|
|
||||||
</sect1>
|
</sect1>
|
||||||
|
|
||||||
<sect1 id="trigger-manager">
|
<sect1 id="trigger-manager">
|
||||||
@ -215,18 +136,20 @@ CREATE TRIGGER <replaceable>trigger</replaceable> [ BEFORE | AFTER ] [ INSERT |
|
|||||||
</note>
|
</note>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
When a function is called by the trigger manager, it is not passed any
|
When a function is called by the trigger manager, it is not passed
|
||||||
normal parameters, but it is passed a <quote>context</> pointer pointing to a
|
any normal parameters, but it is passed a <quote>context</>
|
||||||
<structname>TriggerData</> structure. C functions can check whether they were called
|
pointer pointing to a <structname>TriggerData</> structure. C
|
||||||
from the trigger manager or not by executing the macro
|
functions can check whether they were called from the trigger
|
||||||
|
manager or not by executing the macro
|
||||||
<literal>CALLED_AS_TRIGGER(fcinfo)</literal>, which expands to
|
<literal>CALLED_AS_TRIGGER(fcinfo)</literal>, which expands to
|
||||||
<programlisting>
|
<programlisting>
|
||||||
((fcinfo)->context != NULL && IsA((fcinfo)->context, TriggerData))
|
((fcinfo)->context != NULL && IsA((fcinfo)->context, TriggerData))
|
||||||
</programlisting>
|
</programlisting>
|
||||||
If this returns true, then it is safe to cast <literal>fcinfo->context</> to type
|
If this returns true, then it is safe to cast
|
||||||
<literal>TriggerData *</literal> and make use of the pointed-to
|
<literal>fcinfo->context</> to type <literal>TriggerData
|
||||||
<structname>TriggerData</> structure.
|
*</literal> and make use of the pointed-to
|
||||||
The function must <emphasis>not</emphasis> alter the <structname>TriggerData</>
|
<structname>TriggerData</> structure. The function must
|
||||||
|
<emphasis>not</emphasis> alter the <structname>TriggerData</>
|
||||||
structure or any of the data it points to.
|
structure or any of the data it points to.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
@ -288,8 +211,7 @@ typedef struct TriggerData
|
|||||||
<term>TRIGGER_FIRED_FOR_ROW(event)</term>
|
<term>TRIGGER_FIRED_FOR_ROW(event)</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Returns TRUE if trigger fired for
|
Returns TRUE if trigger fired for a ROW-level event.
|
||||||
a ROW-level event.
|
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -298,8 +220,7 @@ typedef struct TriggerData
|
|||||||
<term>TRIGGER_FIRED_FOR_STATEMENT(event)</term>
|
<term>TRIGGER_FIRED_FOR_STATEMENT(event)</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Returns TRUE if trigger fired for
|
Returns TRUE if trigger fired for STATEMENT-level event.
|
||||||
STATEMENT-level event.
|
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -308,7 +229,7 @@ typedef struct TriggerData
|
|||||||
<term>TRIGGER_FIRED_BY_INSERT(event)</term>
|
<term>TRIGGER_FIRED_BY_INSERT(event)</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Returns TRUE if trigger fired by INSERT.
|
Returns TRUE if trigger fired by <command>INSERT</command>.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -317,7 +238,7 @@ typedef struct TriggerData
|
|||||||
<term>TRIGGER_FIRED_BY_DELETE(event)</term>
|
<term>TRIGGER_FIRED_BY_DELETE(event)</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Returns TRUE if trigger fired by DELETE.
|
Returns TRUE if trigger fired by <command>DELETE</command>.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -326,7 +247,7 @@ typedef struct TriggerData
|
|||||||
<term>TRIGGER_FIRED_BY_UPDATE(event)</term>
|
<term>TRIGGER_FIRED_BY_UPDATE(event)</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Returns TRUE if trigger fired by UPDATE.
|
Returns TRUE if trigger fired by <command>UPDATE</command>.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -356,11 +277,15 @@ typedef struct TriggerData
|
|||||||
<term><structfield>tg_trigtuple</></term>
|
<term><structfield>tg_trigtuple</></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
is a pointer to the tuple for which the trigger is fired. This is the tuple
|
is a pointer to the tuple for which the trigger is fired. This is
|
||||||
being inserted (if INSERT), deleted (if DELETE) or updated (if UPDATE).
|
the tuple being inserted (if <command>INSERT</command>), deleted
|
||||||
If INSERT/DELETE then this is what you are to return to Executor if
|
(if <command>DELETE</command>) or updated (if
|
||||||
you don't want to replace tuple with another one (INSERT) or skip the
|
<command>UPDATE</command>). If this trigger was fired for an
|
||||||
operation.
|
<command>INSERT</command> or <command>DELETE</command> 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
|
||||||
|
<command>INSERT</command>) or skip the operation (in the case of
|
||||||
|
<command>DELETE</command>).
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -369,9 +294,11 @@ typedef struct TriggerData
|
|||||||
<term><structfield>tg_newtuple</></term>
|
<term><structfield>tg_newtuple</></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
is a pointer to the new version of tuple if UPDATE and <symbol>NULL</> if this is
|
is a pointer to the new version of tuple if
|
||||||
for an INSERT or a DELETE. This is what you are to return to Executor if
|
<command>UPDATE</command> and <symbol>NULL</> if this is for an
|
||||||
UPDATE and you don't want to replace this tuple with another one or skip
|
<command>INSERT</command> or a <command>DELETE</command>. This is
|
||||||
|
what you are to return to Executor if <command>UPDATE</command>
|
||||||
|
and you don't want to replace this tuple with another one or skip
|
||||||
the operation.
|
the operation.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
@ -404,8 +331,9 @@ typedef struct Trigger
|
|||||||
where <structfield>tgname</> is the trigger's name,
|
where <structfield>tgname</> is the trigger's name,
|
||||||
<structfield>tgnargs</> is number of arguments in
|
<structfield>tgnargs</> is number of arguments in
|
||||||
<structfield>tgargs</>, <structfield>tgargs</> is an array of
|
<structfield>tgargs</>, <structfield>tgargs</> is an array of
|
||||||
pointers to the arguments specified in the CREATE TRIGGER
|
pointers to the arguments specified in the <command>CREATE
|
||||||
statement. Other members are for internal use only.
|
TRIGGER</command> statement. Other members are for internal use
|
||||||
|
only.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -460,10 +388,12 @@ execution of Q) or after Q is done.
|
|||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
Here is a very simple example of trigger usage. Function <function>trigf</> reports
|
Here is a very simple example of trigger usage. Function
|
||||||
the number of tuples in the triggered relation <literal>ttest</> and skips the
|
<function>trigf</> reports the number of tuples in the triggered
|
||||||
operation if the query attempts to insert a null value into x (i.e - it acts as a
|
relation <literal>ttest</> and skips the operation if the query
|
||||||
not-null constraint but doesn't abort the transaction).
|
attempts to insert a null value into x (i.e - it acts as a
|
||||||
|
<literal>NOT NULL</literal> constraint but doesn't abort the
|
||||||
|
transaction).
|
||||||
|
|
||||||
<programlisting>
|
<programlisting>
|
||||||
#include "executor/spi.h" /* this is what you need to work with SPI */
|
#include "executor/spi.h" /* this is what you need to work with SPI */
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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
|
* NOTES
|
||||||
* Transaction aborts can now occur two ways:
|
* 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
|
* CommitTransaction
|
||||||
* --------------------------------
|
* --------------------------------
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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)
|
if (!binary)
|
||||||
{
|
{
|
||||||
file_has_oids = oids; /* must rely on user to tell us this... */
|
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);
|
ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
|
||||||
|
|
||||||
/* AFTER ROW INSERT Triggers */
|
/* 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;
|
copy_lineno = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Execute AFTER STATEMENT insertion triggers
|
||||||
|
*/
|
||||||
|
ExecASInsertTriggers(estate, resultRelInfo);
|
||||||
|
|
||||||
MemoryContextSwitchTo(oldcontext);
|
MemoryContextSwitchTo(oldcontext);
|
||||||
|
|
||||||
pfree(values);
|
pfree(values);
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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[0] = 'i';
|
||||||
fk_trigger->actions[1] = 'u';
|
fk_trigger->actions[1] = 'u';
|
||||||
fk_trigger->actions[2] = '\0';
|
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->isconstraint = true;
|
||||||
fk_trigger->deferrable = fkconstraint->deferrable;
|
fk_trigger->deferrable = fkconstraint->deferrable;
|
||||||
fk_trigger->initdeferred = fkconstraint->initdeferred;
|
fk_trigger->initdeferred = fkconstraint->initdeferred;
|
||||||
@ -3374,11 +3370,7 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
|
|||||||
fk_trigger->row = true;
|
fk_trigger->row = true;
|
||||||
fk_trigger->actions[0] = 'd';
|
fk_trigger->actions[0] = 'd';
|
||||||
fk_trigger->actions[1] = '\0';
|
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->isconstraint = true;
|
||||||
fk_trigger->deferrable = fkconstraint->deferrable;
|
fk_trigger->deferrable = fkconstraint->deferrable;
|
||||||
fk_trigger->initdeferred = fkconstraint->initdeferred;
|
fk_trigger->initdeferred = fkconstraint->initdeferred;
|
||||||
@ -3445,11 +3437,6 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
|
|||||||
fk_trigger->row = true;
|
fk_trigger->row = true;
|
||||||
fk_trigger->actions[0] = 'u';
|
fk_trigger->actions[0] = 'u';
|
||||||
fk_trigger->actions[1] = '\0';
|
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->isconstraint = true;
|
||||||
fk_trigger->deferrable = fkconstraint->deferrable;
|
fk_trigger->deferrable = fkconstraint->deferrable;
|
||||||
fk_trigger->initdeferred = fkconstraint->initdeferred;
|
fk_trigger->initdeferred = fkconstraint->initdeferred;
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.139 2002/11/13 00:39:46 momjian Exp $
|
* $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.140 2002/11/23 03:59:07 momjian Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -47,7 +47,7 @@ static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata,
|
|||||||
FmgrInfo *finfo,
|
FmgrInfo *finfo,
|
||||||
MemoryContext per_tuple_context);
|
MemoryContext per_tuple_context);
|
||||||
static void DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
|
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,
|
static void DeferredTriggerExecute(DeferredTriggerEvent event, int itemno,
|
||||||
Relation rel, TriggerDesc *trigdesc, FmgrInfo *finfo,
|
Relation rel, TriggerDesc *trigdesc, FmgrInfo *finfo,
|
||||||
MemoryContext per_tuple_context);
|
MemoryContext per_tuple_context);
|
||||||
@ -147,12 +147,14 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
|
|||||||
{
|
{
|
||||||
/* foreign key constraint trigger */
|
/* 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)
|
if (aclresult != ACLCHECK_OK)
|
||||||
aclcheck_error(aclresult, RelationGetRelationName(rel));
|
aclcheck_error(aclresult, RelationGetRelationName(rel));
|
||||||
if (constrrelid != InvalidOid)
|
if (constrrelid != InvalidOid)
|
||||||
{
|
{
|
||||||
aclresult = pg_class_aclcheck(constrrelid, GetUserId(), ACL_REFERENCES);
|
aclresult = pg_class_aclcheck(constrrelid, GetUserId(),
|
||||||
|
ACL_REFERENCES);
|
||||||
if (aclresult != ACLCHECK_OK)
|
if (aclresult != ACLCHECK_OK)
|
||||||
aclcheck_error(aclresult, get_rel_name(constrrelid));
|
aclcheck_error(aclresult, get_rel_name(constrrelid));
|
||||||
}
|
}
|
||||||
@ -160,7 +162,8 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* real trigger */
|
/* real trigger */
|
||||||
aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(), ACL_TRIGGER);
|
aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
|
||||||
|
ACL_TRIGGER);
|
||||||
if (aclresult != ACLCHECK_OK)
|
if (aclresult != ACLCHECK_OK)
|
||||||
aclcheck_error(aclresult, RelationGetRelationName(rel));
|
aclcheck_error(aclresult, RelationGetRelationName(rel));
|
||||||
}
|
}
|
||||||
@ -195,10 +198,8 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
|
|||||||
TRIGGER_SETT_BEFORE(tgtype);
|
TRIGGER_SETT_BEFORE(tgtype);
|
||||||
if (stmt->row)
|
if (stmt->row)
|
||||||
TRIGGER_SETT_ROW(tgtype);
|
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])
|
switch (stmt->actions[i])
|
||||||
{
|
{
|
||||||
@ -1131,6 +1132,64 @@ ExecCallTriggerFunc(TriggerData *trigdata,
|
|||||||
return (HeapTuple) DatumGetPointer(result);
|
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
|
HeapTuple
|
||||||
ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
|
ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
|
||||||
HeapTuple trigtuple)
|
HeapTuple trigtuple)
|
||||||
@ -1149,7 +1208,9 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
|
|||||||
palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
|
palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
|
||||||
|
|
||||||
LocTriggerData.type = T_TriggerData;
|
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_relation = relinfo->ri_RelationDesc;
|
||||||
LocTriggerData.tg_newtuple = NULL;
|
LocTriggerData.tg_newtuple = NULL;
|
||||||
for (i = 0; i < ntrigs; i++)
|
for (i = 0; i < ntrigs; i++)
|
||||||
@ -1177,9 +1238,67 @@ ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
|
|||||||
{
|
{
|
||||||
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
|
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,
|
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
|
bool
|
||||||
@ -1205,7 +1324,9 @@ ExecBRDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
|
|||||||
palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
|
palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
|
||||||
|
|
||||||
LocTriggerData.type = T_TriggerData;
|
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_relation = relinfo->ri_RelationDesc;
|
||||||
LocTriggerData.tg_newtuple = NULL;
|
LocTriggerData.tg_newtuple = NULL;
|
||||||
for (i = 0; i < ntrigs; i++)
|
for (i = 0; i < ntrigs; i++)
|
||||||
@ -1235,17 +1356,75 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
|
|||||||
{
|
{
|
||||||
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
|
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,
|
HeapTuple trigtuple = GetTupleForTrigger(estate, relinfo,
|
||||||
tupleid, NULL);
|
tupleid, NULL);
|
||||||
|
|
||||||
DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
|
DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
|
||||||
trigtuple, NULL);
|
true, trigtuple, NULL);
|
||||||
heap_freetuple(trigtuple);
|
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
|
HeapTuple
|
||||||
ExecBRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
|
ExecBRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
|
||||||
ItemPointer tupleid, HeapTuple newtuple)
|
ItemPointer tupleid, HeapTuple newtuple)
|
||||||
@ -1265,8 +1444,8 @@ ExecBRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
|
|||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* In READ COMMITTED isolevel it's possible that newtuple was changed
|
* In READ COMMITTED isolation level it's possible that newtuple was
|
||||||
* due to concurrent update.
|
* changed due to concurrent update.
|
||||||
*/
|
*/
|
||||||
if (newSlot != NULL)
|
if (newSlot != NULL)
|
||||||
intuple = newtuple = ExecRemoveJunk(estate->es_junkFilter, newSlot);
|
intuple = newtuple = ExecRemoveJunk(estate->es_junkFilter, newSlot);
|
||||||
@ -1306,13 +1485,13 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
|
|||||||
{
|
{
|
||||||
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
|
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,
|
HeapTuple trigtuple = GetTupleForTrigger(estate, relinfo,
|
||||||
tupleid, NULL);
|
tupleid, NULL);
|
||||||
|
|
||||||
DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
|
DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
|
||||||
trigtuple, newtuple);
|
true, trigtuple, newtuple);
|
||||||
heap_freetuple(trigtuple);
|
heap_freetuple(trigtuple);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1344,7 +1523,7 @@ ltrmark:;
|
|||||||
case HeapTupleSelfUpdated:
|
case HeapTupleSelfUpdated:
|
||||||
/* treat it as deleted; do not process */
|
/* treat it as deleted; do not process */
|
||||||
ReleaseBuffer(buffer);
|
ReleaseBuffer(buffer);
|
||||||
return (NULL);
|
return NULL;
|
||||||
|
|
||||||
case HeapTupleMayBeUpdated:
|
case HeapTupleMayBeUpdated:
|
||||||
break;
|
break;
|
||||||
@ -1371,12 +1550,12 @@ ltrmark:;
|
|||||||
* if tuple was deleted or PlanQual failed for updated
|
* if tuple was deleted or PlanQual failed for updated
|
||||||
* tuple - we have not process this tuple!
|
* tuple - we have not process this tuple!
|
||||||
*/
|
*/
|
||||||
return (NULL);
|
return NULL;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
ReleaseBuffer(buffer);
|
ReleaseBuffer(buffer);
|
||||||
elog(ERROR, "Unknown status %u from heap_mark4update", test);
|
elog(ERROR, "Unknown status %u from heap_mark4update", test);
|
||||||
return (NULL);
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -1466,7 +1645,7 @@ deferredTriggerCheckState(Oid tgoid, int32 itemstate)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Not deferrable triggers (i.e. normal AFTER ROW triggers and
|
* 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)
|
if ((itemstate & TRIGGER_DEFERRED_DEFERRABLE) == 0)
|
||||||
return false;
|
return false;
|
||||||
@ -1590,7 +1769,7 @@ DeferredTriggerExecute(DeferredTriggerEvent event, int itemno,
|
|||||||
*/
|
*/
|
||||||
LocTriggerData.type = T_TriggerData;
|
LocTriggerData.type = T_TriggerData;
|
||||||
LocTriggerData.tg_event = (event->dte_event & TRIGGER_EVENT_OPMASK) |
|
LocTriggerData.tg_event = (event->dte_event & TRIGGER_EVENT_OPMASK) |
|
||||||
TRIGGER_EVENT_ROW;
|
(event->dte_event & TRIGGER_EVENT_ROW);
|
||||||
LocTriggerData.tg_relation = rel;
|
LocTriggerData.tg_relation = rel;
|
||||||
|
|
||||||
LocTriggerData.tg_trigger = NULL;
|
LocTriggerData.tg_trigger = NULL;
|
||||||
@ -2139,7 +2318,7 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
|
|||||||
* ----------
|
* ----------
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
|
DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
|
||||||
HeapTuple oldtup, HeapTuple newtup)
|
HeapTuple oldtup, HeapTuple newtup)
|
||||||
{
|
{
|
||||||
Relation rel = relinfo->ri_RelationDesc;
|
Relation rel = relinfo->ri_RelationDesc;
|
||||||
@ -2152,7 +2331,6 @@ DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
|
|||||||
int *tgindx;
|
int *tgindx;
|
||||||
ItemPointerData oldctid;
|
ItemPointerData oldctid;
|
||||||
ItemPointerData newctid;
|
ItemPointerData newctid;
|
||||||
TriggerData LocTriggerData;
|
|
||||||
|
|
||||||
if (deftrig_cxt == NULL)
|
if (deftrig_cxt == NULL)
|
||||||
elog(ERROR,
|
elog(ERROR,
|
||||||
@ -2175,14 +2353,25 @@ DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
|
|||||||
*/
|
*/
|
||||||
oldcxt = MemoryContextSwitchTo(deftrig_cxt);
|
oldcxt = MemoryContextSwitchTo(deftrig_cxt);
|
||||||
|
|
||||||
ntriggers = trigdesc->n_after_row[event];
|
if (row_trigger)
|
||||||
tgindx = trigdesc->tg_after_row[event];
|
{
|
||||||
|
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]) +
|
new_size = offsetof(DeferredTriggerEventData, dte_item[0]) +
|
||||||
ntriggers * sizeof(DeferredTriggerEventItem);
|
ntriggers * sizeof(DeferredTriggerEventItem);
|
||||||
|
|
||||||
new_event = (DeferredTriggerEvent) palloc(new_size);
|
new_event = (DeferredTriggerEvent) palloc(new_size);
|
||||||
new_event->dte_next = NULL;
|
new_event->dte_next = NULL;
|
||||||
new_event->dte_event = event & TRIGGER_EVENT_OPMASK;
|
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;
|
new_event->dte_relid = rel->rd_id;
|
||||||
ItemPointerCopy(&oldctid, &(new_event->dte_oldctid));
|
ItemPointerCopy(&oldctid, &(new_event->dte_oldctid));
|
||||||
ItemPointerCopy(&newctid, &(new_event->dte_newctid));
|
ItemPointerCopy(&newctid, &(new_event->dte_newctid));
|
||||||
@ -2190,15 +2379,21 @@ DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
|
|||||||
for (i = 0; i < ntriggers; i++)
|
for (i = 0; i < ntriggers; i++)
|
||||||
{
|
{
|
||||||
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
|
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
|
||||||
|
DeferredTriggerEventItem *ev_item = &(new_event->dte_item[i]);
|
||||||
|
|
||||||
new_event->dte_item[i].dti_tgoid = trigger->tgoid;
|
ev_item->dti_tgoid = trigger->tgoid;
|
||||||
new_event->dte_item[i].dti_state =
|
ev_item->dti_state =
|
||||||
((trigger->tgdeferrable) ?
|
((trigger->tgdeferrable) ?
|
||||||
TRIGGER_DEFERRED_DEFERRABLE : 0) |
|
TRIGGER_DEFERRED_DEFERRABLE : 0) |
|
||||||
((trigger->tginitdeferred) ?
|
((trigger->tginitdeferred) ?
|
||||||
TRIGGER_DEFERRED_INITDEFERRED : 0) |
|
TRIGGER_DEFERRED_INITDEFERRED : 0);
|
||||||
((trigdesc->n_before_row[event] > 0) ?
|
|
||||||
TRIGGER_DEFERRED_HAS_BEFORE : 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);
|
MemoryContextSwitchTo(oldcxt);
|
||||||
@ -2219,6 +2414,7 @@ DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
|
|||||||
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
|
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
|
||||||
bool is_ri_trigger;
|
bool is_ri_trigger;
|
||||||
bool key_unchanged;
|
bool key_unchanged;
|
||||||
|
TriggerData LocTriggerData;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We are interested in RI_FKEY triggers only.
|
* We are interested in RI_FKEY triggers only.
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
*
|
*
|
||||||
* These three procedures are the external interfaces to the executor.
|
* These three procedures are the external interfaces to the executor.
|
||||||
* In each case, the query descriptor and the execution state is required
|
* 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
|
* ExecutorStart() must be called at the beginning of any execution of any
|
||||||
* query plan and ExecutorEnd() should always be called at the end of
|
* query plan and ExecutorEnd() should always be called at the end of
|
||||||
@ -27,7 +27,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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,
|
ScanDirection direction,
|
||||||
DestReceiver *destfunc)
|
DestReceiver *destfunc)
|
||||||
{
|
{
|
||||||
JunkFilter *junkfilter;
|
JunkFilter *junkfilter;
|
||||||
TupleTableSlot *slot;
|
TupleTableSlot *slot;
|
||||||
ItemPointer tupleid = NULL;
|
ItemPointer tupleid = NULL;
|
||||||
ItemPointerData tuple_ctid;
|
ItemPointerData tuple_ctid;
|
||||||
long current_tuple_count;
|
long current_tuple_count;
|
||||||
TupleTableSlot *result;
|
TupleTableSlot *result;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* initialize local variables
|
* initialize local variables
|
||||||
@ -927,6 +927,24 @@ ExecutePlan(EState *estate,
|
|||||||
*/
|
*/
|
||||||
estate->es_direction = direction;
|
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
|
* Loop until we've processed the proper number of tuples from the
|
||||||
* plan.
|
* plan.
|
||||||
@ -1124,6 +1142,24 @@ lnext: ;
|
|||||||
break;
|
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
|
* here, result is either a slot containing a tuple in the case of a
|
||||||
* SELECT or NULL otherwise.
|
* SELECT or NULL otherwise.
|
||||||
@ -1205,7 +1241,7 @@ ExecInsert(TupleTableSlot *slot,
|
|||||||
|
|
||||||
/* BEFORE ROW INSERT Triggers */
|
/* BEFORE ROW INSERT Triggers */
|
||||||
if (resultRelInfo->ri_TrigDesc &&
|
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;
|
HeapTuple newtuple;
|
||||||
|
|
||||||
@ -1256,8 +1292,7 @@ ExecInsert(TupleTableSlot *slot,
|
|||||||
ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
|
ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
|
||||||
|
|
||||||
/* AFTER ROW INSERT Triggers */
|
/* 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 */
|
/* 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);
|
ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
|
||||||
|
|
||||||
/* AFTER ROW UPDATE Triggers */
|
/* AFTER ROW UPDATE Triggers */
|
||||||
if (resultRelInfo->ri_TrigDesc)
|
ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple);
|
||||||
ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static char *
|
static char *
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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->before = from->before;
|
||||||
newnode->row = from->row;
|
newnode->row = from->row;
|
||||||
memcpy(newnode->actions, from->actions, sizeof(from->actions));
|
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->isconstraint = from->isconstraint;
|
||||||
newnode->deferrable = from->deferrable;
|
newnode->deferrable = from->deferrable;
|
||||||
newnode->initdeferred = from->initdeferred;
|
newnode->initdeferred = from->initdeferred;
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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;
|
return false;
|
||||||
if (strcmp(a->actions, b->actions) != 0)
|
if (strcmp(a->actions, b->actions) != 0)
|
||||||
return false;
|
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)
|
if (a->isconstraint != b->isconstraint)
|
||||||
return false;
|
return false;
|
||||||
if (a->deferrable != b->deferrable)
|
if (a->deferrable != b->deferrable)
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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
|
* HISTORY
|
||||||
* AUTHOR DATE MAJOR EVENT
|
* AUTHOR DATE MAJOR EVENT
|
||||||
@ -1371,7 +1371,7 @@ opt_using:
|
|||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
*
|
*
|
||||||
* QUERY :
|
* QUERY :
|
||||||
* CREATE relname
|
* CREATE TABLE relname
|
||||||
*
|
*
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
@ -2028,11 +2028,6 @@ CreateTrigStmt:
|
|||||||
n->before = $4;
|
n->before = $4;
|
||||||
n->row = $8;
|
n->row = $8;
|
||||||
memcpy (n->actions, $5, 4);
|
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->isconstraint = FALSE;
|
||||||
n->deferrable = FALSE;
|
n->deferrable = FALSE;
|
||||||
n->initdeferred = FALSE;
|
n->initdeferred = FALSE;
|
||||||
@ -2053,11 +2048,6 @@ CreateTrigStmt:
|
|||||||
n->before = FALSE;
|
n->before = FALSE;
|
||||||
n->row = TRUE;
|
n->row = TRUE;
|
||||||
memcpy (n->actions, $6, 4);
|
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->isconstraint = TRUE;
|
||||||
n->deferrable = ($10 & 1) != 0;
|
n->deferrable = ($10 & 1) != 0;
|
||||||
n->initdeferred = ($10 & 2) != 0;
|
n->initdeferred = ($10 & 2) != 0;
|
||||||
@ -2075,17 +2065,17 @@ TriggerActionTime:
|
|||||||
TriggerEvents:
|
TriggerEvents:
|
||||||
TriggerOneEvent
|
TriggerOneEvent
|
||||||
{
|
{
|
||||||
char *e = palloc (4);
|
char *e = palloc(4);
|
||||||
e[0] = $1; e[1] = 0; $$ = e;
|
e[0] = $1; e[1] = 0; $$ = e;
|
||||||
}
|
}
|
||||||
| TriggerOneEvent OR TriggerOneEvent
|
| TriggerOneEvent OR TriggerOneEvent
|
||||||
{
|
{
|
||||||
char *e = palloc (4);
|
char *e = palloc(4);
|
||||||
e[0] = $1; e[1] = $3; e[2] = 0; $$ = e;
|
e[0] = $1; e[1] = $3; e[2] = 0; $$ = e;
|
||||||
}
|
}
|
||||||
| TriggerOneEvent OR TriggerOneEvent OR TriggerOneEvent
|
| 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[0] = $1; e[1] = $3; e[2] = $5; e[3] = 0;
|
||||||
$$ = e;
|
$$ = e;
|
||||||
}
|
}
|
||||||
@ -2102,6 +2092,14 @@ TriggerForSpec:
|
|||||||
{
|
{
|
||||||
$$ = $3;
|
$$ = $3;
|
||||||
}
|
}
|
||||||
|
| /* EMPTY */
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* If ROW/STATEMENT not specified, default to
|
||||||
|
* STATEMENT, per SQL
|
||||||
|
*/
|
||||||
|
$$ = FALSE;
|
||||||
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
TriggerForOpt:
|
TriggerForOpt:
|
||||||
@ -2124,7 +2122,7 @@ TriggerFuncArg:
|
|||||||
ICONST
|
ICONST
|
||||||
{
|
{
|
||||||
char buf[64];
|
char buf[64];
|
||||||
snprintf (buf, sizeof(buf), "%d", $1);
|
snprintf(buf, sizeof(buf), "%d", $1);
|
||||||
$$ = makeString(pstrdup(buf));
|
$$ = makeString(pstrdup(buf));
|
||||||
}
|
}
|
||||||
| FCONST { $$ = makeString($1); }
|
| FCONST { $$ = makeString($1); }
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/* ----------
|
/* ----------
|
||||||
* pg_lzcompress.c -
|
* 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.
|
* This is an implementation of LZ compression for PostgreSQL.
|
||||||
* It uses a simple history table and generates 2-3 byte tags
|
* It uses a simple history table and generates 2-3 byte tags
|
||||||
@ -87,7 +87,7 @@
|
|||||||
* OOOO LLLL OOOO OOOO
|
* OOOO LLLL OOOO OOOO
|
||||||
*
|
*
|
||||||
* This limits the offset to 1-4095 (12 bits) and the length
|
* 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
|
* 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.
|
* 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;
|
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, /* Chunks of any size are compressed */
|
||||||
0, /* */
|
0, /* */
|
||||||
0, /* We want to save at least one single
|
0, /* We want to save at least one single
|
||||||
@ -239,7 +239,7 @@ static PGLZ_Strategy strategy_allways_data = {
|
|||||||
* bytes is found */
|
* bytes is found */
|
||||||
6 /* Look harder for a good match. */
|
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 = {
|
static PGLZ_Strategy strategy_never_data = {
|
||||||
@ -247,7 +247,7 @@ static PGLZ_Strategy strategy_never_data = {
|
|||||||
0, /* */
|
0, /* */
|
||||||
0, /* */
|
0, /* */
|
||||||
0, /* Zero indicates "store uncompressed
|
0, /* Zero indicates "store uncompressed
|
||||||
* allways" */
|
* always" */
|
||||||
0 /* */
|
0 /* */
|
||||||
};
|
};
|
||||||
PGLZ_Strategy *PGLZ_strategy_never = &strategy_never_data;
|
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
|
* 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
|
* use memcpy() here, because the copied areas could
|
||||||
* overlap extremely!
|
* overlap extremely!
|
||||||
*/
|
*/
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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
|
* 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
|
* 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
|
* the file version #, and modify readInt to read the new format AS
|
||||||
* WELL AS the old formats.
|
* WELL AS the old formats.
|
||||||
|
@ -7,22 +7,12 @@
|
|||||||
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* pg_dump will read the system catalogs in a database and
|
* pg_dump will read the system catalogs in a database and dump out a
|
||||||
* dump out a script that reproduces
|
* script that reproduces the schema in terms of SQL that is understood
|
||||||
* the schema of the database in terms of
|
* by PostgreSQL
|
||||||
* user-defined types
|
|
||||||
* user-defined functions
|
|
||||||
* tables
|
|
||||||
* indexes
|
|
||||||
* aggregates
|
|
||||||
* operators
|
|
||||||
* privileges
|
|
||||||
*
|
|
||||||
* the output script is SQL that is understood by PostgreSQL
|
|
||||||
*
|
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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 */
|
/* In 7.3, result of regproc is already quoted */
|
||||||
if (g_fout->remoteVersion >= 70300)
|
if (g_fout->remoteVersion >= 70300)
|
||||||
appendPQExpBuffer(query, "EXECUTE PROCEDURE %s (",
|
appendPQExpBuffer(query, "EXECUTE PROCEDURE %s (",
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* 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 FreeTriggerDesc(TriggerDesc *trigdesc);
|
||||||
|
|
||||||
|
extern void ExecBSInsertTriggers(EState *estate,
|
||||||
|
ResultRelInfo *relinfo);
|
||||||
|
extern void ExecASInsertTriggers(EState *estate,
|
||||||
|
ResultRelInfo *relinfo);
|
||||||
extern HeapTuple ExecBRInsertTriggers(EState *estate,
|
extern HeapTuple ExecBRInsertTriggers(EState *estate,
|
||||||
ResultRelInfo *relinfo,
|
ResultRelInfo *relinfo,
|
||||||
HeapTuple trigtuple);
|
HeapTuple trigtuple);
|
||||||
extern void ExecARInsertTriggers(EState *estate,
|
extern void ExecARInsertTriggers(EState *estate,
|
||||||
ResultRelInfo *relinfo,
|
ResultRelInfo *relinfo,
|
||||||
HeapTuple trigtuple);
|
HeapTuple trigtuple);
|
||||||
|
extern void ExecBSDeleteTriggers(EState *estate,
|
||||||
|
ResultRelInfo *relinfo);
|
||||||
|
extern void ExecASDeleteTriggers(EState *estate,
|
||||||
|
ResultRelInfo *relinfo);
|
||||||
extern bool ExecBRDeleteTriggers(EState *estate,
|
extern bool ExecBRDeleteTriggers(EState *estate,
|
||||||
ResultRelInfo *relinfo,
|
ResultRelInfo *relinfo,
|
||||||
ItemPointer tupleid);
|
ItemPointer tupleid);
|
||||||
extern void ExecARDeleteTriggers(EState *estate,
|
extern void ExecARDeleteTriggers(EState *estate,
|
||||||
ResultRelInfo *relinfo,
|
ResultRelInfo *relinfo,
|
||||||
ItemPointer tupleid);
|
ItemPointer tupleid);
|
||||||
|
extern void ExecBSUpdateTriggers(EState *estate,
|
||||||
|
ResultRelInfo *relinfo);
|
||||||
|
extern void ExecASUpdateTriggers(EState *estate,
|
||||||
|
ResultRelInfo *relinfo);
|
||||||
extern HeapTuple ExecBRUpdateTriggers(EState *estate,
|
extern HeapTuple ExecBRUpdateTriggers(EState *estate,
|
||||||
ResultRelInfo *relinfo,
|
ResultRelInfo *relinfo,
|
||||||
ItemPointer tupleid,
|
ItemPointer tupleid,
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* 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 */
|
List *args; /* list of (T_String) Values or NIL */
|
||||||
bool before; /* BEFORE/AFTER */
|
bool before; /* BEFORE/AFTER */
|
||||||
bool row; /* ROW/STATEMENT */
|
bool row; /* ROW/STATEMENT */
|
||||||
char actions[4]; /* Insert, Update, Delete */
|
char actions[3]; /* 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 */
|
|
||||||
|
|
||||||
/* The following are used for referential */
|
/* The following are used for referential */
|
||||||
/* integrity constraint triggers */
|
/* integrity constraint triggers */
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/* ----------
|
/* ----------
|
||||||
* pg_lzcompress.h -
|
* 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
|
* 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
|
* match_size_good The initial GOOD match size when starting history
|
||||||
* lookup. When looking up the history to find a
|
* lookup. When looking up the history to find a
|
||||||
* match that could be expressed as a tag, the
|
* 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
|
* A good match fast is usually better than the
|
||||||
* best possible one very late. For each iteration
|
* best possible one very late. For each iteration
|
||||||
* in the lookup, this value is lowered so the
|
* in the lookup, this value is lowered so the
|
||||||
@ -147,7 +147,7 @@ typedef struct PGLZ_DecompState
|
|||||||
* This is the default strategy if none
|
* This is the default strategy if none
|
||||||
* is given to pglz_compress().
|
* 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
|
* small input and does fallback to
|
||||||
* uncompressed storage only if output
|
* uncompressed storage only if output
|
||||||
* would be larger than input.
|
* would be larger than input.
|
||||||
@ -158,7 +158,7 @@ typedef struct PGLZ_DecompState
|
|||||||
* ----------
|
* ----------
|
||||||
*/
|
*/
|
||||||
extern PGLZ_Strategy *PGLZ_strategy_default;
|
extern PGLZ_Strategy *PGLZ_strategy_default;
|
||||||
extern PGLZ_Strategy *PGLZ_strategy_allways;
|
extern PGLZ_Strategy *PGLZ_strategy_always;
|
||||||
extern PGLZ_Strategy *PGLZ_strategy_never;
|
extern PGLZ_Strategy *PGLZ_strategy_never;
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* 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
|
* trigger can appear in more than one class, for each class we
|
||||||
* provide a list of integer indexes into the triggers array.
|
* 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_statement[TRIGGER_NUM_EVENT_CLASSES];
|
||||||
uint16 n_before_row[TRIGGER_NUM_EVENT_CLASSES];
|
uint16 n_before_row[TRIGGER_NUM_EVENT_CLASSES];
|
||||||
|
@ -180,7 +180,7 @@ class pgdbCursor:
|
|||||||
def execute(self, operation, params = None):
|
def execute(self, operation, params = None):
|
||||||
# "The parameters may also be specified as list of
|
# "The parameters may also be specified as list of
|
||||||
# tuples to e.g. insert multiple rows in a single
|
# 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 \
|
if params and type(params) == types.ListType and \
|
||||||
type(params[0]) == types.TupleType:
|
type(params[0]) == types.TupleType:
|
||||||
self.executemany(operation, params)
|
self.executemany(operation, params)
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
* procedural language
|
* procedural language
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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.
|
* This software is copyrighted by Jan Wieck - Hamburg.
|
||||||
*
|
*
|
||||||
@ -430,9 +430,9 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
|
|||||||
PLpgSQL_function *save_efunc;
|
PLpgSQL_function *save_efunc;
|
||||||
PLpgSQL_stmt *save_estmt;
|
PLpgSQL_stmt *save_estmt;
|
||||||
char *save_etext;
|
char *save_etext;
|
||||||
PLpgSQL_rec *rec_new;
|
|
||||||
PLpgSQL_rec *rec_old;
|
|
||||||
PLpgSQL_var *var;
|
PLpgSQL_var *var;
|
||||||
|
PLpgSQL_rec *rec_new,
|
||||||
|
*rec_old;
|
||||||
HeapTuple rettup;
|
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
|
* Put the OLD and NEW tuples into record variables
|
||||||
* variable
|
|
||||||
*/
|
*/
|
||||||
rec_new = (PLpgSQL_rec *) (estate.datums[func->new_varno]);
|
rec_new = (PLpgSQL_rec *) (estate.datums[func->new_varno]);
|
||||||
rec_new->freetup = false;
|
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 = (PLpgSQL_rec *) (estate.datums[func->old_varno]);
|
||||||
rec_old->freetup = false;
|
rec_old->freetup = false;
|
||||||
rec_old->freetupdesc = 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->tup = trigdata->tg_trigtuple;
|
||||||
rec_new->tupdesc = trigdata->tg_relation->rd_att;
|
rec_new->tupdesc = trigdata->tg_relation->rd_att;
|
||||||
rec_old->tup = NULL;
|
rec_old->tup = NULL;
|
||||||
rec_old->tupdesc = NULL;
|
rec_old->tupdesc = NULL;
|
||||||
var->value = DirectFunctionCall1(textin, CStringGetDatum("INSERT"));
|
|
||||||
}
|
}
|
||||||
else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
|
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_new->tupdesc = trigdata->tg_relation->rd_att;
|
||||||
rec_old->tup = trigdata->tg_trigtuple;
|
rec_old->tup = trigdata->tg_trigtuple;
|
||||||
rec_old->tupdesc = trigdata->tg_relation->rd_att;
|
rec_old->tupdesc = trigdata->tg_relation->rd_att;
|
||||||
var->value = DirectFunctionCall1(textin, CStringGetDatum("UPDATE"));
|
|
||||||
}
|
}
|
||||||
else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
|
else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
|
||||||
{
|
{
|
||||||
@ -544,22 +550,27 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
|
|||||||
rec_new->tupdesc = NULL;
|
rec_new->tupdesc = NULL;
|
||||||
rec_old->tup = trigdata->tg_trigtuple;
|
rec_old->tup = trigdata->tg_trigtuple;
|
||||||
rec_old->tupdesc = trigdata->tg_relation->rd_att;
|
rec_old->tupdesc = trigdata->tg_relation->rd_att;
|
||||||
var->value = DirectFunctionCall1(textin, CStringGetDatum("DELETE"));
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
elog(ERROR, "Unknown trigger action: not INSERT, DELETE, or UPDATE");
|
||||||
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;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 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 = (PLpgSQL_var *) (estate.datums[func->tg_name_varno]);
|
||||||
var->isnull = false;
|
var->isnull = false;
|
||||||
var->freeval = true;
|
var->freeval = true;
|
||||||
@ -574,7 +585,7 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
|
|||||||
else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
|
else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
|
||||||
var->value = DirectFunctionCall1(textin, CStringGetDatum("AFTER"));
|
var->value = DirectFunctionCall1(textin, CStringGetDatum("AFTER"));
|
||||||
else
|
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 = (PLpgSQL_var *) (estate.datums[func->tg_level_varno]);
|
||||||
var->isnull = false;
|
var->isnull = false;
|
||||||
@ -584,7 +595,7 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
|
|||||||
else if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
|
else if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
|
||||||
var->value = DirectFunctionCall1(textin, CStringGetDatum("STATEMENT"));
|
var->value = DirectFunctionCall1(textin, CStringGetDatum("STATEMENT"));
|
||||||
else
|
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 = (PLpgSQL_var *) (estate.datums[func->tg_relid_varno]);
|
||||||
var->isnull = false;
|
var->isnull = false;
|
||||||
@ -671,13 +682,15 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Check that the returned tuple structure has the same attributes,
|
* 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
|
* 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
|
* 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
|
* 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;
|
rettup = NULL;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -91,7 +91,7 @@ DROP TABLE fkeys;
|
|||||||
DROP TABLE fkeys2;
|
DROP TABLE fkeys2;
|
||||||
-- -- I've disabled the funny_dup17 test because the new semantics
|
-- -- I've disabled the funny_dup17 test because the new semantics
|
||||||
-- -- of AFTER ROW triggers, which get now fired at the end of a
|
-- -- 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
|
-- -- Jan
|
||||||
--
|
--
|
||||||
@ -260,3 +260,55 @@ select * from tttest where price_on <= 35 and price_off > 35 and price_id = 5;
|
|||||||
|
|
||||||
drop table tttest;
|
drop table tttest;
|
||||||
drop sequence ttdummy_seq;
|
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
|
-- -- I've disabled the funny_dup17 test because the new semantics
|
||||||
-- -- of AFTER ROW triggers, which get now fired at the end of a
|
-- -- 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
|
-- -- Jan
|
||||||
--
|
--
|
||||||
@ -196,3 +196,55 @@ select * from tttest where price_on <= 35 and price_off > 35 and price_id = 5;
|
|||||||
|
|
||||||
drop table tttest;
|
drop table tttest;
|
||||||
drop sequence ttdummy_seq;
|
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;
|
Loading…
x
Reference in New Issue
Block a user