Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
P
Postgres FD Implementation
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Abuhujair Javed
Postgres FD Implementation
Commits
d8521b9b
Commit
d8521b9b
authored
Apr 11, 2003
by
Peter Eisentraut
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Revision
parent
dcb00495
Changes
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
199 additions
and
186 deletions
+199
-186
doc/src/sgml/trigger.sgml
doc/src/sgml/trigger.sgml
+199
-186
No files found.
doc/src/sgml/trigger.sgml
View file @
d8521b9b
<!--
$Header: /cvsroot/pgsql/doc/src/sgml/trigger.sgml,v 1.2
7 2003/03/25 16:15:38
petere Exp $
$Header: /cvsroot/pgsql/doc/src/sgml/trigger.sgml,v 1.2
8 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
-querie
s (using SPI) then these
querie
s may fire triggers again. This is known as cascading
If a trigger function executes SQL
command
s (using SPI) then these
command
s 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
parameter
s, but it is passed a <quote>context</>
any normal
argument
s, 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>
d
escribes the event for which the function is called. You may use the
D
escribes 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</>
. U
se
<literal>SPI_getrelname(tg_relation)</> to get <type>char*</> if you
(relation
name; the type is not <type>char*</>
but
<type>NameData</>
; u
se
<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.
O
ther members are for internal use
TRIGGER</command> statement.
The o
ther 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 quer
y
<programlisting>
INSERT INTO a SELECT * FROM a;
</programlisting>
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 internall
y
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:
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.
<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>
<listitem>
<para>
But keep in mind this notice about visibility in the SPI documentation:
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>
<blockquote
>
<listitem
>
<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
.
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>
</blockquote>
</listitem>
</itemizedlist>
</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-example
s
">
<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
fir
ed by trigger manager");
elog(ERROR, "trigf: not
call
ed by trigger manager");
/* tuple to return to
E
xecutor */
/* tuple to return to
e
xecutor */
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;
/*
C
onnect to SPI manager */
/*
c
onnect 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 tuple
s in ttest", when, i);
elog (
INFO, "trigf (fired %s): there are %d row
s 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 TRIGGER tbefore BEFORE INSERT OR UPDATE OR DELETE ON ttest
FOR EACH ROW EXECUTE PROCEDURE trigf();
CREATE TABLE ttest (x int4);
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
tuple
s in ttest
INFO: trigf (fired after ): there are 1
tuple
s in ttest
=> INSERT INTO ttest VALUES (1);
INFO: trigf (fired before): there are 0
row
s in ttest
INFO: trigf (fired after ): there are 1
row
s 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
tuple
s in ttest
INFO: trigf (fired after ): there are 2
tuple
s in ttest
^^^^^^
^^
=> INSERT INTO ttest SELECT x * 2 FROM ttest;
INFO: trigf (fired before): there are 1
row
s in ttest
INFO: trigf (fired after ): there are 2
row
s 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
tuple
s in ttest
=> UPDATE ttest SET x = NULL WHERE x = 2;
INFO: trigf (fired before): there are 2
row
s in ttest
UPDATE 0
vac
=> UPDATE ttest SET x = 4 WHERE x = 2;
INFO: trigf (fired before): there are 2
tuple
s in ttest
INFO: trigf (fired after ): there are 2
tuple
s in ttest
=> UPDATE ttest SET x = 4 WHERE x = 2;
INFO: trigf (fired before): there are 2
row
s in ttest
INFO: trigf (fired after ): there are 2
row
s 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
tuple
s in ttest
INFO: trigf (fired after ): there are 1
tuple
s in ttest
INFO: trigf (fired before): there are 1
tuple
s in ttest
INFO: trigf (fired after ): there are 0
tuple
s in ttest
^^^^^^
^^
=> DELETE FROM ttest;
INFO: trigf (fired before): there are 2
row
s in ttest
INFO: trigf (fired after ): there are 1
row
s in ttest
INFO: trigf (fired before): there are 1
row
s in ttest
INFO: trigf (fired after ): there are 0
row
s 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>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment