PostgreSQL Source Code git master
Loading...
Searching...
No Matches
refint.c
Go to the documentation of this file.
1/*
2 * contrib/spi/refint.c
3 *
4 *
5 * refint.c -- set of functions to define referential integrity
6 * constraints using general triggers.
7 */
8#include "postgres.h"
9
10#include <ctype.h>
11
12#include "commands/trigger.h"
13#include "executor/spi.h"
14#include "utils/builtins.h"
15#include "utils/rel.h"
16
18 .name = "refint",
19 .version = PG_VERSION
20);
21
22/*
23 * check_primary_key () -- check that key in tuple being inserted/updated
24 * references existing tuple in "primary" table.
25 * Though it's called without args You have to specify referenced
26 * table/keys while creating trigger: key field names in triggered table,
27 * referenced table name, referenced key field names:
28 * EXECUTE PROCEDURE
29 * check_primary_key ('Fkey1', 'Fkey2', 'Ptable', 'Pkey1', 'Pkey2').
30 */
31
33
36{
37 TriggerData *trigdata = (TriggerData *) fcinfo->context;
38 Trigger *trigger; /* to get trigger name */
39 int nargs; /* # of args specified in CREATE TRIGGER */
40 char **args; /* arguments: column names and table name */
41 int nkeys; /* # of key columns (= nargs / 2) */
42 Datum *kvals; /* key values */
43 char *relname; /* referenced relation name */
44 Relation rel; /* triggered relation */
45 HeapTuple tuple = NULL; /* tuple to return */
46 TupleDesc tupdesc; /* tuple description */
47 SPIPlanPtr pplan; /* prepared plan */
48 Oid *argtypes = NULL; /* key types to prepare execution plan */
49 bool isnull; /* to know is some column NULL or not */
50 int ret;
51 int i;
53
54#ifdef DEBUG_QUERY
55 elog(DEBUG4, "check_primary_key: Enter Function");
56#endif
57
58 /*
59 * Some checks first...
60 */
61
62 /* Called by trigger manager ? */
63 if (!CALLED_AS_TRIGGER(fcinfo))
64 /* internal error */
65 elog(ERROR, "check_primary_key: not fired by trigger manager");
66
67 /* Should be called for ROW trigger */
68 if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
69 /* internal error */
70 elog(ERROR, "check_primary_key: must be fired for row");
71
72 if (!TRIGGER_FIRED_AFTER(trigdata->tg_event))
73 /* internal error */
74 elog(ERROR, "check_primary_key: must be fired by AFTER trigger");
75
76 /* If INSERTion then must check Tuple to being inserted */
77 if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
78 tuple = trigdata->tg_trigtuple;
79
80 /* Not should be called for DELETE */
81 else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
82 /* internal error */
83 elog(ERROR, "check_primary_key: cannot process DELETE events");
84
85 /* If UPDATE, then must check new Tuple, not old one */
86 else
87 tuple = trigdata->tg_newtuple;
88
89 trigger = trigdata->tg_trigger;
90 nargs = trigger->tgnargs;
91 args = trigger->tgargs;
92
93 if (nargs % 2 != 1) /* odd number of arguments! */
94 /* internal error */
95 elog(ERROR, "check_primary_key: odd number of arguments should be specified");
96
97 nkeys = nargs / 2;
98 relname = args[nkeys];
99 rel = trigdata->tg_relation;
100 tupdesc = rel->rd_att;
101
102 /* Connect to SPI manager */
103 SPI_connect();
104
105 /*
106 * We use SPI plan preparation feature, so allocate space to place key
107 * values.
108 */
109 kvals = (Datum *) palloc(nkeys * sizeof(Datum));
110
111 /* allocate argtypes for preparation */
112 argtypes = (Oid *) palloc(nkeys * sizeof(Oid));
113
114 /* For each column in key ... */
115 for (i = 0; i < nkeys; i++)
116 {
117 /* get index of column in tuple */
118 int fnumber = SPI_fnumber(tupdesc, args[i]);
119
120 /* Bad guys may give us un-existing column in CREATE TRIGGER */
121 if (fnumber <= 0)
124 errmsg("there is no attribute \"%s\" in relation \"%s\"",
125 args[i], SPI_getrelname(rel))));
126
127 /* Well, get binary (in internal format) value of column */
128 kvals[i] = SPI_getbinval(tuple, tupdesc, fnumber, &isnull);
129
130 /*
131 * If it's NULL then nothing to do! DON'T FORGET call SPI_finish ()!
132 * DON'T FORGET return tuple! Executor inserts tuple you're returning!
133 * If you return NULL then nothing will be inserted!
134 */
135 if (isnull)
136 {
137 SPI_finish();
138 return PointerGetDatum(tuple);
139 }
140
141 /* Get typeId of column */
142 argtypes[i] = SPI_gettypeid(tupdesc, fnumber);
143 }
144
145 initStringInfo(&sql);
146
147 /*
148 * Construct query: SELECT 1 FROM _referenced_relation_ WHERE Pkey1 = $1
149 * [AND Pkey2 = $2 [...]]
150 */
151 appendStringInfo(&sql, "select 1 from %s where ", relname);
152 for (i = 1; i <= nkeys; i++)
153 {
154 appendStringInfo(&sql, "%s = $%d ", args[i + nkeys], i);
155 if (i < nkeys)
156 appendStringInfoString(&sql, "and ");
157 }
158
159 /* Prepare plan for query */
160 pplan = SPI_prepare(sql.data, nkeys, argtypes);
161 if (pplan == NULL)
162 /* internal error */
163 elog(ERROR, "check_primary_key: SPI_prepare returned %s", SPI_result_code_string(SPI_result));
164
165 pfree(sql.data);
166
167 /*
168 * Ok, execute prepared plan.
169 */
170 ret = SPI_execp(pplan, kvals, NULL, 1);
171 /* we have no NULLs - so we pass ^^^^ here */
172
173 if (ret < 0)
174 /* internal error */
175 elog(ERROR, "check_primary_key: SPI_execp returned %d", ret);
176
177 /*
178 * If there are no tuples returned by SELECT then ...
179 */
180 if (SPI_processed == 0)
183 errmsg("tuple references non-existent key"),
184 errdetail("Trigger \"%s\" found tuple referencing non-existent key in \"%s\".", trigger->tgname, relname)));
185
186 SPI_finish();
187
188 return PointerGetDatum(tuple);
189}
190
191/*
192 * check_foreign_key () -- check that key in tuple being deleted/updated
193 * is not referenced by tuples in "foreign" table(s).
194 * Though it's called without args You have to specify (while creating trigger):
195 * number of references, action to do if key referenced
196 * ('restrict' | 'setnull' | 'cascade'), key field names in triggered
197 * ("primary") table and referencing table(s)/keys:
198 * EXECUTE PROCEDURE
199 * check_foreign_key (2, 'restrict', 'Pkey1', 'Pkey2',
200 * 'Ftable1', 'Fkey11', 'Fkey12', 'Ftable2', 'Fkey21', 'Fkey22').
201 */
202
204
205Datum
207{
208 TriggerData *trigdata = (TriggerData *) fcinfo->context;
209 Trigger *trigger; /* to get trigger name */
210 int nargs; /* # of args specified in CREATE TRIGGER */
211 char **args; /* arguments: as described above */
212 char **args_temp;
213 int nrefs; /* number of references (== # of plans) */
214 char action; /* 'R'estrict | 'S'etnull | 'C'ascade */
215 int nkeys; /* # of key columns */
216 Datum *kvals; /* key values */
217 char *relname; /* referencing relation name */
218 Relation rel; /* triggered relation */
219 HeapTuple trigtuple = NULL; /* tuple to being changed */
220 HeapTuple newtuple = NULL; /* tuple to return */
221 TupleDesc tupdesc; /* tuple description */
222 SPIPlanPtr *splan; /* prepared plan(s) */
223 Oid *argtypes = NULL; /* key types to prepare execution plan */
224 bool isnull; /* to know is some column NULL or not */
225 bool isequal = true; /* are keys in both tuples equal (in UPDATE) */
226 int is_update = 0;
227 int ret;
228 int i,
229 r;
230 char **args2;
231
232#ifdef DEBUG_QUERY
233 elog(DEBUG4, "check_foreign_key: Enter Function");
234#endif
235
236 /*
237 * Some checks first...
238 */
239
240 /* Called by trigger manager ? */
241 if (!CALLED_AS_TRIGGER(fcinfo))
242 /* internal error */
243 elog(ERROR, "check_foreign_key: not fired by trigger manager");
244
245 /* Should be called for ROW trigger */
246 if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
247 /* internal error */
248 elog(ERROR, "check_foreign_key: must be fired for row");
249
250 /* Not should be called for INSERT */
251 if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
252 /* internal error */
253 elog(ERROR, "check_foreign_key: cannot process INSERT events");
254
255 if (!TRIGGER_FIRED_AFTER(trigdata->tg_event))
256 /* internal error */
257 elog(ERROR, "check_foreign_key: must be fired by AFTER trigger");
258
259 /* Have to check tg_trigtuple - tuple being deleted */
260 trigtuple = trigdata->tg_trigtuple;
261
262 /*
263 * But if this is UPDATE then we have to return tg_newtuple. Also, if key
264 * in tg_newtuple is the same as in tg_trigtuple then nothing to do.
265 */
266 is_update = 0;
267 if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
268 {
269 newtuple = trigdata->tg_newtuple;
270 is_update = 1;
271 }
272 trigger = trigdata->tg_trigger;
273 nargs = trigger->tgnargs;
274 args = trigger->tgargs;
275
276 if (nargs < 5) /* nrefs, action, key, Relation, key - at
277 * least */
278 /* internal error */
279 elog(ERROR, "check_foreign_key: too short %d (< 5) list of arguments", nargs);
280
281 nrefs = pg_strtoint32(args[0]);
282 if (nrefs < 1)
283 /* internal error */
284 elog(ERROR, "check_foreign_key: %d (< 1) number of references specified", nrefs);
285 action = pg_ascii_tolower((unsigned char) *(args[1]));
286 if (action != 'r' && action != 'c' && action != 's')
287 /* internal error */
288 elog(ERROR, "check_foreign_key: invalid action %s", args[1]);
289 nargs -= 2;
290 args += 2;
291 nkeys = (nargs - nrefs) / (nrefs + 1);
292 if (nkeys <= 0 || nargs != (nrefs + nkeys * (nrefs + 1)))
293 /* internal error */
294 elog(ERROR, "check_foreign_key: invalid number of arguments %d for %d references",
295 nargs + 2, nrefs);
296
297 rel = trigdata->tg_relation;
298 tupdesc = rel->rd_att;
299
300 /* Connect to SPI manager */
301 SPI_connect();
302
303 /*
304 * We use SPI plan preparation feature, so allocate space to place key
305 * values.
306 */
307 kvals = (Datum *) palloc(nkeys * sizeof(Datum));
308
309 /* allocate argtypes for preparation */
310 argtypes = (Oid *) palloc(nkeys * sizeof(Oid));
311
312 /* For each column in key ... */
313 for (i = 0; i < nkeys; i++)
314 {
315 /* get index of column in tuple */
316 int fnumber = SPI_fnumber(tupdesc, args[i]);
317
318 /* Bad guys may give us un-existing column in CREATE TRIGGER */
319 if (fnumber <= 0)
322 errmsg("there is no attribute \"%s\" in relation \"%s\"",
323 args[i], SPI_getrelname(rel))));
324
325 /* Well, get binary (in internal format) value of column */
326 kvals[i] = SPI_getbinval(trigtuple, tupdesc, fnumber, &isnull);
327
328 /*
329 * If it's NULL then nothing to do! DON'T FORGET call SPI_finish ()!
330 * DON'T FORGET return tuple! Executor inserts tuple you're returning!
331 * If you return NULL then nothing will be inserted!
332 */
333 if (isnull)
334 {
335 SPI_finish();
336 return PointerGetDatum((newtuple == NULL) ? trigtuple : newtuple);
337 }
338
339 /*
340 * If UPDATE then get column value from new tuple being inserted and
341 * compare is this the same as old one. For the moment we use string
342 * presentation of values...
343 */
344 if (newtuple != NULL)
345 {
346 char *oldval = SPI_getvalue(trigtuple, tupdesc, fnumber);
347 char *newval;
348
349 /* this shouldn't happen! SPI_ERROR_NOOUTFUNC ? */
350 if (oldval == NULL)
351 /* internal error */
352 elog(ERROR, "check_foreign_key: SPI_getvalue returned %s", SPI_result_code_string(SPI_result));
353 newval = SPI_getvalue(newtuple, tupdesc, fnumber);
354 if (newval == NULL || strcmp(oldval, newval) != 0)
355 isequal = false;
356 }
357
358 /* Get typeId of column */
359 argtypes[i] = SPI_gettypeid(tupdesc, fnumber);
360 }
361 args_temp = args;
362 nargs -= nkeys;
363 args += nkeys;
364 args2 = args;
365
366 splan = (SPIPlanPtr *) palloc(nrefs * sizeof(SPIPlanPtr));
367
368 for (r = 0; r < nrefs; r++)
369 {
370 StringInfoData sql;
372
373 initStringInfo(&sql);
374
375 relname = args2[0];
376
377 /*---------
378 * For 'R'estrict action we construct SELECT query:
379 *
380 * SELECT 1
381 * FROM _referencing_relation_
382 * WHERE Fkey1 = $1 [AND Fkey2 = $2 [...]]
383 *
384 * to check is tuple referenced or not.
385 *---------
386 */
387 if (action == 'r')
388 appendStringInfo(&sql, "select 1 from %s where ", relname);
389
390 /*---------
391 * For 'C'ascade action we construct DELETE query
392 *
393 * DELETE
394 * FROM _referencing_relation_
395 * WHERE Fkey1 = $1 [AND Fkey2 = $2 [...]]
396 *
397 * to delete all referencing tuples.
398 *---------
399 */
400
401 /*
402 * Max : Cascade with UPDATE query i create update query that updates
403 * new key values in referenced tables
404 */
405
406
407 else if (action == 'c')
408 {
409 if (is_update == 1)
410 {
411 int fn;
412 char *nv;
413 int k;
414
415 appendStringInfo(&sql, "update %s set ", relname);
416 for (k = 1; k <= nkeys; k++)
417 {
418 fn = SPI_fnumber(tupdesc, args_temp[k - 1]);
419 Assert(fn > 0); /* already checked above */
420 nv = SPI_getvalue(newtuple, tupdesc, fn);
421
422 appendStringInfo(&sql, " %s = %s ",
423 args2[k],
424 nv ? quote_literal_cstr(nv) : "NULL");
425 if (k < nkeys)
426 appendStringInfoString(&sql, ", ");
427 }
428 appendStringInfoString(&sql, " where ");
429 }
430 else
431 /* DELETE */
432 appendStringInfo(&sql, "delete from %s where ", relname);
433 }
434
435 /*
436 * For 'S'etnull action we construct UPDATE query - UPDATE
437 * _referencing_relation_ SET Fkey1 null [, Fkey2 null [...]] WHERE
438 * Fkey1 = $1 [AND Fkey2 = $2 [...]] - to set key columns in all
439 * referencing tuples to NULL.
440 */
441 else if (action == 's')
442 {
443 appendStringInfo(&sql, "update %s set ", relname);
444 for (i = 1; i <= nkeys; i++)
445 {
446 appendStringInfo(&sql, "%s = null", args2[i]);
447 if (i < nkeys)
448 appendStringInfoString(&sql, ", ");
449 }
450 appendStringInfoString(&sql, " where ");
451 }
452
453 /* Construct WHERE qual */
454 for (i = 1; i <= nkeys; i++)
455 {
456 appendStringInfo(&sql, "%s = $%d ", args2[i], i);
457 if (i < nkeys)
458 appendStringInfoString(&sql, "and ");
459 }
460
461 /* Prepare plan for query */
462 pplan = SPI_prepare(sql.data, nkeys, argtypes);
463 if (pplan == NULL)
464 /* internal error */
465 elog(ERROR, "check_foreign_key: SPI_prepare returned %s", SPI_result_code_string(SPI_result));
466
467 splan[r] = pplan;
468
469 args2 += nkeys + 1; /* to the next relation */
470
471#ifdef DEBUG_QUERY
472 elog(DEBUG4, "check_foreign_key Debug Query is : %s ", sql.data);
473#endif
474
475 pfree(sql.data);
476 }
477
478 /*
479 * If UPDATE and key is not changed ...
480 */
481 if (newtuple != NULL && isequal)
482 {
483 SPI_finish();
484 return PointerGetDatum(newtuple);
485 }
486
487 /*
488 * Ok, execute prepared plan(s).
489 */
490 for (r = 0; r < nrefs; r++)
491 {
492 /*
493 * For 'R'estrict we may to execute plan for one tuple only, for other
494 * actions - for all tuples.
495 */
496 int tcount = (action == 'r') ? 1 : 0;
497
498 relname = args[0];
499
500 ret = SPI_execp(splan[r], kvals, NULL, tcount);
501 /* we have no NULLs - so we pass ^^^^ here */
502
503 if (ret < 0)
506 errmsg("SPI_execp returned %d", ret)));
507
508 /* If action is 'R'estrict ... */
509 if (action == 'r')
510 {
511 /* If there is tuple returned by SELECT then ... */
512 if (SPI_processed > 0)
515 errmsg("\"%s\": tuple is referenced in \"%s\"",
516 trigger->tgname, relname)));
517 }
518 else
519 {
520#ifdef REFINT_VERBOSE
521 const char *operation;
522
523 if (action == 'c')
524 operation = is_update ? "updated" : "deleted";
525 else
526 operation = "set to null";
527
528 elog(NOTICE, "%s: " UINT64_FORMAT " tuple(s) of %s are %s",
530#endif
531 }
532 args += nkeys + 1; /* to the next relation */
533 }
534
535 SPI_finish();
536
537 return PointerGetDatum((newtuple == NULL) ? trigtuple : newtuple);
538}
#define Assert(condition)
Definition c.h:943
#define UINT64_FORMAT
Definition c.h:635
static DataChecksumsWorkerOperation operation
int errcode(int sqlerrcode)
Definition elog.c:875
int errdetail(const char *fmt,...) pg_attribute_printf(1
#define ERROR
Definition elog.h:40
#define elog(elevel,...)
Definition elog.h:228
#define NOTICE
Definition elog.h:36
#define ereport(elevel,...)
Definition elog.h:152
#define DEBUG4
Definition elog.h:28
#define PG_MODULE_MAGIC_EXT(...)
Definition fmgr.h:540
#define PG_FUNCTION_INFO_V1(funcname)
Definition fmgr.h:417
#define PG_FUNCTION_ARGS
Definition fmgr.h:193
#define newval
int i
Definition isn.c:77
void pfree(void *pointer)
Definition mcxt.c:1619
void * palloc(Size size)
Definition mcxt.c:1390
int32 pg_strtoint32(const char *s)
Definition numutils.c:382
static char * errmsg
NameData relname
Definition pg_class.h:40
static unsigned char pg_ascii_tolower(unsigned char ch)
Definition port.h:189
uint64_t Datum
Definition postgres.h:70
#define PointerGetDatum(X)
Definition postgres.h:354
unsigned int Oid
static int fb(int x)
char * quote_literal_cstr(const char *rawstr)
Definition quote.c:101
Datum check_foreign_key(PG_FUNCTION_ARGS)
Definition refint.c:206
Datum check_primary_key(PG_FUNCTION_ARGS)
Definition refint.c:35
int SPI_fnumber(TupleDesc tupdesc, const char *fname)
Definition spi.c:1176
uint64 SPI_processed
Definition spi.c:45
Oid SPI_gettypeid(TupleDesc tupdesc, int fnumber)
Definition spi.c:1309
const char * SPI_result_code_string(int code)
Definition spi.c:1973
int SPI_connect(void)
Definition spi.c:95
int SPI_result
Definition spi.c:47
int SPI_finish(void)
Definition spi.c:183
SPIPlanPtr SPI_prepare(const char *src, int nargs, Oid *argtypes)
Definition spi.c:861
int SPI_execp(SPIPlanPtr plan, Datum *Values, const char *Nulls, long tcount)
Definition spi.c:705
char * SPI_getvalue(HeapTuple tuple, TupleDesc tupdesc, int fnumber)
Definition spi.c:1221
Datum SPI_getbinval(HeapTuple tuple, TupleDesc tupdesc, int fnumber, bool *isnull)
Definition spi.c:1253
char * SPI_getrelname(Relation rel)
Definition spi.c:1327
void appendStringInfo(StringInfo str, const char *fmt,...)
Definition stringinfo.c:145
void appendStringInfoString(StringInfo str, const char *s)
Definition stringinfo.c:230
void initStringInfo(StringInfo str)
Definition stringinfo.c:97
TupleDesc rd_att
Definition rel.h:112
Relation tg_relation
Definition trigger.h:35
TriggerEvent tg_event
Definition trigger.h:34
HeapTuple tg_newtuple
Definition trigger.h:37
Trigger * tg_trigger
Definition trigger.h:38
HeapTuple tg_trigtuple
Definition trigger.h:36
int16 tgnargs
Definition reltrigger.h:38
static void * fn(void *arg)
#define TRIGGER_FIRED_BY_DELETE(event)
Definition trigger.h:115
#define CALLED_AS_TRIGGER(fcinfo)
Definition trigger.h:26
#define TRIGGER_FIRED_FOR_ROW(event)
Definition trigger.h:124
#define TRIGGER_FIRED_AFTER(event)
Definition trigger.h:133
#define TRIGGER_FIRED_BY_INSERT(event)
Definition trigger.h:112
#define TRIGGER_FIRED_BY_UPDATE(event)
Definition trigger.h:118
const char * name