Age Owner Branch data TLA Line data Source code
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/memutils.h"
16 : : #include "utils/rel.h"
17 : :
430 tgl@sss.pgh.pa.us 18 :CBC 1 : PG_MODULE_MAGIC_EXT(
19 : : .name = "refint",
20 : : .version = PG_VERSION
21 : : );
22 : :
23 : : typedef struct
24 : : {
25 : : char *ident;
26 : : int nplans;
27 : : SPIPlanPtr *splan;
28 : : } EPlan;
29 : :
30 : : static EPlan *FPlans = NULL;
31 : : static int nFPlans = 0;
32 : : static EPlan *PPlans = NULL;
33 : : static int nPPlans = 0;
34 : :
35 : : static EPlan *find_plan(char *ident, EPlan **eplan, int *nplans);
36 : :
37 : : /*
38 : : * check_primary_key () -- check that key in tuple being inserted/updated
39 : : * references existing tuple in "primary" table.
40 : : * Though it's called without args You have to specify referenced
41 : : * table/keys while creating trigger: key field names in triggered table,
42 : : * referenced table name, referenced key field names:
43 : : * EXECUTE PROCEDURE
44 : : * check_primary_key ('Fkey1', 'Fkey2', 'Ptable', 'Pkey1', 'Pkey2').
45 : : */
46 : :
9322 47 : 2 : PG_FUNCTION_INFO_V1(check_primary_key);
48 : :
49 : : Datum
9497 50 : 19 : check_primary_key(PG_FUNCTION_ARGS)
51 : : {
52 : 19 : TriggerData *trigdata = (TriggerData *) fcinfo->context;
53 : : Trigger *trigger; /* to get trigger name */
54 : : int nargs; /* # of args specified in CREATE TRIGGER */
55 : : char **args; /* arguments: column names and table name */
56 : : int nkeys; /* # of key columns (= nargs / 2) */
57 : : Datum *kvals; /* key values */
58 : : char *relname; /* referenced relation name */
59 : : Relation rel; /* triggered relation */
10488 vadim4o@yahoo.com 60 : 19 : HeapTuple tuple = NULL; /* tuple to return */
61 : : TupleDesc tupdesc; /* tuple description */
62 : : EPlan *plan; /* prepared plan */
8980 bruce@momjian.us 63 : 19 : Oid *argtypes = NULL; /* key types to prepare execution plan */
64 : : bool isnull; /* to know is some column NULL or not */
65 : : char ident[2 * NAMEDATALEN]; /* to identify myself */
66 : : int ret;
67 : : int i;
68 : :
69 : : #ifdef DEBUG_QUERY
70 : : elog(DEBUG4, "check_primary_key: Enter Function");
71 : : #endif
72 : :
73 : : /*
74 : : * Some checks first...
75 : : */
76 : :
77 : : /* Called by trigger manager ? */
9497 tgl@sss.pgh.pa.us 78 [ + - - + ]: 19 : if (!CALLED_AS_TRIGGER(fcinfo))
79 : : /* internal error */
9497 tgl@sss.pgh.pa.us 80 [ # # ]:UBC 0 : elog(ERROR, "check_primary_key: not fired by trigger manager");
81 : :
82 : : /* Should be called for ROW trigger */
5713 tgl@sss.pgh.pa.us 83 [ - + ]:CBC 19 : if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
84 : : /* internal error */
5713 tgl@sss.pgh.pa.us 85 [ # # ]:UBC 0 : elog(ERROR, "check_primary_key: must be fired for row");
86 : :
418 tgl@sss.pgh.pa.us 87 [ - + ]:CBC 19 : if (!TRIGGER_FIRED_AFTER(trigdata->tg_event))
88 : : /* internal error */
418 tgl@sss.pgh.pa.us 89 [ # # ]:UBC 0 : elog(ERROR, "check_primary_key: must be fired by AFTER trigger");
90 : :
91 : : /* If INSERTion then must check Tuple to being inserted */
9497 tgl@sss.pgh.pa.us 92 [ + + ]:CBC 19 : if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
93 : 16 : tuple = trigdata->tg_trigtuple;
94 : :
95 : : /* Not should be called for DELETE */
96 [ - + ]: 3 : else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
97 : : /* internal error */
7058 bruce@momjian.us 98 [ # # ]:UBC 0 : elog(ERROR, "check_primary_key: cannot process DELETE events");
99 : :
100 : : /* If UPDATE, then must check new Tuple, not old one */
101 : : else
9497 tgl@sss.pgh.pa.us 102 :CBC 3 : tuple = trigdata->tg_newtuple;
103 : :
104 : 19 : trigger = trigdata->tg_trigger;
10488 vadim4o@yahoo.com 105 : 19 : nargs = trigger->tgnargs;
106 : 19 : args = trigger->tgargs;
107 : :
9882 bruce@momjian.us 108 [ - + ]: 19 : if (nargs % 2 != 1) /* odd number of arguments! */
109 : : /* internal error */
9882 bruce@momjian.us 110 [ # # ]:UBC 0 : elog(ERROR, "check_primary_key: odd number of arguments should be specified");
111 : :
9882 bruce@momjian.us 112 :CBC 19 : nkeys = nargs / 2;
10488 vadim4o@yahoo.com 113 : 19 : relname = args[nkeys];
9497 tgl@sss.pgh.pa.us 114 : 19 : rel = trigdata->tg_relation;
10488 vadim4o@yahoo.com 115 : 19 : tupdesc = rel->rd_att;
116 : :
117 : : /* Connect to SPI manager */
628 tgl@sss.pgh.pa.us 118 : 19 : SPI_connect();
119 : :
120 : : /*
121 : : * We use SPI plan preparation feature, so allocate space to place key
122 : : * values.
123 : : */
10487 vadim4o@yahoo.com 124 : 19 : kvals = (Datum *) palloc(nkeys * sizeof(Datum));
125 : :
126 : : /*
127 : : * Construct ident string as TriggerName $ TriggeredRelationId and try to
128 : : * find prepared execution plan.
129 : : */
8671 bruce@momjian.us 130 : 19 : snprintf(ident, sizeof(ident), "%s$%u", trigger->tgname, rel->rd_id);
10488 vadim4o@yahoo.com 131 : 19 : plan = find_plan(ident, &PPlans, &nPPlans);
132 : :
133 : : /* if there is no plan then allocate argtypes for preparation */
134 [ + + ]: 19 : if (plan->nplans <= 0)
135 : 3 : argtypes = (Oid *) palloc(nkeys * sizeof(Oid));
136 : :
137 : : /* For each column in key ... */
138 [ + + ]: 50 : for (i = 0; i < nkeys; i++)
139 : : {
140 : : /* get index of column in tuple */
141 : 31 : int fnumber = SPI_fnumber(tupdesc, args[i]);
142 : :
143 : : /* Bad guys may give us un-existing column in CREATE TRIGGER */
3490 tgl@sss.pgh.pa.us 144 [ - + ]: 31 : if (fnumber <= 0)
8346 tgl@sss.pgh.pa.us 145 [ # # ]:UBC 0 : ereport(ERROR,
146 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
147 : : errmsg("there is no attribute \"%s\" in relation \"%s\"",
148 : : args[i], SPI_getrelname(rel))));
149 : :
150 : : /* Well, get binary (in internal format) value of column */
10488 vadim4o@yahoo.com 151 :CBC 31 : kvals[i] = SPI_getbinval(tuple, tupdesc, fnumber, &isnull);
152 : :
153 : : /*
154 : : * If it's NULL then nothing to do! DON'T FORGET call SPI_finish ()!
155 : : * DON'T FORGET return tuple! Executor inserts tuple you're returning!
156 : : * If you return NULL then nothing will be inserted!
157 : : */
158 [ - + ]: 31 : if (isnull)
159 : : {
10488 vadim4o@yahoo.com 160 :UBC 0 : SPI_finish();
9497 tgl@sss.pgh.pa.us 161 : 0 : return PointerGetDatum(tuple);
162 : : }
163 : :
10488 vadim4o@yahoo.com 164 [ + + ]:CBC 31 : if (plan->nplans <= 0) /* Get typeId of column */
165 : 5 : argtypes[i] = SPI_gettypeid(tupdesc, fnumber);
166 : : }
167 : :
168 : : /*
169 : : * If we have to prepare plan ...
170 : : */
171 [ + + ]: 19 : if (plan->nplans <= 0)
172 : : {
173 : : SPIPlanPtr pplan;
174 : : StringInfoData sql;
175 : :
19 nathan@postgresql.or 176 : 3 : initStringInfo(&sql);
177 : :
178 : : /*
179 : : * Construct query: SELECT 1 FROM _referenced_relation_ WHERE Pkey1 =
180 : : * $1 [AND Pkey2 = $2 [...]]
181 : : */
182 : 3 : appendStringInfo(&sql, "select 1 from %s where ", relname);
183 [ + + ]: 8 : for (i = 1; i <= nkeys; i++)
184 : : {
185 : 5 : appendStringInfo(&sql, "%s = $%d ", args[i + nkeys], i);
186 [ + + ]: 5 : if (i < nkeys)
187 : 2 : appendStringInfoString(&sql, "and ");
188 : : }
189 : :
190 : : /* Prepare plan for query */
191 : 3 : pplan = SPI_prepare(sql.data, nkeys, argtypes);
10488 vadim4o@yahoo.com 192 [ - + ]: 3 : if (pplan == NULL)
193 : : /* internal error */
3195 peter_e@gmx.net 194 [ # # ]:UBC 0 : elog(ERROR, "check_primary_key: SPI_prepare returned %s", SPI_result_code_string(SPI_result));
195 : :
196 : : /*
197 : : * Remember that SPI_prepare places plan in current memory context -
198 : : * so, we have to save plan in TopMemoryContext for later use.
199 : : */
5370 tgl@sss.pgh.pa.us 200 [ - + ]:CBC 3 : if (SPI_keepplan(pplan))
201 : : /* internal error */
5370 tgl@sss.pgh.pa.us 202 [ # # ]:UBC 0 : elog(ERROR, "check_primary_key: SPI_keepplan failed");
2334 michael@paquier.xyz 203 :CBC 3 : plan->splan = (SPIPlanPtr *) MemoryContextAlloc(TopMemoryContext,
204 : : sizeof(SPIPlanPtr));
10488 vadim4o@yahoo.com 205 : 3 : *(plan->splan) = pplan;
206 : 3 : plan->nplans = 1;
207 : :
19 nathan@postgresql.or 208 : 3 : pfree(sql.data);
209 : : }
210 : :
211 : : /*
212 : : * Ok, execute prepared plan.
213 : : */
10488 vadim4o@yahoo.com 214 : 19 : ret = SPI_execp(*(plan->splan), kvals, NULL, 1);
215 : : /* we have no NULLs - so we pass ^^^^ here */
216 : :
217 [ - + ]: 19 : if (ret < 0)
218 : : /* internal error */
10371 bruce@momjian.us 219 [ # # ]:UBC 0 : elog(ERROR, "check_primary_key: SPI_execp returned %d", ret);
220 : :
221 : : /*
222 : : * If there are no tuples returned by SELECT then ...
223 : : */
9882 bruce@momjian.us 224 [ + + ]:CBC 19 : if (SPI_processed == 0)
8346 tgl@sss.pgh.pa.us 225 [ + - ]: 3 : ereport(ERROR,
226 : : (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
227 : : errmsg("tuple references non-existent key"),
228 : : errdetail("Trigger \"%s\" found tuple referencing non-existent key in \"%s\".", trigger->tgname, relname)));
229 : :
10488 vadim4o@yahoo.com 230 : 16 : SPI_finish();
231 : :
9497 tgl@sss.pgh.pa.us 232 : 16 : return PointerGetDatum(tuple);
233 : : }
234 : :
235 : : /*
236 : : * check_foreign_key () -- check that key in tuple being deleted/updated
237 : : * is not referenced by tuples in "foreign" table(s).
238 : : * Though it's called without args You have to specify (while creating trigger):
239 : : * number of references, action to do if key referenced
240 : : * ('restrict' | 'setnull' | 'cascade'), key field names in triggered
241 : : * ("primary") table and referencing table(s)/keys:
242 : : * EXECUTE PROCEDURE
243 : : * check_foreign_key (2, 'restrict', 'Pkey1', 'Pkey2',
244 : : * 'Ftable1', 'Fkey11', 'Fkey12', 'Ftable2', 'Fkey21', 'Fkey22').
245 : : */
246 : :
9322 247 : 2 : PG_FUNCTION_INFO_V1(check_foreign_key);
248 : :
249 : : Datum
9497 250 : 6 : check_foreign_key(PG_FUNCTION_ARGS)
251 : : {
252 : 6 : TriggerData *trigdata = (TriggerData *) fcinfo->context;
253 : : Trigger *trigger; /* to get trigger name */
254 : : int nargs; /* # of args specified in CREATE TRIGGER */
255 : : char **args; /* arguments: as described above */
256 : : char **args_temp;
257 : : int nrefs; /* number of references (== # of plans) */
258 : : char action; /* 'R'estrict | 'S'etnull | 'C'ascade */
259 : : int nkeys; /* # of key columns */
260 : : Datum *kvals; /* key values */
261 : : char *relname; /* referencing relation name */
262 : : Relation rel; /* triggered relation */
3265 263 : 6 : HeapTuple trigtuple = NULL; /* tuple to being changed */
8980 bruce@momjian.us 264 : 6 : HeapTuple newtuple = NULL; /* tuple to return */
265 : : TupleDesc tupdesc; /* tuple description */
266 : : EPlan *plan; /* prepared plan(s) */
267 : 6 : Oid *argtypes = NULL; /* key types to prepare execution plan */
268 : : bool isnull; /* to know is some column NULL or not */
7532 269 : 6 : bool isequal = true; /* are keys in both tuples equal (in UPDATE) */
270 : : char ident[2 * NAMEDATALEN]; /* to identify myself */
9867 271 : 6 : int is_update = 0;
272 : : int ret;
273 : : int i,
274 : : r;
275 : :
276 : : #ifdef DEBUG_QUERY
277 : : elog(DEBUG4, "check_foreign_key: Enter Function");
278 : : #endif
279 : :
280 : : /*
281 : : * Some checks first...
282 : : */
283 : :
284 : : /* Called by trigger manager ? */
9497 tgl@sss.pgh.pa.us 285 [ + - - + ]: 6 : if (!CALLED_AS_TRIGGER(fcinfo))
286 : : /* internal error */
9497 tgl@sss.pgh.pa.us 287 [ # # ]:UBC 0 : elog(ERROR, "check_foreign_key: not fired by trigger manager");
288 : :
289 : : /* Should be called for ROW trigger */
5713 tgl@sss.pgh.pa.us 290 [ - + ]:CBC 6 : if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
291 : : /* internal error */
5713 tgl@sss.pgh.pa.us 292 [ # # ]:UBC 0 : elog(ERROR, "check_foreign_key: must be fired for row");
293 : :
294 : : /* Not should be called for INSERT */
9497 tgl@sss.pgh.pa.us 295 [ - + ]:CBC 6 : if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
296 : : /* internal error */
7058 bruce@momjian.us 297 [ # # ]:UBC 0 : elog(ERROR, "check_foreign_key: cannot process INSERT events");
298 : :
418 tgl@sss.pgh.pa.us 299 [ - + ]:CBC 6 : if (!TRIGGER_FIRED_AFTER(trigdata->tg_event))
300 : : /* internal error */
418 tgl@sss.pgh.pa.us 301 [ # # ]:UBC 0 : elog(ERROR, "check_foreign_key: must be fired by AFTER trigger");
302 : :
303 : : /* Have to check tg_trigtuple - tuple being deleted */
9497 tgl@sss.pgh.pa.us 304 :CBC 6 : trigtuple = trigdata->tg_trigtuple;
305 : :
306 : : /*
307 : : * But if this is UPDATE then we have to return tg_newtuple. Also, if key
308 : : * in tg_newtuple is the same as in tg_trigtuple then nothing to do.
309 : : */
9867 bruce@momjian.us 310 : 6 : is_update = 0;
9497 tgl@sss.pgh.pa.us 311 [ + + ]: 6 : if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
312 : : {
313 : 2 : newtuple = trigdata->tg_newtuple;
9867 bruce@momjian.us 314 : 2 : is_update = 1;
315 : : }
9497 tgl@sss.pgh.pa.us 316 : 6 : trigger = trigdata->tg_trigger;
10488 vadim4o@yahoo.com 317 : 6 : nargs = trigger->tgnargs;
318 : 6 : args = trigger->tgargs;
319 : :
320 [ - + ]: 6 : if (nargs < 5) /* nrefs, action, key, Relation, key - at
321 : : * least */
322 : : /* internal error */
10371 bruce@momjian.us 323 [ # # ]:UBC 0 : elog(ERROR, "check_foreign_key: too short %d (< 5) list of arguments", nargs);
324 : :
2869 andres@anarazel.de 325 :CBC 6 : nrefs = pg_strtoint32(args[0]);
10488 vadim4o@yahoo.com 326 [ - + ]: 6 : if (nrefs < 1)
327 : : /* internal error */
10371 bruce@momjian.us 328 [ # # ]:UBC 0 : elog(ERROR, "check_foreign_key: %d (< 1) number of references specified", nrefs);
333 jdavis@postgresql.or 329 :GNC 6 : action = pg_ascii_tolower((unsigned char) *(args[1]));
10488 vadim4o@yahoo.com 330 [ + + - + :CBC 6 : if (action != 'r' && action != 'c' && action != 's')
- - ]
331 : : /* internal error */
10371 bruce@momjian.us 332 [ # # ]:UBC 0 : elog(ERROR, "check_foreign_key: invalid action %s", args[1]);
10488 vadim4o@yahoo.com 333 :CBC 6 : nargs -= 2;
334 : 6 : args += 2;
335 : 6 : nkeys = (nargs - nrefs) / (nrefs + 1);
336 [ + - - + ]: 6 : if (nkeys <= 0 || nargs != (nrefs + nkeys * (nrefs + 1)))
337 : : /* internal error */
10371 bruce@momjian.us 338 [ # # ]:UBC 0 : elog(ERROR, "check_foreign_key: invalid number of arguments %d for %d references",
339 : : nargs + 2, nrefs);
340 : :
9497 tgl@sss.pgh.pa.us 341 :CBC 6 : rel = trigdata->tg_relation;
10488 vadim4o@yahoo.com 342 : 6 : tupdesc = rel->rd_att;
343 : :
344 : : /* Connect to SPI manager */
628 tgl@sss.pgh.pa.us 345 : 6 : SPI_connect();
346 : :
347 : : /*
348 : : * We use SPI plan preparation feature, so allocate space to place key
349 : : * values.
350 : : */
10487 vadim4o@yahoo.com 351 : 6 : kvals = (Datum *) palloc(nkeys * sizeof(Datum));
352 : :
353 : : /*
354 : : * Construct ident string as TriggerName $ TriggeredRelationId $
355 : : * OperationType and try to find prepared execution plan(s).
356 : : */
418 tgl@sss.pgh.pa.us 357 [ + + ]: 6 : snprintf(ident, sizeof(ident), "%s$%u$%c", trigger->tgname, rel->rd_id, is_update ? 'U' : 'D');
10488 vadim4o@yahoo.com 358 : 6 : plan = find_plan(ident, &FPlans, &nFPlans);
359 : :
360 : : /* if there is no plan(s) then allocate argtypes for preparation */
361 [ + + ]: 6 : if (plan->nplans <= 0)
362 : 4 : argtypes = (Oid *) palloc(nkeys * sizeof(Oid));
363 : :
364 : : /*
365 : : * else - check that we have exactly nrefs plan(s) ready
366 : : */
367 [ - + ]: 2 : else if (plan->nplans != nrefs)
368 : : /* internal error */
10371 bruce@momjian.us 369 [ # # ]:UBC 0 : elog(ERROR, "%s: check_foreign_key: # of plans changed in meantime",
370 : : trigger->tgname);
371 : :
372 : : /* For each column in key ... */
10488 vadim4o@yahoo.com 373 [ + + ]:CBC 15 : for (i = 0; i < nkeys; i++)
374 : : {
375 : : /* get index of column in tuple */
376 : 9 : int fnumber = SPI_fnumber(tupdesc, args[i]);
377 : :
378 : : /* Bad guys may give us un-existing column in CREATE TRIGGER */
3490 tgl@sss.pgh.pa.us 379 [ - + ]: 9 : if (fnumber <= 0)
8346 tgl@sss.pgh.pa.us 380 [ # # ]:UBC 0 : ereport(ERROR,
381 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
382 : : errmsg("there is no attribute \"%s\" in relation \"%s\"",
383 : : args[i], SPI_getrelname(rel))));
384 : :
385 : : /* Well, get binary (in internal format) value of column */
10488 vadim4o@yahoo.com 386 :CBC 9 : kvals[i] = SPI_getbinval(trigtuple, tupdesc, fnumber, &isnull);
387 : :
388 : : /*
389 : : * If it's NULL then nothing to do! DON'T FORGET call SPI_finish ()!
390 : : * DON'T FORGET return tuple! Executor inserts tuple you're returning!
391 : : * If you return NULL then nothing will be inserted!
392 : : */
393 [ - + ]: 9 : if (isnull)
394 : : {
10488 vadim4o@yahoo.com 395 :UBC 0 : SPI_finish();
9497 tgl@sss.pgh.pa.us 396 [ # # ]: 0 : return PointerGetDatum((newtuple == NULL) ? trigtuple : newtuple);
397 : : }
398 : :
399 : : /*
400 : : * If UPDATE then get column value from new tuple being inserted and
401 : : * compare is this the same as old one. For the moment we use string
402 : : * presentation of values...
403 : : */
10488 vadim4o@yahoo.com 404 [ + + ]:CBC 9 : if (newtuple != NULL)
405 : : {
406 : 3 : char *oldval = SPI_getvalue(trigtuple, tupdesc, fnumber);
407 : : char *newval;
408 : :
409 : : /* this shouldn't happen! SPI_ERROR_NOOUTFUNC ? */
410 [ - + ]: 3 : if (oldval == NULL)
411 : : /* internal error */
3195 peter_e@gmx.net 412 [ # # ]:UBC 0 : elog(ERROR, "check_foreign_key: SPI_getvalue returned %s", SPI_result_code_string(SPI_result));
10488 vadim4o@yahoo.com 413 :CBC 3 : newval = SPI_getvalue(newtuple, tupdesc, fnumber);
414 [ + - + + ]: 3 : if (newval == NULL || strcmp(oldval, newval) != 0)
415 : 2 : isequal = false;
416 : : }
417 : :
418 [ + + ]: 9 : if (plan->nplans <= 0) /* Get typeId of column */
419 : 6 : argtypes[i] = SPI_gettypeid(tupdesc, fnumber);
420 : : }
9867 bruce@momjian.us 421 : 6 : args_temp = args;
10488 vadim4o@yahoo.com 422 : 6 : nargs -= nkeys;
423 : 6 : args += nkeys;
424 : :
425 : : /*
426 : : * If we have to prepare plans ...
427 : : */
428 [ + + ]: 6 : if (plan->nplans <= 0)
429 : : {
430 : : SPIPlanPtr pplan;
9867 bruce@momjian.us 431 : 4 : char **args2 = args;
432 : :
2334 michael@paquier.xyz 433 : 4 : plan->splan = (SPIPlanPtr *) MemoryContextAlloc(TopMemoryContext,
434 : : nrefs * sizeof(SPIPlanPtr));
435 : :
10488 vadim4o@yahoo.com 436 [ + + ]: 10 : for (r = 0; r < nrefs; r++)
437 : : {
438 : : StringInfoData sql;
439 : :
19 nathan@postgresql.or 440 : 6 : initStringInfo(&sql);
441 : :
10488 vadim4o@yahoo.com 442 : 6 : relname = args2[0];
443 : :
444 : : /*---------
445 : : * For 'R'estrict action we construct SELECT query:
446 : : *
447 : : * SELECT 1
448 : : * FROM _referencing_relation_
449 : : * WHERE Fkey1 = $1 [AND Fkey2 = $2 [...]]
450 : : *
451 : : * to check is tuple referenced or not.
452 : : *---------
453 : : */
454 [ + + ]: 6 : if (action == 'r')
19 nathan@postgresql.or 455 : 2 : appendStringInfo(&sql, "select 1 from %s where ", relname);
456 : :
457 : : /*---------
458 : : * For 'C'ascade action we construct DELETE query
459 : : *
460 : : * DELETE
461 : : * FROM _referencing_relation_
462 : : * WHERE Fkey1 = $1 [AND Fkey2 = $2 [...]]
463 : : *
464 : : * to delete all referencing tuples.
465 : : *---------
466 : : */
467 : :
468 : : /*
469 : : * Max : Cascade with UPDATE query i create update query that
470 : : * updates new key values in referenced tables
471 : : */
472 : :
473 : :
9867 bruce@momjian.us 474 [ + - ]: 4 : else if (action == 'c')
475 : : {
476 [ + + ]: 4 : if (is_update == 1)
477 : : {
478 : : int fn;
479 : : char *nv;
480 : : int k;
481 : :
19 nathan@postgresql.or 482 : 2 : appendStringInfo(&sql, "update %s set ", relname);
9867 bruce@momjian.us 483 [ + + ]: 6 : for (k = 1; k <= nkeys; k++)
484 : : {
485 : 4 : fn = SPI_fnumber(tupdesc, args_temp[k - 1]);
3490 tgl@sss.pgh.pa.us 486 [ - + ]: 4 : Assert(fn > 0); /* already checked above */
9867 bruce@momjian.us 487 : 4 : nv = SPI_getvalue(newtuple, tupdesc, fn);
488 : :
19 nathan@postgresql.or 489 [ + - ]: 4 : appendStringInfo(&sql, " %s = %s ",
16 490 : 4 : args2[k],
491 : 4 : nv ? quote_literal_cstr(nv) : "NULL");
19 492 [ + + ]: 4 : if (k < nkeys)
493 : 2 : appendStringInfoString(&sql, ", ");
494 : : }
495 : 2 : appendStringInfoString(&sql, " where ");
496 : : }
497 : : else
498 : : /* DELETE */
499 : 2 : appendStringInfo(&sql, "delete from %s where ", relname);
500 : : }
501 : :
502 : : /*
503 : : * For 'S'etnull action we construct UPDATE query - UPDATE
504 : : * _referencing_relation_ SET Fkey1 null [, Fkey2 null [...]]
505 : : * WHERE Fkey1 = $1 [AND Fkey2 = $2 [...]] - to set key columns in
506 : : * all referencing tuples to NULL.
507 : : */
10488 vadim4o@yahoo.com 508 [ # # ]:UBC 0 : else if (action == 's')
509 : : {
19 nathan@postgresql.or 510 : 0 : appendStringInfo(&sql, "update %s set ", relname);
10488 vadim4o@yahoo.com 511 [ # # ]: 0 : for (i = 1; i <= nkeys; i++)
512 : : {
19 nathan@postgresql.or 513 : 0 : appendStringInfo(&sql, "%s = null", args2[i]);
514 [ # # ]: 0 : if (i < nkeys)
515 : 0 : appendStringInfoString(&sql, ", ");
516 : : }
517 : 0 : appendStringInfoString(&sql, " where ");
518 : : }
519 : :
520 : : /* Construct WHERE qual */
10488 vadim4o@yahoo.com 521 [ + + ]:CBC 16 : for (i = 1; i <= nkeys; i++)
522 : : {
19 nathan@postgresql.or 523 : 10 : appendStringInfo(&sql, "%s = $%d ", args2[i], i);
524 [ + + ]: 10 : if (i < nkeys)
525 : 4 : appendStringInfoString(&sql, "and ");
526 : : }
527 : :
528 : : /* Prepare plan for query */
529 : 6 : pplan = SPI_prepare(sql.data, nkeys, argtypes);
10488 vadim4o@yahoo.com 530 [ - + ]: 6 : if (pplan == NULL)
531 : : /* internal error */
3195 peter_e@gmx.net 532 [ # # ]:UBC 0 : elog(ERROR, "check_foreign_key: SPI_prepare returned %s", SPI_result_code_string(SPI_result));
533 : :
534 : : /*
535 : : * Remember that SPI_prepare places plan in current memory context
536 : : * - so, we have to save plan in Top memory context for later use.
537 : : */
5370 tgl@sss.pgh.pa.us 538 [ - + ]:CBC 6 : if (SPI_keepplan(pplan))
539 : : /* internal error */
5370 tgl@sss.pgh.pa.us 540 [ # # ]:UBC 0 : elog(ERROR, "check_foreign_key: SPI_keepplan failed");
541 : :
10488 vadim4o@yahoo.com 542 :CBC 6 : plan->splan[r] = pplan;
543 : :
544 : 6 : args2 += nkeys + 1; /* to the next relation */
545 : :
546 : : #ifdef DEBUG_QUERY
547 : : elog(DEBUG4, "check_foreign_key Debug Query is : %s ", sql.data);
548 : : #endif
549 : :
19 nathan@postgresql.or 550 : 6 : pfree(sql.data);
551 : : }
10488 vadim4o@yahoo.com 552 : 4 : plan->nplans = nrefs;
553 : : }
554 : :
555 : : /*
556 : : * If UPDATE and key is not changed ...
557 : : */
558 [ + + + + ]: 6 : if (newtuple != NULL && isequal)
559 : : {
560 : 1 : SPI_finish();
9497 tgl@sss.pgh.pa.us 561 : 1 : return PointerGetDatum(newtuple);
562 : : }
563 : :
564 : : /*
565 : : * Ok, execute prepared plan(s).
566 : : */
10488 vadim4o@yahoo.com 567 [ + + ]: 11 : for (r = 0; r < nrefs; r++)
568 : : {
569 : : /*
570 : : * For 'R'estrict we may to execute plan for one tuple only, for other
571 : : * actions - for all tuples.
572 : : */
573 : 8 : int tcount = (action == 'r') ? 1 : 0;
574 : :
575 : 8 : relname = args[0];
576 : :
577 : 8 : ret = SPI_execp(plan->splan[r], kvals, NULL, tcount);
578 : : /* we have no NULLs - so we pass ^^^^ here */
579 : :
580 [ - + ]: 7 : if (ret < 0)
8346 tgl@sss.pgh.pa.us 581 [ # # ]:UBC 0 : ereport(ERROR,
582 : : (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
583 : : errmsg("SPI_execp returned %d", ret)));
584 : :
585 : : /* If action is 'R'estrict ... */
10488 vadim4o@yahoo.com 586 [ + + ]:CBC 7 : if (action == 'r')
587 : : {
588 : : /* If there is tuple returned by SELECT then ... */
589 [ + + ]: 2 : if (SPI_processed > 0)
8346 tgl@sss.pgh.pa.us 590 [ + - ]: 1 : ereport(ERROR,
591 : : (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
592 : : errmsg("\"%s\": tuple is referenced in \"%s\"",
593 : : trigger->tgname, relname)));
594 : : }
595 : : else
596 : : {
597 : : #ifdef REFINT_VERBOSE
598 : : const char *operation;
599 : :
418 600 [ + - ]: 5 : if (action == 'c')
601 [ + + ]: 5 : operation = is_update ? "updated" : "deleted";
602 : : else
418 tgl@sss.pgh.pa.us 603 :UBC 0 : operation = "set to null";
604 : :
3731 tgl@sss.pgh.pa.us 605 [ + - ]:CBC 5 : elog(NOTICE, "%s: " UINT64_FORMAT " tuple(s) of %s are %s",
606 : : trigger->tgname, SPI_processed, relname, operation);
607 : : #endif
608 : : }
10488 vadim4o@yahoo.com 609 : 6 : args += nkeys + 1; /* to the next relation */
610 : : }
611 : :
612 : 3 : SPI_finish();
613 : :
9497 tgl@sss.pgh.pa.us 614 [ + + ]: 3 : return PointerGetDatum((newtuple == NULL) ? trigtuple : newtuple);
615 : : }
616 : :
617 : : static EPlan *
6197 bruce@momjian.us 618 : 25 : find_plan(char *ident, EPlan **eplan, int *nplans)
619 : : {
620 : : EPlan *newp;
621 : : int i;
622 : : MemoryContext oldcontext;
623 : :
624 : : /*
625 : : * All allocations done for the plans need to happen in a session-safe
626 : : * context.
627 : : */
2334 michael@paquier.xyz 628 : 25 : oldcontext = MemoryContextSwitchTo(TopMemoryContext);
629 : :
10488 vadim4o@yahoo.com 630 [ + + ]: 25 : if (*nplans > 0)
631 : : {
632 [ + + ]: 49 : for (i = 0; i < *nplans; i++)
633 : : {
634 [ + + ]: 44 : if (strcmp((*eplan)[i].ident, ident) == 0)
635 : 18 : break;
636 : : }
637 [ + + ]: 23 : if (i != *nplans)
638 : : {
2334 michael@paquier.xyz 639 : 18 : MemoryContextSwitchTo(oldcontext);
10488 vadim4o@yahoo.com 640 : 18 : return (*eplan + i);
641 : : }
2334 michael@paquier.xyz 642 : 5 : *eplan = (EPlan *) repalloc(*eplan, (i + 1) * sizeof(EPlan));
10488 vadim4o@yahoo.com 643 : 5 : newp = *eplan + i;
644 : : }
645 : : else
646 : : {
176 michael@paquier.xyz 647 :GNC 2 : newp = *eplan = palloc_object(EPlan);
10488 vadim4o@yahoo.com 648 :CBC 2 : (*nplans) = i = 0;
649 : : }
650 : :
2334 michael@paquier.xyz 651 : 7 : newp->ident = pstrdup(ident);
10488 vadim4o@yahoo.com 652 : 7 : newp->nplans = 0;
653 : 7 : newp->splan = NULL;
654 : 7 : (*nplans)++;
655 : :
2334 michael@paquier.xyz 656 : 7 : MemoryContextSwitchTo(oldcontext);
3208 peter_e@gmx.net 657 : 7 : return newp;
658 : : }
|