Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 01a8948

Browse files
committed
Trigger programming guide.
Description of data changes visibility added.
1 parent da707e4 commit 01a8948

File tree

2 files changed

+360
-1
lines changed

2 files changed

+360
-1
lines changed

doc/spi.txt

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ int SPI_finish(void)
7171
with this - it means that nothing was made by SPI manager.
7272

7373
NOTE! SPI_finish() MUST be called by connected procedure or you may get
74-
unpredictable results!
74+
unpredictable results! But you are able to don't call SPI_finish() if you
75+
abort transaction (via elog(WARN)).
7576

7677

7778
int SPI_exec(char *query, int tcount)
@@ -354,6 +355,27 @@ allocate memory for this in upper context!
354355
this query is done!
355356

356357

358+
Data changes visibility
359+
360+
PostgreSQL data changes visibility rule: during query execution data
361+
changes made by query itself (via SQL-function, SPI-function, triggers)
362+
are invisible to the query scan.
363+
364+
For example, in query
365+
366+
INSERT INTO a SELECT * FROM a
367+
368+
tuples inserted are invisible for SELECT' scan.
369+
370+
But also note that
371+
372+
changes made by query Q are visible by queries which are started after
373+
query Q, no matter - are they started inside Q (during execution of Q) or
374+
after Q is done.
375+
376+
Last example of usage SPI function below demonstrates visibility rule.
377+
378+
357379
Examples
358380

359381
There are complex examples in contrib/spi and in
@@ -446,3 +468,43 @@ execq
446468
-----
447469
3 <<< 10 is max value only, 3 is real # of tuples
448470
(1 row)
471+
472+
vac=> delete from a;
473+
DELETE 3
474+
vac=> insert into a values (execq('select * from a', 0) + 1);
475+
INSERT 167712 1
476+
vac=> select * from a;
477+
x
478+
-
479+
1 <<< no tuples in a (0) + 1
480+
(1 row)
481+
482+
vac=> insert into a values (execq('select * from a', 0) + 1);
483+
NOTICE:EXECQ: 0
484+
INSERT 167713 1
485+
vac=> select * from a;
486+
x
487+
-
488+
1
489+
2 <<< there was single tuple in a + 1
490+
(2 rows)
491+
492+
-- This demonstrates data changes visibility rule:
493+
494+
vac=> insert into a select execq('select * from a', 0) * x from a;
495+
NOTICE:EXECQ: 1
496+
NOTICE:EXECQ: 2
497+
NOTICE:EXECQ: 1
498+
NOTICE:EXECQ: 2
499+
NOTICE:EXECQ: 2
500+
INSERT 0 2
501+
vac=> select * from a;
502+
x
503+
-
504+
1
505+
2
506+
2 <<< 2 tuples * 1 (x in first tuple)
507+
6 <<< 3 tuples (2 + 1 just inserted) * 2 (x in second tuple)
508+
(4 rows) ^^^^^^^^
509+
tuples visible to execq() in different invocations
510+

doc/trigger.txt

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
2+
PostgreSQL Trigger Programming Guide
3+
4+
For the lack of Procedural Language (PL) in current version of
5+
PostgreSQL, there is only ability to specify call to a C-function as trigger
6+
action.
7+
Also, STATEMENT-level trigger events are not supported in current
8+
version, and so you are only able to specify BEFORE | AFTER
9+
INSERT|DELETE|UPDATE of a tuple as trigger event.
10+
11+
If trigger event occures, trigger manager (called by Executor)
12+
initializes global structure TriggerData *CurrentTriggerData (described
13+
below) and calls trigger function to handle event.
14+
15+
Trigger function must be created before trigger creation as function
16+
not accepting any arguments and returns opaque.
17+
Actually, there are two specific features in triggers handling.
18+
19+
First, in CREATE TRIGGER one may specify arguments for trigger
20+
function (EXECUTE PROCEDURE tfunc (aa,'bb', 1)), and these arguments
21+
will be passed to trigger function in CurrentTriggerData.
22+
It allows to use single function for many triggers and process events in
23+
different ways.
24+
Also, function may be used for triggering different relations (these
25+
functions are named as "general trigger functions").
26+
27+
Second, trigger function has to return HeapTuple to upper Executor.
28+
No matter for triggers fired AFTER operation (INSERT, DELETE, UPDATE),
29+
but it allows to BEFORE triggers:
30+
- return NULL to skip operation for current tuple (and so tuple
31+
will not be inserted/updated/deleted);
32+
- return pointer to another tuple (INSERT and UPDATE only) which will be
33+
inserted (as new version of updated tuple if UPDATE) instead of
34+
original tuple.
35+
36+
Note, that there is no initialization performed by CREATE TRIGGER
37+
handler. It will be changed in the future.
38+
39+
Also, if more than one trigger defined for the same event on the same
40+
relation then order of trigger firing is unpredictable. It may be changed in
41+
the future.
42+
43+
Also, if a trigger function executes SQL-queries (using SPI) then these
44+
queries may fire triggers again. This is known as cascading of triggers.
45+
There is no explicit limitation for number of cascade levels.
46+
If a trigger is fired by INSERT and inserts new tuple in the same
47+
relation then this trigger will be fired again. Currently, there is nothing
48+
provided for synchronization (etc) of these cases. It may be changed. At
49+
the moment, there is function funny_dup17() in the regress tests which uses
50+
some technics to stop recursion (cascading) of itself...
51+
52+
53+
Interaction with trigger manager
54+
55+
As it's mentioned above when function is called by trigger manager
56+
structure TriggerData *CurrentTriggerData is NOT NULL and initialized. And
57+
so, it's better to check CurrentTriggerData against being NULL in the
58+
begining and set it to NULL just after fetching information - to prevent
59+
calls to trigger function not from trigger manager.
60+
61+
struct TriggerData is defined in src/include/commands/trigger.h:
62+
63+
typedef struct TriggerData
64+
{
65+
TriggerEvent tg_event;
66+
Relation tg_relation;
67+
HeapTuple tg_trigtuple;
68+
HeapTuple tg_newtuple;
69+
Trigger *tg_trigger;
70+
} TriggerData;
71+
72+
tg_event
73+
describes event for what function is called. You may use macros
74+
to deal with tg_event:
75+
76+
TRIGGER_FIRED_BEFORE(event) returns TRUE if trigger fired BEFORE;
77+
TRIGGER_FIRED_AFTER(event) returns TRUE if trigger fired AFTER;
78+
TRIGGER_FIRED_FOR_ROW(event) returns TRUE if trigger fired for
79+
ROW-level event;
80+
TRIGGER_FIRED_FOR_STATEMENT(event) returns TRUE if trigger fired for
81+
STATEMENT-level event;
82+
TRIGGER_FIRED_BY_INSERT(event) returns TRUE if trigger fired by INSERT;
83+
TRIGGER_FIRED_BY_DELETE(event) returns TRUE if trigger fired by DELETE;
84+
TRIGGER_FIRED_BY_UPDATE(event) returns TRUE if trigger fired by UPDATE.
85+
86+
tg_relation
87+
is pointer to structure describing triggered relation. Look @
88+
src/include/utils/rel.h about this structure. The most interest things
89+
are tg_relation->rd_att (descriptor of relation tuples) and
90+
tg_relation->rd_rel->relname (relation' name. This is not char*, but
91+
NameData - use SPI_getrelname(tg_relation) to get char* to copy of name).
92+
93+
tg_trigtuple
94+
is tuple (pointer) for which trigger is fired. This is tuple to being
95+
inserted (if INSERT), deleted (if DELETE) or updated (if UPDATE).
96+
If INSERT/DELETE then this is what you are to return to Executor if
97+
you don't want to replace tuple with another one (INSERT) or skip
98+
operation.
99+
100+
tg_newtuple
101+
is pointer to new version of tuple if UPDATE and NULL if INSERT/DELETE.
102+
This is what you are to return to Executor if UPDATE and you don't want
103+
to replace tuple with another one or skip operation.
104+
105+
tg_trigger
106+
is pointer to structure Trigger defined in src/include/utils/rel.h:
107+
108+
typedef struct Trigger
109+
{
110+
char *tgname;
111+
Oid tgfoid;
112+
func_ptr tgfunc;
113+
int16 tgtype;
114+
int16 tgnargs;
115+
int16 tgattr[8];
116+
char **tgargs;
117+
} Trigger;
118+
119+
tgname is trigger' name, tgnargs is number of arguments in tgargs, tgargs
120+
is array of pointers to arguments specified in CREATE TRIGGER. Other
121+
members are for internal use.
122+
123+
124+
Data changes visibility
125+
126+
PostgreSQL data changes visibility rule: during query execution data
127+
changes made by query itself (via SQL-function, SPI-function, triggers)
128+
are invisible to the query scan.
129+
130+
For example, in query
131+
132+
INSERT INTO a SELECT * FROM a
133+
134+
tuples inserted are invisible for SELECT' scan.
135+
136+
But keep in mind notices about visibility in SPI documentation:
137+
138+
changes made by query Q are visible by queries which are started after
139+
query Q, no matter - are they started inside Q (during execution of Q) or
140+
after Q is done.
141+
142+
This is true for triggers as well. And so, though tuple being inserted
143+
(tg_trigtuple) is not visible to queries in BEFORE trigger, this tuple (just
144+
inserted) is visible to queries in AFTER trigger, and to queries in
145+
BEFORE/AFTER triggers fired after this!
146+
147+
148+
Examples
149+
150+
There are complex examples in contrib/spi and in
151+
src/test/regress/regress.c.
152+
153+
This is very simple example of trigger usage. Function trigf reports
154+
about number of tuples in triggered relation ttest and in trigger fired
155+
BEFORE INSERT/UPDATE checks against is attribute x NULL and skips operations
156+
for NULLs (ala NOT NULL implementation using triggers without aborting
157+
transaction if NULL).
158+
159+
----------------------------------------------------------------------------
160+
#include "executor/spi.h" /* this is what you need to work with SPI */
161+
#include "commands/trigger.h" /* -"- and triggers */
162+
163+
HeapTuple trigf(void);
164+
165+
HeapTuple
166+
trigf()
167+
{
168+
TupleDesc tupdesc;
169+
HeapTuple rettuple;
170+
char *when;
171+
bool checknull = false;
172+
bool isnull;
173+
int ret, i;
174+
175+
if (!CurrentTriggerData)
176+
elog(WARN, "trigf: triggers are not initialized");
177+
178+
/* tuple to return to Executor */
179+
if (TRIGGER_FIRED_BY_UPDATE(CurrentTriggerData->tg_event))
180+
rettuple = CurrentTriggerData->tg_newtuple;
181+
else
182+
rettuple = CurrentTriggerData->tg_trigtuple;
183+
184+
/* check for NULLs ? */
185+
if (!TRIGGER_FIRED_BY_DELETE(CurrentTriggerData->tg_event) &&
186+
TRIGGER_FIRED_BEFORE(CurrentTriggerData->tg_event))
187+
checknull = true;
188+
189+
if (TRIGGER_FIRED_BEFORE(CurrentTriggerData->tg_event))
190+
when = "before";
191+
else
192+
when = "after ";
193+
194+
tupdesc = CurrentTriggerData->tg_relation->rd_att;
195+
CurrentTriggerData = NULL;
196+
197+
/* Connect to SPI manager */
198+
if ((ret = SPI_connect()) < 0)
199+
elog(WARN, "trigf (fired %s): SPI_connect returned %d", when, ret);
200+
201+
/* Get number of tuples in relation */
202+
ret = SPI_exec("select count(*) from ttest", 0);
203+
204+
if (ret < 0)
205+
elog(WARN, "trigf (fired %s): SPI_exec returned %d", when, ret);
206+
207+
i = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull);
208+
209+
elog (NOTICE, "trigf (fired %s): there are %d tuples in ttest", when, i);
210+
211+
SPI_finish();
212+
213+
if (checknull)
214+
{
215+
i = SPI_getbinval(rettuple, tupdesc, 1, &isnull);
216+
if (isnull)
217+
rettuple = NULL;
218+
}
219+
220+
return (rettuple);
221+
}
222+
----------------------------------------------------------------------------
223+
224+
Now, compile and
225+
create table ttest (x int4);
226+
create function trigf () returns opaque as
227+
'...path_to_so' language 'c';
228+
229+
vac=> create trigger tbefore before insert or update or delete on ttest
230+
for each row execute procedure trigf();
231+
CREATE
232+
vac=> create trigger tafter after insert or update or delete on ttest
233+
for each row execute procedure trigf();
234+
CREATE
235+
vac=> insert into ttest values (null);
236+
NOTICE:trigf (fired before): there are 0 tuples in ttest
237+
INSERT 0 0
238+
239+
-- Insertion skipped and AFTER trigger is not fired
240+
241+
vac=> select * from ttest;
242+
x
243+
-
244+
(0 rows)
245+
246+
vac=> insert into ttest values (1);
247+
NOTICE:trigf (fired before): there are 0 tuples in ttest
248+
NOTICE:trigf (fired after ): there are 1 tuples in ttest
249+
^^^^^^^^
250+
remember about visibility
251+
INSERT 167793 1
252+
vac=> select * from ttest;
253+
x
254+
-
255+
1
256+
(1 row)
257+
258+
vac=> insert into ttest select x * 2 from ttest;
259+
NOTICE:trigf (fired before): there are 1 tuples in ttest
260+
NOTICE:trigf (fired after ): there are 2 tuples in ttest
261+
^^^^^^^^
262+
remember about visibility
263+
INSERT 167794 1
264+
vac=> select * from ttest;
265+
x
266+
-
267+
1
268+
2
269+
(2 rows)
270+
271+
vac=> update ttest set x = null where x = 2;
272+
NOTICE:trigf (fired before): there are 2 tuples in ttest
273+
UPDATE 0
274+
vac=> update ttest set x = 4 where x = 2;
275+
NOTICE:trigf (fired before): there are 2 tuples in ttest
276+
NOTICE:trigf (fired after ): there are 2 tuples in ttest
277+
UPDATE 1
278+
vac=> select * from ttest;
279+
x
280+
-
281+
1
282+
4
283+
(2 rows)
284+
285+
vac=> delete from ttest;
286+
NOTICE:trigf (fired before): there are 2 tuples in ttest
287+
NOTICE:trigf (fired after ): there are 1 tuples in ttest
288+
NOTICE:trigf (fired before): there are 1 tuples in ttest
289+
NOTICE:trigf (fired after ): there are 0 tuples in ttest
290+
^^^^^^^^
291+
remember about visibility
292+
DELETE 2
293+
vac=> select * from ttest;
294+
x
295+
-
296+
(0 rows)
297+

0 commit comments

Comments
 (0)