|
| 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