mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-09-28 17:31:47 +02:00
Revision
This commit is contained in:
parent
dcb0049523
commit
d8521b9b91
@ -1,30 +1,32 @@
|
||||
<!--
|
||||
$Header: /cvsroot/pgsql/doc/src/sgml/trigger.sgml,v 1.27 2003/03/25 16:15:38 petere Exp $
|
||||
$Header: /cvsroot/pgsql/doc/src/sgml/trigger.sgml,v 1.28 2003/04/11 18:41:20 petere Exp $
|
||||
-->
|
||||
|
||||
<chapter id="triggers">
|
||||
<title>Triggers</title>
|
||||
|
||||
<para>
|
||||
<productname>PostgreSQL</productname> has various server-side
|
||||
function interfaces. Server-side functions can be written in
|
||||
<acronym>SQL</acronym>, C, or any defined procedural
|
||||
language. Trigger functions can be written in C and most procedural
|
||||
languages, but not in <acronym>SQL</acronym>. Both per-row and
|
||||
per-statement triggers are supported. A trigger procedure can
|
||||
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.
|
||||
This chapter describes how to write trigger functions. In
|
||||
particular, it describes the C-language interface for trigger
|
||||
functions. The trigger interfaces in most procedural languages
|
||||
work analogously. (Trigger functions cannot be written in SQL.)
|
||||
</para>
|
||||
|
||||
<para>
|
||||
A trigger function can execute before or after a
|
||||
<command>INSERT</command>, <command>UPDATE</command>, or
|
||||
<command>DELETE</command>, either once per modified row, or once
|
||||
per <acronym>SQL</acronym> statement.
|
||||
</para>
|
||||
|
||||
<sect1 id="trigger-definition">
|
||||
<title>Trigger Definition</title>
|
||||
|
||||
<para>
|
||||
If a trigger event occurs, the trigger manager (called by the
|
||||
Executor) sets up a <structname>TriggerData</> information
|
||||
structure (described below) and calls the trigger function to
|
||||
handle the event.
|
||||
If a trigger event occurs, the trigger manager is called by the
|
||||
executor. It sets up an information structure of type
|
||||
<structname>TriggerData</> (described below) and calls the trigger
|
||||
function to handle the event.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
@ -42,15 +44,16 @@ $Header: /cvsroot/pgsql/doc/src/sgml/trigger.sgml,v 1.27 2003/03/25 16:15:38 pet
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Trigger functions return a <structname>HeapTuple</> to the calling
|
||||
executor. The return value is ignored for triggers fired AFTER an
|
||||
operation, but it allows BEFORE triggers to:
|
||||
Trigger functions return a value of type <structname>HeapTuple</>,
|
||||
which represents a table row, to the calling executor. The return
|
||||
value is ignored for triggers fired after an operation, but a
|
||||
triggers fired before an operation has the following choices:
|
||||
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
Return a <symbol>NULL</> pointer to skip the operation for the
|
||||
current tuple (and so the tuple will not be
|
||||
It can return a <symbol>NULL</> pointer to skip the operation
|
||||
for the current row (and so the row will not be
|
||||
inserted/updated/deleted).
|
||||
</para>
|
||||
</listitem>
|
||||
@ -58,60 +61,54 @@ $Header: /cvsroot/pgsql/doc/src/sgml/trigger.sgml,v 1.27 2003/03/25 16:15:38 pet
|
||||
<listitem>
|
||||
<para>
|
||||
For <command>INSERT</command> and <command>UPDATE</command>
|
||||
triggers only, the returned tuple becomes the tuple which will
|
||||
be inserted or will replace the tuple being updated. This
|
||||
triggers only, the returned row becomes the row that will
|
||||
be inserted or will replace the row being updated. This
|
||||
allows the trigger function to modify the row being inserted or
|
||||
updated.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
|
||||
A BEFORE trigger that does not intend to cause either of these behaviors
|
||||
must be careful to return the same NEW tuple it is passed.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Note that there is no initialization performed by the
|
||||
<command>CREATE TRIGGER</command> handler. This may be changed in
|
||||
the future.
|
||||
A before trigger that does not intend to cause either of these
|
||||
behaviors must be careful to return the same row that was passed
|
||||
in as the new row (see below).
|
||||
</para>
|
||||
|
||||
<para>
|
||||
If more than one trigger is defined for the same event on the same
|
||||
relation, the triggers will be fired in alphabetical order by
|
||||
name. In the case of BEFORE triggers, the possibly-modified tuple
|
||||
name. In the case of before triggers, the possibly-modified row
|
||||
returned by each trigger becomes the input to the next trigger.
|
||||
If any BEFORE trigger returns <symbol>NULL</>, the operation is
|
||||
abandoned and subsequent triggers are not fired.
|
||||
If any before trigger returns a <symbol>NULL</> pointer, the
|
||||
operation is abandoned and subsequent triggers are not fired.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
If a trigger function executes SQL-queries (using SPI) then these
|
||||
queries may fire triggers again. This is known as cascading
|
||||
If a trigger function executes SQL commands (using SPI) then these
|
||||
commands may fire triggers again. This is known as cascading
|
||||
triggers. There is no direct limitation on the number of cascade
|
||||
levels. It is possible for cascades to cause recursive invocation
|
||||
of the same trigger --- for example, an <command>INSERT</command>
|
||||
trigger might execute a query that inserts an additional tuple
|
||||
levels. It is possible for cascades to cause a recursive invocation
|
||||
of the same trigger; for example, an <command>INSERT</command>
|
||||
trigger might execute a command that inserts an additional row
|
||||
into the same table, causing the <command>INSERT</command> trigger
|
||||
to be fired again. It is the trigger programmer's responsibility
|
||||
to avoid infinite recursion in such scenarios.
|
||||
</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.
|
||||
When a trigger is being defined, arguments can be specified for
|
||||
it. 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 column 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 columns, 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>
|
||||
@ -122,26 +119,20 @@ $Header: /cvsroot/pgsql/doc/src/sgml/trigger.sgml,v 1.27 2003/03/25 16:15:38 pet
|
||||
<para>
|
||||
This section describes the low-level details of the interface to a
|
||||
trigger function. This information is only needed when writing a
|
||||
trigger function in C. If you are using a higher-level function
|
||||
trigger function in C. If you are using a higher-level
|
||||
language then these details are handled for you.
|
||||
</para>
|
||||
|
||||
<note>
|
||||
<para>
|
||||
The interface described here applies for
|
||||
<productname>PostgreSQL</productname> 7.1 and later.
|
||||
Earlier versions passed the <structname>TriggerData</> pointer in a global
|
||||
variable <varname>CurrentTriggerData</>.
|
||||
</para>
|
||||
</note>
|
||||
|
||||
<para>
|
||||
When a function is called by the trigger manager, it is not passed
|
||||
any normal parameters, but it is passed a <quote>context</>
|
||||
any normal arguments, but it is passed a <quote>context</>
|
||||
pointer pointing to a <structname>TriggerData</> structure. C
|
||||
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
|
||||
<programlisting>
|
||||
CALLED_AS_TRIGGER(fcinfo)
|
||||
</programlisting>
|
||||
which expands to
|
||||
<programlisting>
|
||||
((fcinfo)->context != NULL && IsA((fcinfo)->context, TriggerData))
|
||||
</programlisting>
|
||||
@ -176,7 +167,7 @@ typedef struct TriggerData
|
||||
<term><structfield>type</></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Always <literal>T_TriggerData</literal> if this is a trigger event.
|
||||
Always <literal>T_TriggerData</literal>.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
@ -185,69 +176,69 @@ typedef struct TriggerData
|
||||
<term><structfield>tg_event</></term>
|
||||
<listitem>
|
||||
<para>
|
||||
describes the event for which the function is called. You may use the
|
||||
Describes the event for which the function is called. You may use the
|
||||
following macros to examine <literal>tg_event</literal>:
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term>TRIGGER_FIRED_BEFORE(tg_event)</term>
|
||||
<term><literal>TRIGGER_FIRED_BEFORE(tg_event)</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
returns TRUE if trigger fired BEFORE.
|
||||
Returns true if the trigger fired before the operation.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term>TRIGGER_FIRED_AFTER(tg_event)</term>
|
||||
<term><literal>TRIGGER_FIRED_AFTER(tg_event)</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Returns TRUE if trigger fired AFTER.
|
||||
Returns true if the trigger fired after the operation.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term>TRIGGER_FIRED_FOR_ROW(event)</term>
|
||||
<term><literal>TRIGGER_FIRED_FOR_ROW(tg_event)</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Returns TRUE if trigger fired for a ROW-level event.
|
||||
Returns true if the trigger fired for a row-level event.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term>TRIGGER_FIRED_FOR_STATEMENT(event)</term>
|
||||
<term><literal>TRIGGER_FIRED_FOR_STATEMENT(tg_event)</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Returns TRUE if trigger fired for STATEMENT-level event.
|
||||
Returns true if the trigger fired for a statement-level event.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term>TRIGGER_FIRED_BY_INSERT(event)</term>
|
||||
<term><literal>TRIGGER_FIRED_BY_INSERT(tg_event)</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Returns TRUE if trigger fired by <command>INSERT</command>.
|
||||
Returns true if the trigger was fired by an <command>INSERT</command> command.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term>TRIGGER_FIRED_BY_DELETE(event)</term>
|
||||
<term><literal>TRIGGER_FIRED_BY_UPDATE(tg_event)</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Returns TRUE if trigger fired by <command>DELETE</command>.
|
||||
Returns true if the trigger was fired by an <command>UPDATE</command> command.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term>TRIGGER_FIRED_BY_UPDATE(event)</term>
|
||||
<term><literal>TRIGGER_FIRED_BY_DELETE(tg_event)</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Returns TRUE if trigger fired by <command>UPDATE</command>.
|
||||
Returns true if the trigger was fired by a <command>DELETE</command> command.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
@ -260,14 +251,14 @@ typedef struct TriggerData
|
||||
<term><structfield>tg_relation</></term>
|
||||
<listitem>
|
||||
<para>
|
||||
is a pointer to structure describing the triggered
|
||||
relation. Look at <filename>utils/rel.h</> for details about
|
||||
A pointer to a structure describing the relation that the trigger fired for.
|
||||
Look at <filename>utils/rel.h</> for details about
|
||||
this structure. The most interesting things are
|
||||
<literal>tg_relation->rd_att</> (descriptor of the relation
|
||||
tuples) and <literal>tg_relation->rd_rel->relname</>
|
||||
(relation's name. This is not <type>char*</>, but
|
||||
<type>NameData</>. Use
|
||||
<literal>SPI_getrelname(tg_relation)</> to get <type>char*</> if you
|
||||
(relation name; the type is not <type>char*</> but
|
||||
<type>NameData</>; use
|
||||
<literal>SPI_getrelname(tg_relation)</> to get a <type>char*</> if you
|
||||
need a copy of the name).
|
||||
</para>
|
||||
</listitem>
|
||||
@ -277,15 +268,13 @@ typedef struct TriggerData
|
||||
<term><structfield>tg_trigtuple</></term>
|
||||
<listitem>
|
||||
<para>
|
||||
is a pointer to the tuple for which the trigger is fired. This is
|
||||
the tuple being inserted (if <command>INSERT</command>), deleted
|
||||
(if <command>DELETE</command>) or updated (if
|
||||
<command>UPDATE</command>). If this trigger was fired for an
|
||||
<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>).
|
||||
A pointer to the row for which the trigger was fired. This is
|
||||
the row being inserted, updated, or deleted. If this trigger
|
||||
was fired for an <command>INSERT</command> or
|
||||
<command>DELETE</command> then this is what you should return
|
||||
to from the function if you don't want to replace the row with
|
||||
a different one (in the case of <command>INSERT</command>) or
|
||||
skip the operation.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
@ -294,12 +283,13 @@ typedef struct TriggerData
|
||||
<term><structfield>tg_newtuple</></term>
|
||||
<listitem>
|
||||
<para>
|
||||
is a pointer to the new version of tuple if
|
||||
<command>UPDATE</command> and <symbol>NULL</> if this is for an
|
||||
<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.
|
||||
A pointer to the new version of the row, if the trigger was
|
||||
fired for an <command>UPDATE</command>, and <symbol>NULL</> if
|
||||
it is for an <command>INSERT</command> or a
|
||||
<command>DELETE</command>. This is what you have to return
|
||||
from the function if the event is an <command>UPDATE</command>
|
||||
and you don't want to replace this row by a different one or
|
||||
skip the operation.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
@ -308,7 +298,8 @@ typedef struct TriggerData
|
||||
<term><structfield>tg_trigger</></term>
|
||||
<listitem>
|
||||
<para>
|
||||
is pointer to structure <structname>Trigger</> defined in <filename>utils/rel.h</>:
|
||||
A pointer to a structure of type <structname>Trigger</>,
|
||||
defined in <filename>utils/rel.h</>:
|
||||
|
||||
<programlisting>
|
||||
typedef struct Trigger
|
||||
@ -330,9 +321,9 @@ typedef struct Trigger
|
||||
|
||||
where <structfield>tgname</> is the trigger's name,
|
||||
<structfield>tgnargs</> is number of arguments in
|
||||
<structfield>tgargs</>, <structfield>tgargs</> is an array of
|
||||
<structfield>tgargs</>, and <structfield>tgargs</> is an array of
|
||||
pointers to the arguments specified in the <command>CREATE
|
||||
TRIGGER</command> statement. Other members are for internal use
|
||||
TRIGGER</command> statement. The other members are for internal use
|
||||
only.
|
||||
</para>
|
||||
</listitem>
|
||||
@ -345,59 +336,73 @@ typedef struct Trigger
|
||||
<title>Visibility of Data Changes</title>
|
||||
|
||||
<para>
|
||||
<productname>PostgreSQL</productname> data changes visibility rule: during a query execution, data
|
||||
changes made by the query itself (via SQL-function, SPI-function, triggers)
|
||||
are invisible to the query scan. For example, in query
|
||||
If you are using the SPI interface to execute SQL commands in your
|
||||
trigger functions written in C (or you are using a different
|
||||
language and execute SQL commands in some way, which internally
|
||||
goes through SPI as well), be sure to read <xref
|
||||
linkend="spi-visibility"> so that you know which data is visible
|
||||
at which point during the execution of a trigger. For triggers,
|
||||
the most important consequences of the data visibility rules are:
|
||||
|
||||
<programlisting>
|
||||
INSERT INTO a SELECT * FROM a;
|
||||
</programlisting>
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
The row being inserted (<structfield>tg_trigtuple</>) is
|
||||
<emphasis>not</emphasis> visible to SQL commands executed in a
|
||||
before trigger.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
tuples inserted are invisible for SELECT scan. In effect, this
|
||||
duplicates the database table within itself (subject to unique index
|
||||
rules, of course) without recursing.
|
||||
<listitem>
|
||||
<para>
|
||||
The row being inserted (<structfield>tg_trigtuple</>)
|
||||
<emphasis>is</emphasis> visible to SQL commands executed in an
|
||||
after trigger (because it was just inserted).
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
A just-inserted row is visible to all SQL commands executed
|
||||
within any trigger that is fired later in the execution of the
|
||||
outer command (e.g., for the next row).
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
But keep in mind this notice about visibility in the SPI documentation:
|
||||
|
||||
<blockquote>
|
||||
<para>
|
||||
Changes made by query Q are visible by queries that are started after
|
||||
query Q, no matter whether they are started inside Q (during the
|
||||
execution of Q) or after Q is done.
|
||||
</para>
|
||||
</blockquote>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
This is true for triggers as well so, though a tuple being inserted
|
||||
(<structfield>tg_trigtuple</>) is not visible to queries in a BEFORE trigger, this tuple
|
||||
(just inserted) is visible to queries in an AFTER trigger, and to queries
|
||||
in BEFORE/AFTER triggers fired after this!
|
||||
The next section contains a demonstration of these rules applied.
|
||||
</para>
|
||||
</sect1>
|
||||
|
||||
<sect1 id="trigger-examples">
|
||||
<title>Examples</title>
|
||||
<sect1 id="trigger-example">
|
||||
<title>A Complete Example</title>
|
||||
|
||||
<para>
|
||||
There are more complex examples in
|
||||
<filename>src/test/regress/regress.c</filename> and
|
||||
in <filename>contrib/spi</filename>.
|
||||
Here is a very simple example of a trigger function written in C.
|
||||
The function <function>trigf</> reports the number of rows in the
|
||||
table <literal>ttest</> and skips the actual operation if the
|
||||
command attempts to insert a null value into the column
|
||||
<literal>x</>. (So the trigger acts as a not-null constraint but
|
||||
doesn't abort the transaction.)
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Here is a very simple example of trigger usage. Function
|
||||
<function>trigf</> reports the number of tuples in the triggered
|
||||
relation <literal>ttest</> and skips the operation if the query
|
||||
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).
|
||||
|
||||
First, the table definition:
|
||||
<programlisting>
|
||||
CREATE TABLE ttest (
|
||||
x integer
|
||||
);
|
||||
</programlisting>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
This is the source code of the trigger function:
|
||||
<programlisting>
|
||||
#include "postgres.h"
|
||||
#include "executor/spi.h" /* this is what you need to work with SPI */
|
||||
#include "commands/trigger.h" /* -"- and triggers */
|
||||
#include "commands/trigger.h" /* ... and triggers */
|
||||
|
||||
extern Datum trigf(PG_FUNCTION_ARGS);
|
||||
|
||||
@ -414,11 +419,11 @@ trigf(PG_FUNCTION_ARGS)
|
||||
bool isnull;
|
||||
int ret, i;
|
||||
|
||||
/* Make sure trigdata is pointing at what I expect */
|
||||
/* make sure it's called as a trigger at all */
|
||||
if (!CALLED_AS_TRIGGER(fcinfo))
|
||||
elog(ERROR, "trigf: not fired by trigger manager");
|
||||
elog(ERROR, "trigf: not called by trigger manager");
|
||||
|
||||
/* tuple to return to Executor */
|
||||
/* tuple to return to executor */
|
||||
if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
|
||||
rettuple = trigdata->tg_newtuple;
|
||||
else
|
||||
@ -436,29 +441,29 @@ trigf(PG_FUNCTION_ARGS)
|
||||
|
||||
tupdesc = trigdata->tg_relation->rd_att;
|
||||
|
||||
/* Connect to SPI manager */
|
||||
/* connect to SPI manager */
|
||||
if ((ret = SPI_connect()) < 0)
|
||||
elog(INFO, "trigf (fired %s): SPI_connect returned %d", when, ret);
|
||||
|
||||
/* Get number of tuples in relation */
|
||||
/* get number of rows in table */
|
||||
ret = SPI_exec("SELECT count(*) FROM ttest", 0);
|
||||
|
||||
if (ret < 0)
|
||||
elog(NOTICE, "trigf (fired %s): SPI_exec returned %d", when, ret);
|
||||
|
||||
/* count(*) returns int8 as of PG 7.2, so be careful to convert */
|
||||
i = (int) DatumGetInt64(SPI_getbinval(SPI_tuptable->vals[0],
|
||||
/* count(*) returns int8, so be careful to convert */
|
||||
i = DatumGetInt64(SPI_getbinval(SPI_tuptable->vals[0],
|
||||
SPI_tuptable->tupdesc,
|
||||
1,
|
||||
&isnull));
|
||||
|
||||
elog (NOTICE, "trigf (fired %s): there are %d tuples in ttest", when, i);
|
||||
elog (INFO, "trigf (fired %s): there are %d rows in ttest", when, i);
|
||||
|
||||
SPI_finish();
|
||||
|
||||
if (checknull)
|
||||
{
|
||||
(void) SPI_getbinval(rettuple, tupdesc, 1, &isnull);
|
||||
SPI_getbinval(rettuple, tupdesc, 1, &isnull);
|
||||
if (isnull)
|
||||
rettuple = NULL;
|
||||
}
|
||||
@ -469,36 +474,38 @@ trigf(PG_FUNCTION_ARGS)
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Now, compile and create the trigger function:
|
||||
|
||||
After you have compiled the source code, declare the function and
|
||||
the triggers:
|
||||
<programlisting>
|
||||
CREATE FUNCTION trigf () RETURNS TRIGGER AS
|
||||
'...path_to_so' LANGUAGE C;
|
||||
CREATE FUNCTION trigf() RETURNS trigger
|
||||
AS '<replaceable>filename</>'
|
||||
LANGUAGE C;
|
||||
|
||||
CREATE TABLE ttest (x int4);
|
||||
CREATE TRIGGER tbefore BEFORE INSERT OR UPDATE OR DELETE ON ttest
|
||||
FOR EACH ROW EXECUTE PROCEDURE trigf();
|
||||
|
||||
CREATE TRIGGER tafter AFTER INSERT OR UPDATE OR DELETE ON ttest
|
||||
FOR EACH ROW EXECUTE PROCEDURE trigf();
|
||||
</programlisting>
|
||||
</para>
|
||||
|
||||
<programlisting>
|
||||
vac=> CREATE TRIGGER tbefore BEFORE INSERT OR UPDATE OR DELETE ON ttest
|
||||
FOR EACH ROW EXECUTE PROCEDURE trigf();
|
||||
CREATE
|
||||
vac=> CREATE TRIGGER tafter AFTER INSERT OR UPDATE OR DELETE ON ttest
|
||||
FOR EACH ROW EXECUTE PROCEDURE trigf();
|
||||
CREATE
|
||||
vac=> INSERT INTO ttest VALUES (NULL);
|
||||
WARNING: trigf (fired before): there are 0 tuples in ttest
|
||||
<para>
|
||||
Now you can test the operation of the trigger:
|
||||
<screen>
|
||||
=> INSERT INTO ttest VALUES (NULL);
|
||||
INFO: trigf (fired before): there are 0 rows in ttest
|
||||
INSERT 0 0
|
||||
|
||||
-- Insertion skipped and AFTER trigger is not fired
|
||||
|
||||
vac=> SELECT * FROM ttest;
|
||||
=> SELECT * FROM ttest;
|
||||
x
|
||||
---
|
||||
(0 rows)
|
||||
|
||||
vac=> INSERT INTO ttest VALUES (1);
|
||||
INFO: trigf (fired before): there are 0 tuples in ttest
|
||||
INFO: trigf (fired after ): there are 1 tuples in ttest
|
||||
=> INSERT INTO ttest VALUES (1);
|
||||
INFO: trigf (fired before): there are 0 rows in ttest
|
||||
INFO: trigf (fired after ): there are 1 rows in ttest
|
||||
^^^^^^^^
|
||||
remember what we said about visibility.
|
||||
INSERT 167793 1
|
||||
@ -508,25 +515,25 @@ vac=> SELECT * FROM ttest;
|
||||
1
|
||||
(1 row)
|
||||
|
||||
vac=> INSERT INTO ttest SELECT x * 2 FROM ttest;
|
||||
INFO: trigf (fired before): there are 1 tuples in ttest
|
||||
INFO: trigf (fired after ): there are 2 tuples in ttest
|
||||
^^^^^^^^
|
||||
=> INSERT INTO ttest SELECT x * 2 FROM ttest;
|
||||
INFO: trigf (fired before): there are 1 rows in ttest
|
||||
INFO: trigf (fired after ): there are 2 rows in ttest
|
||||
^^^^^^
|
||||
remember what we said about visibility.
|
||||
INSERT 167794 1
|
||||
vac=> SELECT * FROM ttest;
|
||||
=> SELECT * FROM ttest;
|
||||
x
|
||||
---
|
||||
1
|
||||
2
|
||||
(2 rows)
|
||||
|
||||
vac=> UPDATE ttest SET x = NULL WHERE x = 2;
|
||||
INFO: trigf (fired before): there are 2 tuples in ttest
|
||||
=> UPDATE ttest SET x = NULL WHERE x = 2;
|
||||
INFO: trigf (fired before): there are 2 rows in ttest
|
||||
UPDATE 0
|
||||
vac=> UPDATE ttest SET x = 4 WHERE x = 2;
|
||||
INFO: trigf (fired before): there are 2 tuples in ttest
|
||||
INFO: trigf (fired after ): there are 2 tuples in ttest
|
||||
=> UPDATE ttest SET x = 4 WHERE x = 2;
|
||||
INFO: trigf (fired before): there are 2 rows in ttest
|
||||
INFO: trigf (fired after ): there are 2 rows in ttest
|
||||
UPDATE 1
|
||||
vac=> SELECT * FROM ttest;
|
||||
x
|
||||
@ -535,21 +542,27 @@ vac=> SELECT * FROM ttest;
|
||||
4
|
||||
(2 rows)
|
||||
|
||||
vac=> DELETE FROM ttest;
|
||||
INFO: trigf (fired before): there are 2 tuples in ttest
|
||||
INFO: trigf (fired after ): there are 1 tuples in ttest
|
||||
INFO: trigf (fired before): there are 1 tuples in ttest
|
||||
INFO: trigf (fired after ): there are 0 tuples in ttest
|
||||
^^^^^^^^
|
||||
=> DELETE FROM ttest;
|
||||
INFO: trigf (fired before): there are 2 rows in ttest
|
||||
INFO: trigf (fired after ): there are 1 rows in ttest
|
||||
INFO: trigf (fired before): there are 1 rows in ttest
|
||||
INFO: trigf (fired after ): there are 0 rows in ttest
|
||||
^^^^^^
|
||||
remember what we said about visibility.
|
||||
DELETE 2
|
||||
vac=> SELECT * FROM ttest;
|
||||
=> SELECT * FROM ttest;
|
||||
x
|
||||
---
|
||||
(0 rows)
|
||||
</programlisting>
|
||||
</screen>
|
||||
|
||||
</para>
|
||||
|
||||
<para>
|
||||
There are more complex examples in
|
||||
<filename>src/test/regress/regress.c</filename> and
|
||||
in <filename>contrib/spi</filename>.
|
||||
</para>
|
||||
</sect1>
|
||||
</chapter>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user