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"
18 extern Datum check_primary_key(PG_FUNCTION_ARGS);
19 extern Datum check_foreign_key(PG_FUNCTION_ARGS);
29 static EPlan *FPlans = NULL;
30 static int nFPlans = 0;
31 static EPlan *PPlans = NULL;
32 static int nPPlans = 0;
34 static EPlan *find_plan(char *ident, EPlan **eplan, int *nplans);
37 * check_primary_key () -- check that key in tuple being inserted/updated
38 * references existing tuple in "primary" table.
39 * Though it's called without args You have to specify referenced
40 * table/keys while creating trigger: key field names in triggered table,
41 * referenced table name, referenced key field names:
43 * check_primary_key ('Fkey1', 'Fkey2', 'Ptable', 'Pkey1', 'Pkey2').
46 PG_FUNCTION_INFO_V1(check_primary_key);
49 check_primary_key(PG_FUNCTION_ARGS)
51 TriggerData *trigdata = (TriggerData *) fcinfo->context;
52 Trigger *trigger; /* to get trigger name */
53 int nargs; /* # of args specified in CREATE TRIGGER */
54 char **args; /* arguments: column names and table name */
55 int nkeys; /* # of key columns (= nargs / 2) */
56 Datum *kvals; /* key values */
57 char *relname; /* referenced relation name */
58 Relation rel; /* triggered relation */
59 HeapTuple tuple = NULL; /* tuple to return */
60 TupleDesc tupdesc; /* tuple description */
61 EPlan *plan; /* prepared plan */
62 Oid *argtypes = NULL; /* key types to prepare execution plan */
63 bool isnull; /* to know is some column NULL or not */
64 char ident[2 * NAMEDATALEN]; /* to identify myself */
69 elog(DEBUG4, "check_primary_key: Enter Function");
73 * Some checks first...
76 /* Called by trigger manager ? */
77 if (!CALLED_AS_TRIGGER(fcinfo))
79 elog(ERROR, "check_primary_key: not fired by trigger manager");
81 /* Should be called for ROW trigger */
82 if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
84 elog(ERROR, "check_primary_key: must be fired for row");
86 /* If INSERTion then must check Tuple to being inserted */
87 if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
88 tuple = trigdata->tg_trigtuple;
90 /* Not should be called for DELETE */
91 else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
93 elog(ERROR, "check_primary_key: cannot process DELETE events");
95 /* If UPDATion the must check new Tuple, not old one */
97 tuple = trigdata->tg_newtuple;
99 trigger = trigdata->tg_trigger;
100 nargs = trigger->tgnargs;
101 args = trigger->tgargs;
103 if (nargs % 2 != 1) /* odd number of arguments! */
105 elog(ERROR, "check_primary_key: odd number of arguments should be specified");
108 relname = args[nkeys];
109 rel = trigdata->tg_relation;
110 tupdesc = rel->rd_att;
112 /* Connect to SPI manager */
113 if ((ret = SPI_connect()) < 0)
115 elog(ERROR, "check_primary_key: SPI_connect returned %d", ret);
118 * We use SPI plan preparation feature, so allocate space to place key
121 kvals = (Datum *) palloc(nkeys * sizeof(Datum));
124 * Construct ident string as TriggerName $ TriggeredRelationId and try to
125 * find prepared execution plan.
127 snprintf(ident, sizeof(ident), "%s$%u", trigger->tgname, rel->rd_id);
128 plan = find_plan(ident, &PPlans, &nPPlans);
130 /* if there is no plan then allocate argtypes for preparation */
131 if (plan->nplans <= 0)
132 argtypes = (Oid *) palloc(nkeys * sizeof(Oid));
134 /* For each column in key ... */
135 for (i = 0; i < nkeys; i++)
137 /* get index of column in tuple */
138 int fnumber = SPI_fnumber(tupdesc, args[i]);
140 /* Bad guys may give us un-existing column in CREATE TRIGGER */
143 (errcode(ERRCODE_UNDEFINED_COLUMN),
144 errmsg("there is no attribute \"%s\" in relation \"%s\"",
145 args[i], SPI_getrelname(rel))));
147 /* Well, get binary (in internal format) value of column */
148 kvals[i] = SPI_getbinval(tuple, tupdesc, fnumber, &isnull);
151 * If it's NULL then nothing to do! DON'T FORGET call SPI_finish ()!
152 * DON'T FORGET return tuple! Executor inserts tuple you're returning!
153 * If you return NULL then nothing will be inserted!
158 return PointerGetDatum(tuple);
161 if (plan->nplans <= 0) /* Get typeId of column */
162 argtypes[i] = SPI_gettypeid(tupdesc, fnumber);
166 * If we have to prepare plan ...
168 if (plan->nplans <= 0)
174 * Construct query: SELECT 1 FROM _referenced_relation_ WHERE Pkey1 =
175 * $1 [AND Pkey2 = $2 [...]]
177 snprintf(sql, sizeof(sql), "select 1 from %s where ", relname);
178 for (i = 0; i < nkeys; i++)
180 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s = $%d %s",
181 args[i + nkeys + 1], i + 1, (i < nkeys - 1) ? "and " : "");
184 /* Prepare plan for query */
185 pplan = SPI_prepare(sql, nkeys, argtypes);
188 elog(ERROR, "check_primary_key: SPI_prepare returned %d", SPI_result);
191 * Remember that SPI_prepare places plan in current memory context -
192 * so, we have to save plan in Top memory context for latter use.
194 pplan = SPI_saveplan(pplan);
197 elog(ERROR, "check_primary_key: SPI_saveplan returned %d", SPI_result);
198 plan->splan = (SPIPlanPtr *) malloc(sizeof(SPIPlanPtr));
199 *(plan->splan) = pplan;
204 * Ok, execute prepared plan.
206 ret = SPI_execp(*(plan->splan), kvals, NULL, 1);
207 /* we have no NULLs - so we pass ^^^^ here */
211 elog(ERROR, "check_primary_key: SPI_execp returned %d", ret);
214 * If there are no tuples returned by SELECT then ...
216 if (SPI_processed == 0)
218 (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
219 errmsg("tuple references non-existent key"),
220 errdetail("Trigger \"%s\" found tuple referencing non-existent key in \"%s\".", trigger->tgname, relname)));
224 return PointerGetDatum(tuple);
228 * check_foreign_key () -- check that key in tuple being deleted/updated
229 * is not referenced by tuples in "foreign" table(s).
230 * Though it's called without args You have to specify (while creating trigger):
231 * number of references, action to do if key referenced
232 * ('restrict' | 'setnull' | 'cascade'), key field names in triggered
233 * ("primary") table and referencing table(s)/keys:
235 * check_foreign_key (2, 'restrict', 'Pkey1', 'Pkey2',
236 * 'Ftable1', 'Fkey11', 'Fkey12', 'Ftable2', 'Fkey21', 'Fkey22').
239 PG_FUNCTION_INFO_V1(check_foreign_key);
242 check_foreign_key(PG_FUNCTION_ARGS)
244 TriggerData *trigdata = (TriggerData *) fcinfo->context;
245 Trigger *trigger; /* to get trigger name */
246 int nargs; /* # of args specified in CREATE TRIGGER */
247 char **args; /* arguments: as described above */
249 int nrefs; /* number of references (== # of plans) */
250 char action; /* 'R'estrict | 'S'etnull | 'C'ascade */
251 int nkeys; /* # of key columns */
252 Datum *kvals; /* key values */
253 char *relname; /* referencing relation name */
254 Relation rel; /* triggered relation */
255 HeapTuple trigtuple = NULL; /* tuple to being changed */
256 HeapTuple newtuple = NULL; /* tuple to return */
257 TupleDesc tupdesc; /* tuple description */
258 EPlan *plan; /* prepared plan(s) */
259 Oid *argtypes = NULL; /* key types to prepare execution plan */
260 bool isnull; /* to know is some column NULL or not */
261 bool isequal = true; /* are keys in both tuples equal (in UPDATE) */
262 char ident[2 * NAMEDATALEN]; /* to identify myself */
269 elog(DEBUG4, "check_foreign_key: Enter Function");
273 * Some checks first...
276 /* Called by trigger manager ? */
277 if (!CALLED_AS_TRIGGER(fcinfo))
279 elog(ERROR, "check_foreign_key: not fired by trigger manager");
281 /* Should be called for ROW trigger */
282 if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
284 elog(ERROR, "check_foreign_key: must be fired for row");
286 /* Not should be called for INSERT */
287 if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
289 elog(ERROR, "check_foreign_key: cannot process INSERT events");
291 /* Have to check tg_trigtuple - tuple being deleted */
292 trigtuple = trigdata->tg_trigtuple;
295 * But if this is UPDATE then we have to return tg_newtuple. Also, if key
296 * in tg_newtuple is the same as in tg_trigtuple then nothing to do.
299 if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
301 newtuple = trigdata->tg_newtuple;
304 trigger = trigdata->tg_trigger;
305 nargs = trigger->tgnargs;
306 args = trigger->tgargs;
308 if (nargs < 5) /* nrefs, action, key, Relation, key - at
311 elog(ERROR, "check_foreign_key: too short %d (< 5) list of arguments", nargs);
313 nrefs = pg_atoi(args[0], sizeof(int), 0);
316 elog(ERROR, "check_foreign_key: %d (< 1) number of references specified", nrefs);
317 action = tolower((unsigned char) *(args[1]));
318 if (action != 'r' && action != 'c' && action != 's')
320 elog(ERROR, "check_foreign_key: invalid action %s", args[1]);
323 nkeys = (nargs - nrefs) / (nrefs + 1);
324 if (nkeys <= 0 || nargs != (nrefs + nkeys * (nrefs + 1)))
326 elog(ERROR, "check_foreign_key: invalid number of arguments %d for %d references",
329 rel = trigdata->tg_relation;
330 tupdesc = rel->rd_att;
332 /* Connect to SPI manager */
333 if ((ret = SPI_connect()) < 0)
335 elog(ERROR, "check_foreign_key: SPI_connect returned %d", ret);
338 * We use SPI plan preparation feature, so allocate space to place key
341 kvals = (Datum *) palloc(nkeys * sizeof(Datum));
344 * Construct ident string as TriggerName $ TriggeredRelationId and try to
345 * find prepared execution plan(s).
347 snprintf(ident, sizeof(ident), "%s$%u", trigger->tgname, rel->rd_id);
348 plan = find_plan(ident, &FPlans, &nFPlans);
350 /* if there is no plan(s) then allocate argtypes for preparation */
351 if (plan->nplans <= 0)
352 argtypes = (Oid *) palloc(nkeys * sizeof(Oid));
355 * else - check that we have exactly nrefs plan(s) ready
357 else if (plan->nplans != nrefs)
359 elog(ERROR, "%s: check_foreign_key: # of plans changed in meantime",
362 /* For each column in key ... */
363 for (i = 0; i < nkeys; i++)
365 /* get index of column in tuple */
366 int fnumber = SPI_fnumber(tupdesc, args[i]);
368 /* Bad guys may give us un-existing column in CREATE TRIGGER */
371 (errcode(ERRCODE_UNDEFINED_COLUMN),
372 errmsg("there is no attribute \"%s\" in relation \"%s\"",
373 args[i], SPI_getrelname(rel))));
375 /* Well, get binary (in internal format) value of column */
376 kvals[i] = SPI_getbinval(trigtuple, tupdesc, fnumber, &isnull);
379 * If it's NULL then nothing to do! DON'T FORGET call SPI_finish ()!
380 * DON'T FORGET return tuple! Executor inserts tuple you're returning!
381 * If you return NULL then nothing will be inserted!
386 return PointerGetDatum((newtuple == NULL) ? trigtuple : newtuple);
390 * If UPDATE then get column value from new tuple being inserted and
391 * compare is this the same as old one. For the moment we use string
392 * presentation of values...
394 if (newtuple != NULL)
396 char *oldval = SPI_getvalue(trigtuple, tupdesc, fnumber);
399 /* this shouldn't happen! SPI_ERROR_NOOUTFUNC ? */
402 elog(ERROR, "check_foreign_key: SPI_getvalue returned %d", SPI_result);
403 newval = SPI_getvalue(newtuple, tupdesc, fnumber);
404 if (newval == NULL || strcmp(oldval, newval) != 0)
408 if (plan->nplans <= 0) /* Get typeId of column */
409 argtypes[i] = SPI_gettypeid(tupdesc, fnumber);
416 * If we have to prepare plans ...
418 if (plan->nplans <= 0)
424 plan->splan = (SPIPlanPtr *) malloc(nrefs * sizeof(SPIPlanPtr));
426 for (r = 0; r < nrefs; r++)
431 * For 'R'estrict action we construct SELECT query:
434 * FROM _referencing_relation_
435 * WHERE Fkey1 = $1 [AND Fkey2 = $2 [...]]
437 * to check is tuple referenced or not.
442 snprintf(sql, sizeof(sql), "select 1 from %s where ", relname);
445 * For 'C'ascade action we construct DELETE query
448 * FROM _referencing_relation_
449 * WHERE Fkey1 = $1 [AND Fkey2 = $2 [...]]
451 * to delete all referencing tuples.
456 * Max : Cascade with UPDATE query i create update query that
457 * updates new key values in referenced tables
461 else if (action == 'c')
469 snprintf(sql, sizeof(sql), "update %s set ", relname);
470 for (k = 1; k <= nkeys; k++)
472 int is_char_type = 0;
475 fn = SPI_fnumber(tupdesc, args_temp[k - 1]);
476 nv = SPI_getvalue(newtuple, tupdesc, fn);
477 type = SPI_gettype(tupdesc, fn);
479 if ((strcmp(type, "text") && strcmp(type, "varchar") &&
480 strcmp(type, "char") && strcmp(type, "bpchar") &&
481 strcmp(type, "date") && strcmp(type, "timestamp")) == 0)
484 elog(DEBUG4, "check_foreign_key Debug value %s type %s %d",
485 nv, type, is_char_type);
489 * is_char_type =1 i set ' ' for define a new value
491 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql),
493 args2[k], (is_char_type > 0) ? "'" : "",
494 nv, (is_char_type > 0) ? "'" : "", (k < nkeys) ? ", " : "");
497 strcat(sql, " where ");
502 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 %d", 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 latter
542 pplan = SPI_saveplan(pplan);
545 elog(ERROR, "check_foreign_key: SPI_saveplan returned %d", SPI_result);
547 plan->splan[r] = pplan;
549 args2 += nkeys + 1; /* to the next relation */
551 plan->nplans = nrefs;
553 elog(DEBUG4, "check_foreign_key Debug Query is : %s ", sql);
558 * If UPDATE and key is not changed ...
560 if (newtuple != NULL && isequal)
563 return PointerGetDatum(newtuple);
567 * Ok, execute prepared plan(s).
569 for (r = 0; r < nrefs; r++)
572 * For 'R'estrict we may to execute plan for one tuple only, for other
573 * actions - for all tuples.
575 int tcount = (action == 'r') ? 1 : 0;
579 snprintf(ident, sizeof(ident), "%s$%u", trigger->tgname, rel->rd_id);
580 plan = find_plan(ident, &FPlans, &nFPlans);
581 ret = SPI_execp(plan->splan[r], kvals, NULL, tcount);
582 /* we have no NULLs - so we pass ^^^^ here */
586 (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
587 errmsg("SPI_execp returned %d", ret)));
589 /* If action is 'R'estrict ... */
592 /* If there is tuple returned by SELECT then ... */
593 if (SPI_processed > 0)
595 (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
596 errmsg("\"%s\": tuple is referenced in \"%s\"",
597 trigger->tgname, relname)));
601 #ifdef REFINT_VERBOSE
602 elog(NOTICE, "%s: %d tuple(s) of %s are %s",
603 trigger->tgname, SPI_processed, relname,
604 (action == 'c') ? "deleted" : "set to null");
607 args += nkeys + 1; /* to the next relation */
612 return PointerGetDatum((newtuple == NULL) ? trigtuple : newtuple);
616 find_plan(char *ident, EPlan **eplan, int *nplans)
623 for (i = 0; i < *nplans; i++)
625 if (strcmp((*eplan)[i].ident, ident) == 0)
630 *eplan = (EPlan *) realloc(*eplan, (i + 1) * sizeof(EPlan));
635 newp = *eplan = (EPlan *) malloc(sizeof(EPlan));
639 newp->ident = (char *) malloc(strlen(ident) + 1);
640 strcpy(newp->ident, ident);