This commit is contained in:
Peter Eisentraut 2003-04-11 18:41:20 +00:00
parent dcb0049523
commit d8521b9b91

View File

@ -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"> <chapter id="triggers">
<title>Triggers</title> <title>Triggers</title>
<para> <para>
<productname>PostgreSQL</productname> has various server-side This chapter describes how to write trigger functions. In
function interfaces. Server-side functions can be written in particular, it describes the C-language interface for trigger
<acronym>SQL</acronym>, C, or any defined procedural functions. The trigger interfaces in most procedural languages
language. Trigger functions can be written in C and most procedural work analogously. (Trigger functions cannot be written in SQL.)
languages, but not in <acronym>SQL</acronym>. Both per-row and </para>
per-statement triggers are supported. A trigger procedure can
execute BEFORE or AFTER a <command>INSERT</command>, <para>
<command>DELETE</command> or <command>UPDATE</command>, either once A trigger function can execute before or after a
per modified row, or once per <acronym>SQL</acronym> statement. <command>INSERT</command>, <command>UPDATE</command>, or
<command>DELETE</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 If a trigger event occurs, the trigger manager is called by the
Executor) sets up a <structname>TriggerData</> information executor. It sets up an information structure of type
structure (described below) and calls the trigger function to <structname>TriggerData</> (described below) and calls the trigger
handle the event. function to handle the event.
</para> </para>
<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>
<para> <para>
Trigger functions return a <structname>HeapTuple</> to the calling Trigger functions return a value of type <structname>HeapTuple</>,
executor. The return value is ignored for triggers fired AFTER an which represents a table row, to the calling executor. The return
operation, but it allows BEFORE triggers to: value is ignored for triggers fired after an operation, but a
triggers fired before an operation has the following choices:
<itemizedlist> <itemizedlist>
<listitem> <listitem>
<para> <para>
Return a <symbol>NULL</> pointer to skip the operation for the It can return a <symbol>NULL</> pointer to skip the operation
current tuple (and so the tuple will not be for the current row (and so the row will not be
inserted/updated/deleted). inserted/updated/deleted).
</para> </para>
</listitem> </listitem>
@ -58,60 +61,54 @@ $Header: /cvsroot/pgsql/doc/src/sgml/trigger.sgml,v 1.27 2003/03/25 16:15:38 pet
<listitem> <listitem>
<para> <para>
For <command>INSERT</command> and <command>UPDATE</command> For <command>INSERT</command> and <command>UPDATE</command>
triggers only, the returned tuple becomes the tuple which will triggers only, the returned row becomes the row that will
be inserted or will replace the tuple being updated. This be inserted or will replace the row being updated. This
allows the trigger function to modify the row being inserted or allows the trigger function to modify the row being inserted or
updated. updated.
</para> </para>
</listitem> </listitem>
</itemizedlist> </itemizedlist>
A BEFORE trigger that does not intend to cause either of these behaviors A before trigger that does not intend to cause either of these
must be careful to return the same NEW tuple it is passed. behaviors must be careful to return the same row that was passed
</para> in as the new row (see below).
<para>
Note that there is no initialization performed by the
<command>CREATE TRIGGER</command> handler. This may be changed in
the future.
</para> </para>
<para> <para>
If more than one trigger is defined for the same event on the same If more than one trigger is defined for the same event on the same
relation, the triggers will be fired in alphabetical order by 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. returned by each trigger becomes the input to the next trigger.
If any BEFORE trigger returns <symbol>NULL</>, the operation is If any before trigger returns a <symbol>NULL</> pointer, the
abandoned and subsequent triggers are not fired. operation is abandoned and subsequent triggers are not fired.
</para> </para>
<para> <para>
If a trigger function executes SQL-queries (using SPI) then these If a trigger function executes SQL commands (using SPI) then these
queries may fire triggers again. This is known as cascading commands may fire triggers again. This is known as cascading
triggers. There is no direct limitation on the number of cascade triggers. There is no direct limitation on the number of cascade
levels. It is possible for cascades to cause recursive invocation levels. It is possible for cascades to cause a recursive invocation
of the same trigger --- for example, an <command>INSERT</command> of the same trigger; for example, an <command>INSERT</command>
trigger might execute a query that inserts an additional tuple trigger might execute a command that inserts an additional row
into the same table, causing the <command>INSERT</command> trigger into the same table, causing the <command>INSERT</command> trigger
to be fired again. It is the trigger programmer's responsibility to be fired again. It is the trigger programmer's responsibility
to avoid infinite recursion in such scenarios. to avoid infinite recursion in such scenarios.
</para> </para>
<para> <para>
When a trigger is defined, a number of arguments can be When a trigger is being defined, arguments can be specified for
specified. The purpose of including arguments in the trigger it. The purpose of including arguments in the trigger definition
definition is to allow different triggers with similar is to allow different triggers with similar requirements to call
requirements to call the same function. As an example, there the same function. As an example, there could be a generalized
could be a generalized trigger function that takes as its trigger function that takes as its arguments two column names and
arguments two field names and puts the current user in one and the puts the current user in one and the current time stamp in the
current time stamp in the other. Properly written, this trigger other. Properly written, this trigger function would be
function would be independent of the specific table it is independent of the specific table it is triggering on. So the
triggering on. So the same function could be used for same function could be used for <command>INSERT</command> events
<command>INSERT</command> events on any table with suitable on any table with suitable columns, to automatically track creation
fields, to automatically track creation of records in a of records in a transaction table for example. It could also be
transaction table for example. It could also be used to track used to track last-update events if defined as an
last-update events if defined as an <command>UPDATE</command> <command>UPDATE</command> trigger.
trigger.
</para> </para>
</sect1> </sect1>
@ -122,26 +119,20 @@ $Header: /cvsroot/pgsql/doc/src/sgml/trigger.sgml,v 1.27 2003/03/25 16:15:38 pet
<para> <para>
This section describes the low-level details of the interface to a This section describes the low-level details of the interface to a
trigger function. This information is only needed when writing 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. language then these details are handled for you.
</para> </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> <para>
When a function is called by the trigger manager, it is not passed 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 pointer pointing to a <structname>TriggerData</> structure. C
functions can check whether they were called from the trigger functions can check whether they were called from the trigger
manager or not by executing the macro 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> <programlisting>
((fcinfo)->context != NULL && IsA((fcinfo)->context, TriggerData)) ((fcinfo)->context != NULL && IsA((fcinfo)->context, TriggerData))
</programlisting> </programlisting>
@ -176,7 +167,7 @@ typedef struct TriggerData
<term><structfield>type</></term> <term><structfield>type</></term>
<listitem> <listitem>
<para> <para>
Always <literal>T_TriggerData</literal> if this is a trigger event. Always <literal>T_TriggerData</literal>.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
@ -185,69 +176,69 @@ typedef struct TriggerData
<term><structfield>tg_event</></term> <term><structfield>tg_event</></term>
<listitem> <listitem>
<para> <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>: following macros to examine <literal>tg_event</literal>:
<variablelist> <variablelist>
<varlistentry> <varlistentry>
<term>TRIGGER_FIRED_BEFORE(tg_event)</term> <term><literal>TRIGGER_FIRED_BEFORE(tg_event)</literal></term>
<listitem> <listitem>
<para> <para>
returns TRUE if trigger fired BEFORE. Returns true if the trigger fired before the operation.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
<term>TRIGGER_FIRED_AFTER(tg_event)</term> <term><literal>TRIGGER_FIRED_AFTER(tg_event)</literal></term>
<listitem> <listitem>
<para> <para>
Returns TRUE if trigger fired AFTER. Returns true if the trigger fired after the operation.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
<term>TRIGGER_FIRED_FOR_ROW(event)</term> <term><literal>TRIGGER_FIRED_FOR_ROW(tg_event)</literal></term>
<listitem> <listitem>
<para> <para>
Returns TRUE if trigger fired for a ROW-level event. Returns true if the trigger fired for a row-level event.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
<term>TRIGGER_FIRED_FOR_STATEMENT(event)</term> <term><literal>TRIGGER_FIRED_FOR_STATEMENT(tg_event)</literal></term>
<listitem> <listitem>
<para> <para>
Returns TRUE if trigger fired for STATEMENT-level event. Returns true if the trigger fired for a statement-level event.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
<term>TRIGGER_FIRED_BY_INSERT(event)</term> <term><literal>TRIGGER_FIRED_BY_INSERT(tg_event)</literal></term>
<listitem> <listitem>
<para> <para>
Returns TRUE if trigger fired by <command>INSERT</command>. Returns true if the trigger was fired by an <command>INSERT</command> command.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
<term>TRIGGER_FIRED_BY_DELETE(event)</term> <term><literal>TRIGGER_FIRED_BY_UPDATE(tg_event)</literal></term>
<listitem> <listitem>
<para> <para>
Returns TRUE if trigger fired by <command>DELETE</command>. Returns true if the trigger was fired by an <command>UPDATE</command> command.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
<term>TRIGGER_FIRED_BY_UPDATE(event)</term> <term><literal>TRIGGER_FIRED_BY_DELETE(tg_event)</literal></term>
<listitem> <listitem>
<para> <para>
Returns TRUE if trigger fired by <command>UPDATE</command>. Returns true if the trigger was fired by a <command>DELETE</command> command.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
@ -260,14 +251,14 @@ typedef struct TriggerData
<term><structfield>tg_relation</></term> <term><structfield>tg_relation</></term>
<listitem> <listitem>
<para> <para>
is a pointer to structure describing the triggered A pointer to a structure describing the relation that the trigger fired for.
relation. Look at <filename>utils/rel.h</> for details about Look at <filename>utils/rel.h</> for details about
this structure. The most interesting things are this structure. The most interesting things are
<literal>tg_relation->rd_att</> (descriptor of the relation <literal>tg_relation->rd_att</> (descriptor of the relation
tuples) and <literal>tg_relation->rd_rel->relname</> tuples) and <literal>tg_relation->rd_rel->relname</>
(relation's name. This is not <type>char*</>, but (relation name; the type is not <type>char*</> but
<type>NameData</>. Use <type>NameData</>; use
<literal>SPI_getrelname(tg_relation)</> to get <type>char*</> if you <literal>SPI_getrelname(tg_relation)</> to get a <type>char*</> if you
need a copy of the name). need a copy of the name).
</para> </para>
</listitem> </listitem>
@ -277,15 +268,13 @@ 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 A pointer to the row for which the trigger was fired. This is
the tuple being inserted (if <command>INSERT</command>), deleted the row being inserted, updated, or deleted. If this trigger
(if <command>DELETE</command>) or updated (if was fired for an <command>INSERT</command> or
<command>UPDATE</command>). If this trigger was fired for an <command>DELETE</command> then this is what you should return
<command>INSERT</command> or <command>DELETE</command> then this to from the function if you don't want to replace the row with
is what you should return to the Executor if you don't want to a different one (in the case of <command>INSERT</command>) or
replace the tuple with a different one (in the case of skip the operation.
<command>INSERT</command>) or skip the operation (in the case of
<command>DELETE</command>).
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
@ -294,12 +283,13 @@ 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 A pointer to the new version of the row, if the trigger was
<command>UPDATE</command> and <symbol>NULL</> if this is for an fired for an <command>UPDATE</command>, and <symbol>NULL</> if
<command>INSERT</command> or a <command>DELETE</command>. This is it is for an <command>INSERT</command> or a
what you are to return to Executor if <command>UPDATE</command> <command>DELETE</command>. This is what you have to return
and you don't want to replace this tuple with another one or skip from the function if the event is an <command>UPDATE</command>
the operation. and you don't want to replace this row by a different one or
skip the operation.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
@ -308,7 +298,8 @@ typedef struct TriggerData
<term><structfield>tg_trigger</></term> <term><structfield>tg_trigger</></term>
<listitem> <listitem>
<para> <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> <programlisting>
typedef struct Trigger typedef struct Trigger
@ -330,9 +321,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</>, and <structfield>tgargs</> is an array of
pointers to the arguments specified in the <command>CREATE 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. only.
</para> </para>
</listitem> </listitem>
@ -345,59 +336,73 @@ typedef struct Trigger
<title>Visibility of Data Changes</title> <title>Visibility of Data Changes</title>
<para> <para>
<productname>PostgreSQL</productname> data changes visibility rule: during a query execution, data If you are using the SPI interface to execute SQL commands in your
changes made by the query itself (via SQL-function, SPI-function, triggers) trigger functions written in C (or you are using a different
are invisible to the query scan. For example, in query 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> <itemizedlist>
INSERT INTO a SELECT * FROM a; <listitem>
</programlisting> <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 <listitem>
duplicates the database table within itself (subject to unique index <para>
rules, of course) without recursing. 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>
<para> <para>
But keep in mind this notice about visibility in the SPI documentation: The next section contains a demonstration of these rules applied.
<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!
</para> </para>
</sect1> </sect1>
<sect1 id="trigger-examples"> <sect1 id="trigger-example">
<title>Examples</title> <title>A Complete Example</title>
<para> <para>
There are more complex examples in Here is a very simple example of a trigger function written in C.
<filename>src/test/regress/regress.c</filename> and The function <function>trigf</> reports the number of rows in the
in <filename>contrib/spi</filename>. 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>
<para> <para>
Here is a very simple example of trigger usage. Function First, the table definition:
<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).
<programlisting> <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 "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); extern Datum trigf(PG_FUNCTION_ARGS);
@ -414,11 +419,11 @@ trigf(PG_FUNCTION_ARGS)
bool isnull; bool isnull;
int ret, i; 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)) 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)) if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
rettuple = trigdata->tg_newtuple; rettuple = trigdata->tg_newtuple;
else else
@ -436,29 +441,29 @@ trigf(PG_FUNCTION_ARGS)
tupdesc = trigdata->tg_relation->rd_att; tupdesc = trigdata->tg_relation->rd_att;
/* Connect to SPI manager */ /* connect to SPI manager */
if ((ret = SPI_connect()) < 0) if ((ret = SPI_connect()) < 0)
elog(INFO, "trigf (fired %s): SPI_connect returned %d", when, ret); 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); ret = SPI_exec("SELECT count(*) FROM ttest", 0);
if (ret < 0) if (ret < 0)
elog(NOTICE, "trigf (fired %s): SPI_exec returned %d", when, ret); elog(NOTICE, "trigf (fired %s): SPI_exec returned %d", when, ret);
/* count(*) returns int8 as of PG 7.2, so be careful to convert */ /* count(*) returns int8, so be careful to convert */
i = (int) DatumGetInt64(SPI_getbinval(SPI_tuptable->vals[0], i = DatumGetInt64(SPI_getbinval(SPI_tuptable->vals[0],
SPI_tuptable->tupdesc, SPI_tuptable->tupdesc,
1, 1,
&amp;isnull)); &amp;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(); SPI_finish();
if (checknull) if (checknull)
{ {
(void) SPI_getbinval(rettuple, tupdesc, 1, &amp;isnull); SPI_getbinval(rettuple, tupdesc, 1, &amp;isnull);
if (isnull) if (isnull)
rettuple = NULL; rettuple = NULL;
} }
@ -469,36 +474,38 @@ trigf(PG_FUNCTION_ARGS)
</para> </para>
<para> <para>
Now, compile and create the trigger function: After you have compiled the source code, declare the function and
the triggers:
<programlisting> <programlisting>
CREATE FUNCTION trigf () RETURNS TRIGGER AS CREATE FUNCTION trigf() RETURNS trigger
'...path_to_so' LANGUAGE C; 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> </programlisting>
</para>
<programlisting> <para>
vac=> CREATE TRIGGER tbefore BEFORE INSERT OR UPDATE OR DELETE ON ttest Now you can test the operation of the trigger:
FOR EACH ROW EXECUTE PROCEDURE trigf(); <screen>
CREATE => INSERT INTO ttest VALUES (NULL);
vac=> CREATE TRIGGER tafter AFTER INSERT OR UPDATE OR DELETE ON ttest INFO: trigf (fired before): there are 0 rows in ttest
FOR EACH ROW EXECUTE PROCEDURE trigf();
CREATE
vac=> INSERT INTO ttest VALUES (NULL);
WARNING: trigf (fired before): there are 0 tuples in ttest
INSERT 0 0 INSERT 0 0
-- Insertion skipped and AFTER trigger is not fired -- Insertion skipped and AFTER trigger is not fired
vac=> SELECT * FROM ttest; => SELECT * FROM ttest;
x x
--- ---
(0 rows) (0 rows)
vac=> INSERT INTO ttest VALUES (1); => INSERT INTO ttest VALUES (1);
INFO: trigf (fired before): there are 0 tuples in ttest INFO: trigf (fired before): there are 0 rows in ttest
INFO: trigf (fired after ): there are 1 tuples in ttest INFO: trigf (fired after ): there are 1 rows in ttest
^^^^^^^^ ^^^^^^^^
remember what we said about visibility. remember what we said about visibility.
INSERT 167793 1 INSERT 167793 1
@ -508,25 +515,25 @@ vac=> SELECT * FROM ttest;
1 1
(1 row) (1 row)
vac=> INSERT INTO ttest SELECT x * 2 FROM ttest; => INSERT INTO ttest SELECT x * 2 FROM ttest;
INFO: trigf (fired before): there are 1 tuples in ttest INFO: trigf (fired before): there are 1 rows in ttest
INFO: trigf (fired after ): there are 2 tuples in ttest INFO: trigf (fired after ): there are 2 rows in ttest
^^^^^^^^ ^^^^^^
remember what we said about visibility. remember what we said about visibility.
INSERT 167794 1 INSERT 167794 1
vac=> SELECT * FROM ttest; => SELECT * FROM ttest;
x x
--- ---
1 1
2 2
(2 rows) (2 rows)
vac=> UPDATE ttest SET x = NULL WHERE x = 2; => UPDATE ttest SET x = NULL WHERE x = 2;
INFO: trigf (fired before): there are 2 tuples in ttest INFO: trigf (fired before): there are 2 rows in ttest
UPDATE 0 UPDATE 0
vac=> UPDATE ttest SET x = 4 WHERE x = 2; => UPDATE ttest SET x = 4 WHERE x = 2;
INFO: trigf (fired before): there are 2 tuples in ttest INFO: trigf (fired before): there are 2 rows in ttest
INFO: trigf (fired after ): there are 2 tuples in ttest INFO: trigf (fired after ): there are 2 rows in ttest
UPDATE 1 UPDATE 1
vac=> SELECT * FROM ttest; vac=> SELECT * FROM ttest;
x x
@ -535,21 +542,27 @@ vac=> SELECT * FROM ttest;
4 4
(2 rows) (2 rows)
vac=> DELETE FROM ttest; => DELETE FROM ttest;
INFO: trigf (fired before): there are 2 tuples in ttest INFO: trigf (fired before): there are 2 rows in ttest
INFO: trigf (fired after ): there are 1 tuples in ttest INFO: trigf (fired after ): there are 1 rows in ttest
INFO: trigf (fired before): there are 1 tuples in ttest INFO: trigf (fired before): there are 1 rows in ttest
INFO: trigf (fired after ): there are 0 tuples in ttest INFO: trigf (fired after ): there are 0 rows in ttest
^^^^^^^^ ^^^^^^
remember what we said about visibility. remember what we said about visibility.
DELETE 2 DELETE 2
vac=> SELECT * FROM ttest; => SELECT * FROM ttest;
x x
--- ---
(0 rows) (0 rows)
</programlisting> </screen>
</para> </para>
<para>
There are more complex examples in
<filename>src/test/regress/regress.c</filename> and
in <filename>contrib/spi</filename>.
</para>
</sect1> </sect1>
</chapter> </chapter>