5 * refint.c -- set of functions to define referential integrity
6 * constraints using general triggers.
12 #include "commands/trigger.h"
13 #include "executor/spi.h"
14 #include "utils/builtins.h"
15 #include "utils/memutils.h"
16 #include "utils/rel.h"
27 static EPlan
*FPlans
= NULL
;
28 static int nFPlans
= 0;
29 static EPlan
*PPlans
= NULL
;
30 static int nPPlans
= 0;
32 static EPlan
*find_plan(char *ident
, EPlan
**eplan
, int *nplans
);
35 * check_primary_key () -- check that key in tuple being inserted/updated
36 * references existing tuple in "primary" table.
37 * Though it's called without args You have to specify referenced
38 * table/keys while creating trigger: key field names in triggered table,
39 * referenced table name, referenced key field names:
41 * check_primary_key ('Fkey1', 'Fkey2', 'Ptable', 'Pkey1', 'Pkey2').
44 PG_FUNCTION_INFO_V1(check_primary_key
);
47 check_primary_key(PG_FUNCTION_ARGS
)
49 TriggerData
*trigdata
= (TriggerData
*) fcinfo
->context
;
50 Trigger
*trigger
; /* to get trigger name */
51 int nargs
; /* # of args specified in CREATE TRIGGER */
52 char **args
; /* arguments: column names and table name */
53 int nkeys
; /* # of key columns (= nargs / 2) */
54 Datum
*kvals
; /* key values */
55 char *relname
; /* referenced relation name */
56 Relation rel
; /* triggered relation */
57 HeapTuple tuple
= NULL
; /* tuple to return */
58 TupleDesc tupdesc
; /* tuple description */
59 EPlan
*plan
; /* prepared plan */
60 Oid
*argtypes
= NULL
; /* key types to prepare execution plan */
61 bool isnull
; /* to know is some column NULL or not */
62 char ident
[2 * NAMEDATALEN
]; /* to identify myself */
67 elog(DEBUG4
, "check_primary_key: Enter Function");
71 * Some checks first...
74 /* Called by trigger manager ? */
75 if (!CALLED_AS_TRIGGER(fcinfo
))
77 elog(ERROR
, "check_primary_key: not fired by trigger manager");
79 /* Should be called for ROW trigger */
80 if (!TRIGGER_FIRED_FOR_ROW(trigdata
->tg_event
))
82 elog(ERROR
, "check_primary_key: must be fired for row");
84 /* If INSERTion then must check Tuple to being inserted */
85 if (TRIGGER_FIRED_BY_INSERT(trigdata
->tg_event
))
86 tuple
= trigdata
->tg_trigtuple
;
88 /* Not should be called for DELETE */
89 else if (TRIGGER_FIRED_BY_DELETE(trigdata
->tg_event
))
91 elog(ERROR
, "check_primary_key: cannot process DELETE events");
93 /* If UPDATE, then must check new Tuple, not old one */
95 tuple
= trigdata
->tg_newtuple
;
97 trigger
= trigdata
->tg_trigger
;
98 nargs
= trigger
->tgnargs
;
99 args
= trigger
->tgargs
;
101 if (nargs
% 2 != 1) /* odd number of arguments! */
103 elog(ERROR
, "check_primary_key: odd number of arguments should be specified");
106 relname
= args
[nkeys
];
107 rel
= trigdata
->tg_relation
;
108 tupdesc
= rel
->rd_att
;
110 /* Connect to SPI manager */
111 if ((ret
= SPI_connect()) < 0)
113 elog(ERROR
, "check_primary_key: SPI_connect returned %d", ret
);
116 * We use SPI plan preparation feature, so allocate space to place key
119 kvals
= (Datum
*) palloc(nkeys
* sizeof(Datum
));
122 * Construct ident string as TriggerName $ TriggeredRelationId and try to
123 * find prepared execution plan.
125 snprintf(ident
, sizeof(ident
), "%s$%u", trigger
->tgname
, rel
->rd_id
);
126 plan
= find_plan(ident
, &PPlans
, &nPPlans
);
128 /* if there is no plan then allocate argtypes for preparation */
129 if (plan
->nplans
<= 0)
130 argtypes
= (Oid
*) palloc(nkeys
* sizeof(Oid
));
132 /* For each column in key ... */
133 for (i
= 0; i
< nkeys
; i
++)
135 /* get index of column in tuple */
136 int fnumber
= SPI_fnumber(tupdesc
, args
[i
]);
138 /* Bad guys may give us un-existing column in CREATE TRIGGER */
141 (errcode(ERRCODE_UNDEFINED_COLUMN
),
142 errmsg("there is no attribute \"%s\" in relation \"%s\"",
143 args
[i
], SPI_getrelname(rel
))));
145 /* Well, get binary (in internal format) value of column */
146 kvals
[i
] = SPI_getbinval(tuple
, tupdesc
, fnumber
, &isnull
);
149 * If it's NULL then nothing to do! DON'T FORGET call SPI_finish ()!
150 * DON'T FORGET return tuple! Executor inserts tuple you're returning!
151 * If you return NULL then nothing will be inserted!
156 return PointerGetDatum(tuple
);
159 if (plan
->nplans
<= 0) /* Get typeId of column */
160 argtypes
[i
] = SPI_gettypeid(tupdesc
, fnumber
);
164 * If we have to prepare plan ...
166 if (plan
->nplans
<= 0)
172 * Construct query: SELECT 1 FROM _referenced_relation_ WHERE Pkey1 =
173 * $1 [AND Pkey2 = $2 [...]]
175 snprintf(sql
, sizeof(sql
), "select 1 from %s where ", relname
);
176 for (i
= 0; i
< nkeys
; i
++)
178 snprintf(sql
+ strlen(sql
), sizeof(sql
) - strlen(sql
), "%s = $%d %s",
179 args
[i
+ nkeys
+ 1], i
+ 1, (i
< nkeys
- 1) ? "and " : "");
182 /* Prepare plan for query */
183 pplan
= SPI_prepare(sql
, nkeys
, argtypes
);
186 elog(ERROR
, "check_primary_key: SPI_prepare returned %s", SPI_result_code_string(SPI_result
));
189 * Remember that SPI_prepare places plan in current memory context -
190 * so, we have to save plan in TopMemoryContext for later use.
192 if (SPI_keepplan(pplan
))
194 elog(ERROR
, "check_primary_key: SPI_keepplan failed");
195 plan
->splan
= (SPIPlanPtr
*) MemoryContextAlloc(TopMemoryContext
,
197 *(plan
->splan
) = pplan
;
202 * Ok, execute prepared plan.
204 ret
= SPI_execp(*(plan
->splan
), kvals
, NULL
, 1);
205 /* we have no NULLs - so we pass ^^^^ here */
209 elog(ERROR
, "check_primary_key: SPI_execp returned %d", ret
);
212 * If there are no tuples returned by SELECT then ...
214 if (SPI_processed
== 0)
216 (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION
),
217 errmsg("tuple references non-existent key"),
218 errdetail("Trigger \"%s\" found tuple referencing non-existent key in \"%s\".", trigger
->tgname
, relname
)));
222 return PointerGetDatum(tuple
);
226 * check_foreign_key () -- check that key in tuple being deleted/updated
227 * is not referenced by tuples in "foreign" table(s).
228 * Though it's called without args You have to specify (while creating trigger):
229 * number of references, action to do if key referenced
230 * ('restrict' | 'setnull' | 'cascade'), key field names in triggered
231 * ("primary") table and referencing table(s)/keys:
233 * check_foreign_key (2, 'restrict', 'Pkey1', 'Pkey2',
234 * 'Ftable1', 'Fkey11', 'Fkey12', 'Ftable2', 'Fkey21', 'Fkey22').
237 PG_FUNCTION_INFO_V1(check_foreign_key
);
240 check_foreign_key(PG_FUNCTION_ARGS
)
242 TriggerData
*trigdata
= (TriggerData
*) fcinfo
->context
;
243 Trigger
*trigger
; /* to get trigger name */
244 int nargs
; /* # of args specified in CREATE TRIGGER */
245 char **args
; /* arguments: as described above */
247 int nrefs
; /* number of references (== # of plans) */
248 char action
; /* 'R'estrict | 'S'etnull | 'C'ascade */
249 int nkeys
; /* # of key columns */
250 Datum
*kvals
; /* key values */
251 char *relname
; /* referencing relation name */
252 Relation rel
; /* triggered relation */
253 HeapTuple trigtuple
= NULL
; /* tuple to being changed */
254 HeapTuple newtuple
= NULL
; /* tuple to return */
255 TupleDesc tupdesc
; /* tuple description */
256 EPlan
*plan
; /* prepared plan(s) */
257 Oid
*argtypes
= NULL
; /* key types to prepare execution plan */
258 bool isnull
; /* to know is some column NULL or not */
259 bool isequal
= true; /* are keys in both tuples equal (in UPDATE) */
260 char ident
[2 * NAMEDATALEN
]; /* to identify myself */
267 elog(DEBUG4
, "check_foreign_key: Enter Function");
271 * Some checks first...
274 /* Called by trigger manager ? */
275 if (!CALLED_AS_TRIGGER(fcinfo
))
277 elog(ERROR
, "check_foreign_key: not fired by trigger manager");
279 /* Should be called for ROW trigger */
280 if (!TRIGGER_FIRED_FOR_ROW(trigdata
->tg_event
))
282 elog(ERROR
, "check_foreign_key: must be fired for row");
284 /* Not should be called for INSERT */
285 if (TRIGGER_FIRED_BY_INSERT(trigdata
->tg_event
))
287 elog(ERROR
, "check_foreign_key: cannot process INSERT events");
289 /* Have to check tg_trigtuple - tuple being deleted */
290 trigtuple
= trigdata
->tg_trigtuple
;
293 * But if this is UPDATE then we have to return tg_newtuple. Also, if key
294 * in tg_newtuple is the same as in tg_trigtuple then nothing to do.
297 if (TRIGGER_FIRED_BY_UPDATE(trigdata
->tg_event
))
299 newtuple
= trigdata
->tg_newtuple
;
302 trigger
= trigdata
->tg_trigger
;
303 nargs
= trigger
->tgnargs
;
304 args
= trigger
->tgargs
;
306 if (nargs
< 5) /* nrefs, action, key, Relation, key - at
309 elog(ERROR
, "check_foreign_key: too short %d (< 5) list of arguments", nargs
);
311 nrefs
= pg_strtoint32(args
[0]);
314 elog(ERROR
, "check_foreign_key: %d (< 1) number of references specified", nrefs
);
315 action
= tolower((unsigned char) *(args
[1]));
316 if (action
!= 'r' && action
!= 'c' && action
!= 's')
318 elog(ERROR
, "check_foreign_key: invalid action %s", args
[1]);
321 nkeys
= (nargs
- nrefs
) / (nrefs
+ 1);
322 if (nkeys
<= 0 || nargs
!= (nrefs
+ nkeys
* (nrefs
+ 1)))
324 elog(ERROR
, "check_foreign_key: invalid number of arguments %d for %d references",
327 rel
= trigdata
->tg_relation
;
328 tupdesc
= rel
->rd_att
;
330 /* Connect to SPI manager */
331 if ((ret
= SPI_connect()) < 0)
333 elog(ERROR
, "check_foreign_key: SPI_connect returned %d", ret
);
336 * We use SPI plan preparation feature, so allocate space to place key
339 kvals
= (Datum
*) palloc(nkeys
* sizeof(Datum
));
342 * Construct ident string as TriggerName $ TriggeredRelationId and try to
343 * find prepared execution plan(s).
345 snprintf(ident
, sizeof(ident
), "%s$%u", trigger
->tgname
, rel
->rd_id
);
346 plan
= find_plan(ident
, &FPlans
, &nFPlans
);
348 /* if there is no plan(s) then allocate argtypes for preparation */
349 if (plan
->nplans
<= 0)
350 argtypes
= (Oid
*) palloc(nkeys
* sizeof(Oid
));
353 * else - check that we have exactly nrefs plan(s) ready
355 else if (plan
->nplans
!= nrefs
)
357 elog(ERROR
, "%s: check_foreign_key: # of plans changed in meantime",
360 /* For each column in key ... */
361 for (i
= 0; i
< nkeys
; i
++)
363 /* get index of column in tuple */
364 int fnumber
= SPI_fnumber(tupdesc
, args
[i
]);
366 /* Bad guys may give us un-existing column in CREATE TRIGGER */
369 (errcode(ERRCODE_UNDEFINED_COLUMN
),
370 errmsg("there is no attribute \"%s\" in relation \"%s\"",
371 args
[i
], SPI_getrelname(rel
))));
373 /* Well, get binary (in internal format) value of column */
374 kvals
[i
] = SPI_getbinval(trigtuple
, tupdesc
, fnumber
, &isnull
);
377 * If it's NULL then nothing to do! DON'T FORGET call SPI_finish ()!
378 * DON'T FORGET return tuple! Executor inserts tuple you're returning!
379 * If you return NULL then nothing will be inserted!
384 return PointerGetDatum((newtuple
== NULL
) ? trigtuple
: newtuple
);
388 * If UPDATE then get column value from new tuple being inserted and
389 * compare is this the same as old one. For the moment we use string
390 * presentation of values...
392 if (newtuple
!= NULL
)
394 char *oldval
= SPI_getvalue(trigtuple
, tupdesc
, fnumber
);
397 /* this shouldn't happen! SPI_ERROR_NOOUTFUNC ? */
400 elog(ERROR
, "check_foreign_key: SPI_getvalue returned %s", SPI_result_code_string(SPI_result
));
401 newval
= SPI_getvalue(newtuple
, tupdesc
, fnumber
);
402 if (newval
== NULL
|| strcmp(oldval
, newval
) != 0)
406 if (plan
->nplans
<= 0) /* Get typeId of column */
407 argtypes
[i
] = SPI_gettypeid(tupdesc
, fnumber
);
414 * If we have to prepare plans ...
416 if (plan
->nplans
<= 0)
422 plan
->splan
= (SPIPlanPtr
*) MemoryContextAlloc(TopMemoryContext
,
423 nrefs
* sizeof(SPIPlanPtr
));
425 for (r
= 0; r
< nrefs
; r
++)
430 * For 'R'estrict action we construct SELECT query:
433 * FROM _referencing_relation_
434 * WHERE Fkey1 = $1 [AND Fkey2 = $2 [...]]
436 * to check is tuple referenced or not.
441 snprintf(sql
, sizeof(sql
), "select 1 from %s where ", relname
);
444 * For 'C'ascade action we construct DELETE query
447 * FROM _referencing_relation_
448 * WHERE Fkey1 = $1 [AND Fkey2 = $2 [...]]
450 * to delete all referencing tuples.
455 * Max : Cascade with UPDATE query i create update query that
456 * updates new key values in referenced tables
460 else if (action
== 'c')
468 snprintf(sql
, sizeof(sql
), "update %s set ", relname
);
469 for (k
= 1; k
<= nkeys
; k
++)
471 int is_char_type
= 0;
474 fn
= SPI_fnumber(tupdesc
, args_temp
[k
- 1]);
475 Assert(fn
> 0); /* already checked above */
476 nv
= SPI_getvalue(newtuple
, tupdesc
, fn
);
477 type
= SPI_gettype(tupdesc
, fn
);
479 if (strcmp(type
, "text") == 0 ||
480 strcmp(type
, "varchar") == 0 ||
481 strcmp(type
, "char") == 0 ||
482 strcmp(type
, "bpchar") == 0 ||
483 strcmp(type
, "date") == 0 ||
484 strcmp(type
, "timestamp") == 0)
487 elog(DEBUG4
, "check_foreign_key Debug value %s type %s %d",
488 nv
, type
, is_char_type
);
492 * is_char_type =1 i set ' ' for define a new value
494 snprintf(sql
+ strlen(sql
), sizeof(sql
) - strlen(sql
),
496 args2
[k
], (is_char_type
> 0) ? "'" : "",
497 nv
, (is_char_type
> 0) ? "'" : "", (k
< nkeys
) ? ", " : "");
499 strcat(sql
, " where ");
503 snprintf(sql
, sizeof(sql
), "delete from %s where ", relname
);
507 * For 'S'etnull action we construct UPDATE query - UPDATE
508 * _referencing_relation_ SET Fkey1 null [, Fkey2 null [...]]
509 * WHERE Fkey1 = $1 [AND Fkey2 = $2 [...]] - to set key columns in
510 * all referencing tuples to NULL.
512 else if (action
== 's')
514 snprintf(sql
, sizeof(sql
), "update %s set ", relname
);
515 for (i
= 1; i
<= nkeys
; i
++)
517 snprintf(sql
+ strlen(sql
), sizeof(sql
) - strlen(sql
),
519 args2
[i
], (i
< nkeys
) ? ", " : "");
521 strcat(sql
, " where ");
524 /* Construct WHERE qual */
525 for (i
= 1; i
<= nkeys
; i
++)
527 snprintf(sql
+ strlen(sql
), sizeof(sql
) - strlen(sql
), "%s = $%d %s",
528 args2
[i
], i
, (i
< nkeys
) ? "and " : "");
531 /* Prepare plan for query */
532 pplan
= SPI_prepare(sql
, nkeys
, argtypes
);
535 elog(ERROR
, "check_foreign_key: SPI_prepare returned %s", SPI_result_code_string(SPI_result
));
538 * Remember that SPI_prepare places plan in current memory context
539 * - so, we have to save plan in Top memory context for later use.
541 if (SPI_keepplan(pplan
))
543 elog(ERROR
, "check_foreign_key: SPI_keepplan failed");
545 plan
->splan
[r
] = pplan
;
547 args2
+= nkeys
+ 1; /* to the next relation */
549 plan
->nplans
= nrefs
;
551 elog(DEBUG4
, "check_foreign_key Debug Query is : %s ", sql
);
556 * If UPDATE and key is not changed ...
558 if (newtuple
!= NULL
&& isequal
)
561 return PointerGetDatum(newtuple
);
565 * Ok, execute prepared plan(s).
567 for (r
= 0; r
< nrefs
; r
++)
570 * For 'R'estrict we may to execute plan for one tuple only, for other
571 * actions - for all tuples.
573 int tcount
= (action
== 'r') ? 1 : 0;
577 snprintf(ident
, sizeof(ident
), "%s$%u", trigger
->tgname
, rel
->rd_id
);
578 plan
= find_plan(ident
, &FPlans
, &nFPlans
);
579 ret
= SPI_execp(plan
->splan
[r
], kvals
, NULL
, tcount
);
580 /* we have no NULLs - so we pass ^^^^ here */
584 (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION
),
585 errmsg("SPI_execp returned %d", ret
)));
587 /* If action is 'R'estrict ... */
590 /* If there is tuple returned by SELECT then ... */
591 if (SPI_processed
> 0)
593 (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION
),
594 errmsg("\"%s\": tuple is referenced in \"%s\"",
595 trigger
->tgname
, relname
)));
599 #ifdef REFINT_VERBOSE
600 elog(NOTICE
, "%s: " UINT64_FORMAT
" tuple(s) of %s are %s",
601 trigger
->tgname
, SPI_processed
, relname
,
602 (action
== 'c') ? "deleted" : "set to null");
605 args
+= nkeys
+ 1; /* to the next relation */
610 return PointerGetDatum((newtuple
== NULL
) ? trigtuple
: newtuple
);
614 find_plan(char *ident
, EPlan
**eplan
, int *nplans
)
618 MemoryContext oldcontext
;
621 * All allocations done for the plans need to happen in a session-safe
624 oldcontext
= MemoryContextSwitchTo(TopMemoryContext
);
628 for (i
= 0; i
< *nplans
; i
++)
630 if (strcmp((*eplan
)[i
].ident
, ident
) == 0)
635 MemoryContextSwitchTo(oldcontext
);
638 *eplan
= (EPlan
*) repalloc(*eplan
, (i
+ 1) * sizeof(EPlan
));
643 newp
= *eplan
= (EPlan
*) palloc(sizeof(EPlan
));
647 newp
->ident
= pstrdup(ident
);
652 MemoryContextSwitchTo(oldcontext
);