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