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