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 :
18 2 : 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 :
47 4 : PG_FUNCTION_INFO_V1(check_primary_key);
48 :
49 : Datum
50 38 : check_primary_key(PG_FUNCTION_ARGS)
51 : {
52 38 : 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 */
60 38 : HeapTuple tuple = NULL; /* tuple to return */
61 : TupleDesc tupdesc; /* tuple description */
62 : EPlan *plan; /* prepared plan */
63 38 : 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 ? */
78 38 : if (!CALLED_AS_TRIGGER(fcinfo))
79 : /* internal error */
80 0 : elog(ERROR, "check_primary_key: not fired by trigger manager");
81 :
82 : /* Should be called for ROW trigger */
83 38 : if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
84 : /* internal error */
85 0 : elog(ERROR, "check_primary_key: must be fired for row");
86 :
87 38 : if (!TRIGGER_FIRED_AFTER(trigdata->tg_event))
88 : /* internal error */
89 0 : elog(ERROR, "check_primary_key: must be fired by AFTER trigger");
90 :
91 : /* If INSERTion then must check Tuple to being inserted */
92 38 : if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
93 32 : tuple = trigdata->tg_trigtuple;
94 :
95 : /* Not should be called for DELETE */
96 6 : else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
97 : /* internal error */
98 0 : elog(ERROR, "check_primary_key: cannot process DELETE events");
99 :
100 : /* If UPDATE, then must check new Tuple, not old one */
101 : else
102 6 : tuple = trigdata->tg_newtuple;
103 :
104 38 : trigger = trigdata->tg_trigger;
105 38 : nargs = trigger->tgnargs;
106 38 : args = trigger->tgargs;
107 :
108 38 : if (nargs % 2 != 1) /* odd number of arguments! */
109 : /* internal error */
110 0 : elog(ERROR, "check_primary_key: odd number of arguments should be specified");
111 :
112 38 : nkeys = nargs / 2;
113 38 : relname = args[nkeys];
114 38 : rel = trigdata->tg_relation;
115 38 : tupdesc = rel->rd_att;
116 :
117 : /* Connect to SPI manager */
118 38 : SPI_connect();
119 :
120 : /*
121 : * We use SPI plan preparation feature, so allocate space to place key
122 : * values.
123 : */
124 38 : kvals = (Datum *) palloc(nkeys * sizeof(Datum));
125 :
126 : /*
127 : * Construct ident string as TriggerName $ TriggeredRelationId and try to
128 : * find prepared execution plan.
129 : */
130 38 : snprintf(ident, sizeof(ident), "%s$%u", trigger->tgname, rel->rd_id);
131 38 : plan = find_plan(ident, &PPlans, &nPPlans);
132 :
133 : /* if there is no plan then allocate argtypes for preparation */
134 38 : if (plan->nplans <= 0)
135 6 : argtypes = (Oid *) palloc(nkeys * sizeof(Oid));
136 :
137 : /* For each column in key ... */
138 100 : for (i = 0; i < nkeys; i++)
139 : {
140 : /* get index of column in tuple */
141 62 : int fnumber = SPI_fnumber(tupdesc, args[i]);
142 :
143 : /* Bad guys may give us un-existing column in CREATE TRIGGER */
144 62 : if (fnumber <= 0)
145 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 */
151 62 : 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 62 : if (isnull)
159 : {
160 0 : SPI_finish();
161 0 : return PointerGetDatum(tuple);
162 : }
163 :
164 62 : if (plan->nplans <= 0) /* Get typeId of column */
165 10 : argtypes[i] = SPI_gettypeid(tupdesc, fnumber);
166 : }
167 :
168 : /*
169 : * If we have to prepare plan ...
170 : */
171 38 : 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 : */
180 6 : snprintf(sql, sizeof(sql), "select 1 from %s where ", relname);
181 16 : for (i = 0; i < nkeys; i++)
182 : {
183 10 : snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s = $%d %s",
184 20 : args[i + nkeys + 1], i + 1, (i < nkeys - 1) ? "and " : "");
185 : }
186 :
187 : /* Prepare plan for query */
188 6 : pplan = SPI_prepare(sql, nkeys, argtypes);
189 6 : if (pplan == NULL)
190 : /* internal error */
191 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 : */
197 6 : if (SPI_keepplan(pplan))
198 : /* internal error */
199 0 : elog(ERROR, "check_primary_key: SPI_keepplan failed");
200 6 : plan->splan = (SPIPlanPtr *) MemoryContextAlloc(TopMemoryContext,
201 : sizeof(SPIPlanPtr));
202 6 : *(plan->splan) = pplan;
203 6 : plan->nplans = 1;
204 : }
205 :
206 : /*
207 : * Ok, execute prepared plan.
208 : */
209 38 : ret = SPI_execp(*(plan->splan), kvals, NULL, 1);
210 : /* we have no NULLs - so we pass ^^^^ here */
211 :
212 38 : if (ret < 0)
213 : /* internal error */
214 0 : elog(ERROR, "check_primary_key: SPI_execp returned %d", ret);
215 :
216 : /*
217 : * If there are no tuples returned by SELECT then ...
218 : */
219 38 : if (SPI_processed == 0)
220 6 : 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 :
225 32 : SPI_finish();
226 :
227 32 : 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 :
242 4 : PG_FUNCTION_INFO_V1(check_foreign_key);
243 :
244 : Datum
245 12 : check_foreign_key(PG_FUNCTION_ARGS)
246 : {
247 12 : 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 */
258 12 : HeapTuple trigtuple = NULL; /* tuple to being changed */
259 12 : HeapTuple newtuple = NULL; /* tuple to return */
260 : TupleDesc tupdesc; /* tuple description */
261 : EPlan *plan; /* prepared plan(s) */
262 12 : Oid *argtypes = NULL; /* key types to prepare execution plan */
263 : bool isnull; /* to know is some column NULL or not */
264 12 : bool isequal = true; /* are keys in both tuples equal (in UPDATE) */
265 : char ident[2 * NAMEDATALEN]; /* to identify myself */
266 12 : 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 ? */
280 12 : if (!CALLED_AS_TRIGGER(fcinfo))
281 : /* internal error */
282 0 : elog(ERROR, "check_foreign_key: not fired by trigger manager");
283 :
284 : /* Should be called for ROW trigger */
285 12 : if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
286 : /* internal error */
287 0 : elog(ERROR, "check_foreign_key: must be fired for row");
288 :
289 : /* Not should be called for INSERT */
290 12 : if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
291 : /* internal error */
292 0 : elog(ERROR, "check_foreign_key: cannot process INSERT events");
293 :
294 12 : if (!TRIGGER_FIRED_AFTER(trigdata->tg_event))
295 : /* internal error */
296 0 : elog(ERROR, "check_foreign_key: must be fired by AFTER trigger");
297 :
298 : /* Have to check tg_trigtuple - tuple being deleted */
299 12 : 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 : */
305 12 : is_update = 0;
306 12 : if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
307 : {
308 4 : newtuple = trigdata->tg_newtuple;
309 4 : is_update = 1;
310 : }
311 12 : trigger = trigdata->tg_trigger;
312 12 : nargs = trigger->tgnargs;
313 12 : args = trigger->tgargs;
314 :
315 12 : if (nargs < 5) /* nrefs, action, key, Relation, key - at
316 : * least */
317 : /* internal error */
318 0 : elog(ERROR, "check_foreign_key: too short %d (< 5) list of arguments", nargs);
319 :
320 12 : nrefs = pg_strtoint32(args[0]);
321 12 : if (nrefs < 1)
322 : /* internal error */
323 0 : elog(ERROR, "check_foreign_key: %d (< 1) number of references specified", nrefs);
324 12 : action = tolower((unsigned char) *(args[1]));
325 12 : if (action != 'r' && action != 'c' && action != 's')
326 : /* internal error */
327 0 : elog(ERROR, "check_foreign_key: invalid action %s", args[1]);
328 12 : nargs -= 2;
329 12 : args += 2;
330 12 : nkeys = (nargs - nrefs) / (nrefs + 1);
331 12 : if (nkeys <= 0 || nargs != (nrefs + nkeys * (nrefs + 1)))
332 : /* internal error */
333 0 : elog(ERROR, "check_foreign_key: invalid number of arguments %d for %d references",
334 : nargs + 2, nrefs);
335 :
336 12 : rel = trigdata->tg_relation;
337 12 : tupdesc = rel->rd_att;
338 :
339 : /* Connect to SPI manager */
340 12 : SPI_connect();
341 :
342 : /*
343 : * We use SPI plan preparation feature, so allocate space to place key
344 : * values.
345 : */
346 12 : 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 : */
352 12 : snprintf(ident, sizeof(ident), "%s$%u$%c", trigger->tgname, rel->rd_id, is_update ? 'U' : 'D');
353 12 : plan = find_plan(ident, &FPlans, &nFPlans);
354 :
355 : /* if there is no plan(s) then allocate argtypes for preparation */
356 12 : if (plan->nplans <= 0)
357 8 : argtypes = (Oid *) palloc(nkeys * sizeof(Oid));
358 :
359 : /*
360 : * else - check that we have exactly nrefs plan(s) ready
361 : */
362 4 : else if (plan->nplans != nrefs)
363 : /* internal error */
364 0 : elog(ERROR, "%s: check_foreign_key: # of plans changed in meantime",
365 : trigger->tgname);
366 :
367 : /* For each column in key ... */
368 30 : for (i = 0; i < nkeys; i++)
369 : {
370 : /* get index of column in tuple */
371 18 : int fnumber = SPI_fnumber(tupdesc, args[i]);
372 :
373 : /* Bad guys may give us un-existing column in CREATE TRIGGER */
374 18 : if (fnumber <= 0)
375 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 */
381 18 : 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 18 : if (isnull)
389 : {
390 0 : SPI_finish();
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 : */
399 18 : if (newtuple != NULL)
400 : {
401 6 : char *oldval = SPI_getvalue(trigtuple, tupdesc, fnumber);
402 : char *newval;
403 :
404 : /* this shouldn't happen! SPI_ERROR_NOOUTFUNC ? */
405 6 : if (oldval == NULL)
406 : /* internal error */
407 0 : elog(ERROR, "check_foreign_key: SPI_getvalue returned %s", SPI_result_code_string(SPI_result));
408 6 : newval = SPI_getvalue(newtuple, tupdesc, fnumber);
409 6 : if (newval == NULL || strcmp(oldval, newval) != 0)
410 4 : isequal = false;
411 : }
412 :
413 18 : if (plan->nplans <= 0) /* Get typeId of column */
414 12 : argtypes[i] = SPI_gettypeid(tupdesc, fnumber);
415 : }
416 12 : args_temp = args;
417 12 : nargs -= nkeys;
418 12 : args += nkeys;
419 :
420 : /*
421 : * If we have to prepare plans ...
422 : */
423 12 : if (plan->nplans <= 0)
424 : {
425 : SPIPlanPtr pplan;
426 : char sql[8192];
427 8 : char **args2 = args;
428 :
429 8 : plan->splan = (SPIPlanPtr *) MemoryContextAlloc(TopMemoryContext,
430 : nrefs * sizeof(SPIPlanPtr));
431 :
432 20 : for (r = 0; r < nrefs; r++)
433 : {
434 12 : 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 12 : if (action == 'r')
447 :
448 4 : 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 :
467 8 : else if (action == 'c')
468 : {
469 8 : if (is_update == 1)
470 : {
471 : int fn;
472 : char *nv;
473 : int k;
474 :
475 4 : snprintf(sql, sizeof(sql), "update %s set ", relname);
476 12 : for (k = 1; k <= nkeys; k++)
477 : {
478 8 : int is_char_type = 0;
479 : char *type;
480 :
481 8 : fn = SPI_fnumber(tupdesc, args_temp[k - 1]);
482 : Assert(fn > 0); /* already checked above */
483 8 : nv = SPI_getvalue(newtuple, tupdesc, fn);
484 8 : type = SPI_gettype(tupdesc, fn);
485 :
486 8 : if (strcmp(type, "text") == 0 ||
487 4 : strcmp(type, "varchar") == 0 ||
488 4 : strcmp(type, "char") == 0 ||
489 4 : strcmp(type, "bpchar") == 0 ||
490 4 : strcmp(type, "date") == 0 ||
491 4 : strcmp(type, "timestamp") == 0)
492 4 : 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 : */
501 8 : snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql),
502 : " %s = %s%s%s %s ",
503 8 : args2[k], (is_char_type > 0) ? "'" : "",
504 : nv, (is_char_type > 0) ? "'" : "", (k < nkeys) ? ", " : "");
505 : }
506 4 : strcat(sql, " where ");
507 : }
508 : else
509 : /* DELETE */
510 4 : 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 : */
519 0 : else if (action == 's')
520 : {
521 0 : snprintf(sql, sizeof(sql), "update %s set ", relname);
522 0 : for (i = 1; i <= nkeys; i++)
523 : {
524 0 : snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql),
525 : "%s = null%s",
526 0 : args2[i], (i < nkeys) ? ", " : "");
527 : }
528 0 : strcat(sql, " where ");
529 : }
530 :
531 : /* Construct WHERE qual */
532 32 : for (i = 1; i <= nkeys; i++)
533 : {
534 20 : snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s = $%d %s",
535 20 : args2[i], i, (i < nkeys) ? "and " : "");
536 : }
537 :
538 : /* Prepare plan for query */
539 12 : pplan = SPI_prepare(sql, nkeys, argtypes);
540 12 : if (pplan == NULL)
541 : /* internal error */
542 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 : */
548 12 : if (SPI_keepplan(pplan))
549 : /* internal error */
550 0 : elog(ERROR, "check_foreign_key: SPI_keepplan failed");
551 :
552 12 : plan->splan[r] = pplan;
553 :
554 12 : args2 += nkeys + 1; /* to the next relation */
555 : }
556 8 : 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 12 : if (newtuple != NULL && isequal)
566 : {
567 2 : SPI_finish();
568 2 : return PointerGetDatum(newtuple);
569 : }
570 :
571 : /*
572 : * Ok, execute prepared plan(s).
573 : */
574 22 : 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 16 : int tcount = (action == 'r') ? 1 : 0;
581 :
582 16 : relname = args[0];
583 :
584 16 : ret = SPI_execp(plan->splan[r], kvals, NULL, tcount);
585 : /* we have no NULLs - so we pass ^^^^ here */
586 :
587 14 : if (ret < 0)
588 0 : ereport(ERROR,
589 : (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
590 : errmsg("SPI_execp returned %d", ret)));
591 :
592 : /* If action is 'R'estrict ... */
593 14 : if (action == 'r')
594 : {
595 : /* If there is tuple returned by SELECT then ... */
596 4 : if (SPI_processed > 0)
597 2 : 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 :
607 10 : if (action == 'c')
608 10 : operation = is_update ? "updated" : "deleted";
609 : else
610 0 : operation = "set to null";
611 :
612 10 : elog(NOTICE, "%s: " UINT64_FORMAT " tuple(s) of %s are %s",
613 : trigger->tgname, SPI_processed, relname, operation);
614 : #endif
615 : }
616 12 : args += nkeys + 1; /* to the next relation */
617 : }
618 :
619 6 : SPI_finish();
620 :
621 6 : return PointerGetDatum((newtuple == NULL) ? trigtuple : newtuple);
622 : }
623 :
624 : static EPlan *
625 50 : 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 : */
635 50 : oldcontext = MemoryContextSwitchTo(TopMemoryContext);
636 :
637 50 : if (*nplans > 0)
638 : {
639 98 : for (i = 0; i < *nplans; i++)
640 : {
641 88 : if (strcmp((*eplan)[i].ident, ident) == 0)
642 36 : break;
643 : }
644 46 : if (i != *nplans)
645 : {
646 36 : MemoryContextSwitchTo(oldcontext);
647 36 : return (*eplan + i);
648 : }
649 10 : *eplan = (EPlan *) repalloc(*eplan, (i + 1) * sizeof(EPlan));
650 10 : newp = *eplan + i;
651 : }
652 : else
653 : {
654 4 : newp = *eplan = (EPlan *) palloc(sizeof(EPlan));
655 4 : (*nplans) = i = 0;
656 : }
657 :
658 14 : newp->ident = pstrdup(ident);
659 14 : newp->nplans = 0;
660 14 : newp->splan = NULL;
661 14 : (*nplans)++;
662 :
663 14 : MemoryContextSwitchTo(oldcontext);
664 14 : return newp;
665 : }
|