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