Header And Logo

PostgreSQL
| The world's most advanced open source database.

refint.c

Go to the documentation of this file.
00001 /*
00002  * contrib/spi/refint.c
00003  *
00004  *
00005  * refint.c --  set of functions to define referential integrity
00006  *      constraints using general triggers.
00007  */
00008 #include "postgres.h"
00009 
00010 #include <ctype.h>
00011 
00012 #include "commands/trigger.h"
00013 #include "executor/spi.h"
00014 #include "utils/builtins.h"
00015 #include "utils/rel.h"
00016 
00017 PG_MODULE_MAGIC;
00018 
00019 extern Datum check_primary_key(PG_FUNCTION_ARGS);
00020 extern Datum check_foreign_key(PG_FUNCTION_ARGS);
00021 
00022 
00023 typedef struct
00024 {
00025     char       *ident;
00026     int         nplans;
00027     SPIPlanPtr *splan;
00028 } EPlan;
00029 
00030 static EPlan *FPlans = NULL;
00031 static int  nFPlans = 0;
00032 static EPlan *PPlans = NULL;
00033 static int  nPPlans = 0;
00034 
00035 static EPlan *find_plan(char *ident, EPlan **eplan, int *nplans);
00036 
00037 /*
00038  * check_primary_key () -- check that key in tuple being inserted/updated
00039  *           references existing tuple in "primary" table.
00040  * Though it's called without args You have to specify referenced
00041  * table/keys while creating trigger:  key field names in triggered table,
00042  * referenced table name, referenced key field names:
00043  * EXECUTE PROCEDURE
00044  * check_primary_key ('Fkey1', 'Fkey2', 'Ptable', 'Pkey1', 'Pkey2').
00045  */
00046 
00047 PG_FUNCTION_INFO_V1(check_primary_key);
00048 
00049 Datum
00050 check_primary_key(PG_FUNCTION_ARGS)
00051 {
00052     TriggerData *trigdata = (TriggerData *) fcinfo->context;
00053     Trigger    *trigger;        /* to get trigger name */
00054     int         nargs;          /* # of args specified in CREATE TRIGGER */
00055     char      **args;           /* arguments: column names and table name */
00056     int         nkeys;          /* # of key columns (= nargs / 2) */
00057     Datum      *kvals;          /* key values */
00058     char       *relname;        /* referenced relation name */
00059     Relation    rel;            /* triggered relation */
00060     HeapTuple   tuple = NULL;   /* tuple to return */
00061     TupleDesc   tupdesc;        /* tuple description */
00062     EPlan      *plan;           /* prepared plan */
00063     Oid        *argtypes = NULL;    /* key types to prepare execution plan */
00064     bool        isnull;         /* to know is some column NULL or not */
00065     char        ident[2 * NAMEDATALEN]; /* to identify myself */
00066     int         ret;
00067     int         i;
00068 
00069 #ifdef  DEBUG_QUERY
00070     elog(DEBUG4, "check_primary_key: Enter Function");
00071 #endif
00072 
00073     /*
00074      * Some checks first...
00075      */
00076 
00077     /* Called by trigger manager ? */
00078     if (!CALLED_AS_TRIGGER(fcinfo))
00079         /* internal error */
00080         elog(ERROR, "check_primary_key: not fired by trigger manager");
00081 
00082     /* Should be called for ROW trigger */
00083     if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
00084         /* internal error */
00085         elog(ERROR, "check_primary_key: must be fired for row");
00086 
00087     /* If INSERTion then must check Tuple to being inserted */
00088     if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
00089         tuple = trigdata->tg_trigtuple;
00090 
00091     /* Not should be called for DELETE */
00092     else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
00093         /* internal error */
00094         elog(ERROR, "check_primary_key: cannot process DELETE events");
00095 
00096     /* If UPDATion the must check new Tuple, not old one */
00097     else
00098         tuple = trigdata->tg_newtuple;
00099 
00100     trigger = trigdata->tg_trigger;
00101     nargs = trigger->tgnargs;
00102     args = trigger->tgargs;
00103 
00104     if (nargs % 2 != 1)         /* odd number of arguments! */
00105         /* internal error */
00106         elog(ERROR, "check_primary_key: odd number of arguments should be specified");
00107 
00108     nkeys = nargs / 2;
00109     relname = args[nkeys];
00110     rel = trigdata->tg_relation;
00111     tupdesc = rel->rd_att;
00112 
00113     /* Connect to SPI manager */
00114     if ((ret = SPI_connect()) < 0)
00115         /* internal error */
00116         elog(ERROR, "check_primary_key: SPI_connect returned %d", ret);
00117 
00118     /*
00119      * We use SPI plan preparation feature, so allocate space to place key
00120      * values.
00121      */
00122     kvals = (Datum *) palloc(nkeys * sizeof(Datum));
00123 
00124     /*
00125      * Construct ident string as TriggerName $ TriggeredRelationId and try to
00126      * find prepared execution plan.
00127      */
00128     snprintf(ident, sizeof(ident), "%s$%u", trigger->tgname, rel->rd_id);
00129     plan = find_plan(ident, &PPlans, &nPPlans);
00130 
00131     /* if there is no plan then allocate argtypes for preparation */
00132     if (plan->nplans <= 0)
00133         argtypes = (Oid *) palloc(nkeys * sizeof(Oid));
00134 
00135     /* For each column in key ... */
00136     for (i = 0; i < nkeys; i++)
00137     {
00138         /* get index of column in tuple */
00139         int         fnumber = SPI_fnumber(tupdesc, args[i]);
00140 
00141         /* Bad guys may give us un-existing column in CREATE TRIGGER */
00142         if (fnumber < 0)
00143             ereport(ERROR,
00144                     (errcode(ERRCODE_UNDEFINED_COLUMN),
00145                      errmsg("there is no attribute \"%s\" in relation \"%s\"",
00146                             args[i], SPI_getrelname(rel))));
00147 
00148         /* Well, get binary (in internal format) value of column */
00149         kvals[i] = SPI_getbinval(tuple, tupdesc, fnumber, &isnull);
00150 
00151         /*
00152          * If it's NULL then nothing to do! DON'T FORGET call SPI_finish ()!
00153          * DON'T FORGET return tuple! Executor inserts tuple you're returning!
00154          * If you return NULL then nothing will be inserted!
00155          */
00156         if (isnull)
00157         {
00158             SPI_finish();
00159             return PointerGetDatum(tuple);
00160         }
00161 
00162         if (plan->nplans <= 0)  /* Get typeId of column */
00163             argtypes[i] = SPI_gettypeid(tupdesc, fnumber);
00164     }
00165 
00166     /*
00167      * If we have to prepare plan ...
00168      */
00169     if (plan->nplans <= 0)
00170     {
00171         SPIPlanPtr  pplan;
00172         char        sql[8192];
00173 
00174         /*
00175          * Construct query: SELECT 1 FROM _referenced_relation_ WHERE Pkey1 =
00176          * $1 [AND Pkey2 = $2 [...]]
00177          */
00178         snprintf(sql, sizeof(sql), "select 1 from %s where ", relname);
00179         for (i = 0; i < nkeys; i++)
00180         {
00181             snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s = $%d %s",
00182                   args[i + nkeys + 1], i + 1, (i < nkeys - 1) ? "and " : "");
00183         }
00184 
00185         /* Prepare plan for query */
00186         pplan = SPI_prepare(sql, nkeys, argtypes);
00187         if (pplan == NULL)
00188             /* internal error */
00189             elog(ERROR, "check_primary_key: SPI_prepare returned %d", SPI_result);
00190 
00191         /*
00192          * Remember that SPI_prepare places plan in current memory context -
00193          * so, we have to save plan in Top memory context for later use.
00194          */
00195         if (SPI_keepplan(pplan))
00196             /* internal error */
00197             elog(ERROR, "check_primary_key: SPI_keepplan failed");
00198         plan->splan = (SPIPlanPtr *) malloc(sizeof(SPIPlanPtr));
00199         *(plan->splan) = pplan;
00200         plan->nplans = 1;
00201     }
00202 
00203     /*
00204      * Ok, execute prepared plan.
00205      */
00206     ret = SPI_execp(*(plan->splan), kvals, NULL, 1);
00207     /* we have no NULLs - so we pass   ^^^^   here */
00208 
00209     if (ret < 0)
00210         /* internal error */
00211         elog(ERROR, "check_primary_key: SPI_execp returned %d", ret);
00212 
00213     /*
00214      * If there are no tuples returned by SELECT then ...
00215      */
00216     if (SPI_processed == 0)
00217         ereport(ERROR,
00218                 (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
00219                  errmsg("tuple references non-existent key"),
00220                  errdetail("Trigger \"%s\" found tuple referencing non-existent key in \"%s\".", trigger->tgname, relname)));
00221 
00222     SPI_finish();
00223 
00224     return PointerGetDatum(tuple);
00225 }
00226 
00227 /*
00228  * check_foreign_key () -- check that key in tuple being deleted/updated
00229  *           is not referenced by tuples in "foreign" table(s).
00230  * Though it's called without args You have to specify (while creating trigger):
00231  * number of references, action to do if key referenced
00232  * ('restrict' | 'setnull' | 'cascade'), key field names in triggered
00233  * ("primary") table and referencing table(s)/keys:
00234  * EXECUTE PROCEDURE
00235  * check_foreign_key (2, 'restrict', 'Pkey1', 'Pkey2',
00236  * 'Ftable1', 'Fkey11', 'Fkey12', 'Ftable2', 'Fkey21', 'Fkey22').
00237  */
00238 
00239 PG_FUNCTION_INFO_V1(check_foreign_key);
00240 
00241 Datum
00242 check_foreign_key(PG_FUNCTION_ARGS)
00243 {
00244     TriggerData *trigdata = (TriggerData *) fcinfo->context;
00245     Trigger    *trigger;        /* to get trigger name */
00246     int         nargs;          /* # of args specified in CREATE TRIGGER */
00247     char      **args;           /* arguments: as described above */
00248     char      **args_temp;
00249     int         nrefs;          /* number of references (== # of plans) */
00250     char        action;         /* 'R'estrict | 'S'etnull | 'C'ascade */
00251     int         nkeys;          /* # of key columns */
00252     Datum      *kvals;          /* key values */
00253     char       *relname;        /* referencing relation name */
00254     Relation    rel;            /* triggered relation */
00255     HeapTuple   trigtuple = NULL;       /* tuple to being changed */
00256     HeapTuple   newtuple = NULL;    /* tuple to return */
00257     TupleDesc   tupdesc;        /* tuple description */
00258     EPlan      *plan;           /* prepared plan(s) */
00259     Oid        *argtypes = NULL;    /* key types to prepare execution plan */
00260     bool        isnull;         /* to know is some column NULL or not */
00261     bool        isequal = true; /* are keys in both tuples equal (in UPDATE) */
00262     char        ident[2 * NAMEDATALEN]; /* to identify myself */
00263     int         is_update = 0;
00264     int         ret;
00265     int         i,
00266                 r;
00267 
00268 #ifdef DEBUG_QUERY
00269     elog(DEBUG4, "check_foreign_key: Enter Function");
00270 #endif
00271 
00272     /*
00273      * Some checks first...
00274      */
00275 
00276     /* Called by trigger manager ? */
00277     if (!CALLED_AS_TRIGGER(fcinfo))
00278         /* internal error */
00279         elog(ERROR, "check_foreign_key: not fired by trigger manager");
00280 
00281     /* Should be called for ROW trigger */
00282     if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
00283         /* internal error */
00284         elog(ERROR, "check_foreign_key: must be fired for row");
00285 
00286     /* Not should be called for INSERT */
00287     if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
00288         /* internal error */
00289         elog(ERROR, "check_foreign_key: cannot process INSERT events");
00290 
00291     /* Have to check tg_trigtuple - tuple being deleted */
00292     trigtuple = trigdata->tg_trigtuple;
00293 
00294     /*
00295      * But if this is UPDATE then we have to return tg_newtuple. Also, if key
00296      * in tg_newtuple is the same as in tg_trigtuple then nothing to do.
00297      */
00298     is_update = 0;
00299     if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
00300     {
00301         newtuple = trigdata->tg_newtuple;
00302         is_update = 1;
00303     }
00304     trigger = trigdata->tg_trigger;
00305     nargs = trigger->tgnargs;
00306     args = trigger->tgargs;
00307 
00308     if (nargs < 5)              /* nrefs, action, key, Relation, key - at
00309                                  * least */
00310         /* internal error */
00311         elog(ERROR, "check_foreign_key: too short %d (< 5) list of arguments", nargs);
00312 
00313     nrefs = pg_atoi(args[0], sizeof(int), 0);
00314     if (nrefs < 1)
00315         /* internal error */
00316         elog(ERROR, "check_foreign_key: %d (< 1) number of references specified", nrefs);
00317     action = tolower((unsigned char) *(args[1]));
00318     if (action != 'r' && action != 'c' && action != 's')
00319         /* internal error */
00320         elog(ERROR, "check_foreign_key: invalid action %s", args[1]);
00321     nargs -= 2;
00322     args += 2;
00323     nkeys = (nargs - nrefs) / (nrefs + 1);
00324     if (nkeys <= 0 || nargs != (nrefs + nkeys * (nrefs + 1)))
00325         /* internal error */
00326         elog(ERROR, "check_foreign_key: invalid number of arguments %d for %d references",
00327              nargs + 2, nrefs);
00328 
00329     rel = trigdata->tg_relation;
00330     tupdesc = rel->rd_att;
00331 
00332     /* Connect to SPI manager */
00333     if ((ret = SPI_connect()) < 0)
00334         /* internal error */
00335         elog(ERROR, "check_foreign_key: SPI_connect returned %d", ret);
00336 
00337     /*
00338      * We use SPI plan preparation feature, so allocate space to place key
00339      * values.
00340      */
00341     kvals = (Datum *) palloc(nkeys * sizeof(Datum));
00342 
00343     /*
00344      * Construct ident string as TriggerName $ TriggeredRelationId and try to
00345      * find prepared execution plan(s).
00346      */
00347     snprintf(ident, sizeof(ident), "%s$%u", trigger->tgname, rel->rd_id);
00348     plan = find_plan(ident, &FPlans, &nFPlans);
00349 
00350     /* if there is no plan(s) then allocate argtypes for preparation */
00351     if (plan->nplans <= 0)
00352         argtypes = (Oid *) palloc(nkeys * sizeof(Oid));
00353 
00354     /*
00355      * else - check that we have exactly nrefs plan(s) ready
00356      */
00357     else if (plan->nplans != nrefs)
00358         /* internal error */
00359         elog(ERROR, "%s: check_foreign_key: # of plans changed in meantime",
00360              trigger->tgname);
00361 
00362     /* For each column in key ... */
00363     for (i = 0; i < nkeys; i++)
00364     {
00365         /* get index of column in tuple */
00366         int         fnumber = SPI_fnumber(tupdesc, args[i]);
00367 
00368         /* Bad guys may give us un-existing column in CREATE TRIGGER */
00369         if (fnumber < 0)
00370             ereport(ERROR,
00371                     (errcode(ERRCODE_UNDEFINED_COLUMN),
00372                      errmsg("there is no attribute \"%s\" in relation \"%s\"",
00373                             args[i], SPI_getrelname(rel))));
00374 
00375         /* Well, get binary (in internal format) value of column */
00376         kvals[i] = SPI_getbinval(trigtuple, tupdesc, fnumber, &isnull);
00377 
00378         /*
00379          * If it's NULL then nothing to do! DON'T FORGET call SPI_finish ()!
00380          * DON'T FORGET return tuple! Executor inserts tuple you're returning!
00381          * If you return NULL then nothing will be inserted!
00382          */
00383         if (isnull)
00384         {
00385             SPI_finish();
00386             return PointerGetDatum((newtuple == NULL) ? trigtuple : newtuple);
00387         }
00388 
00389         /*
00390          * If UPDATE then get column value from new tuple being inserted and
00391          * compare is this the same as old one. For the moment we use string
00392          * presentation of values...
00393          */
00394         if (newtuple != NULL)
00395         {
00396             char       *oldval = SPI_getvalue(trigtuple, tupdesc, fnumber);
00397             char       *newval;
00398 
00399             /* this shouldn't happen! SPI_ERROR_NOOUTFUNC ? */
00400             if (oldval == NULL)
00401                 /* internal error */
00402                 elog(ERROR, "check_foreign_key: SPI_getvalue returned %d", SPI_result);
00403             newval = SPI_getvalue(newtuple, tupdesc, fnumber);
00404             if (newval == NULL || strcmp(oldval, newval) != 0)
00405                 isequal = false;
00406         }
00407 
00408         if (plan->nplans <= 0)  /* Get typeId of column */
00409             argtypes[i] = SPI_gettypeid(tupdesc, fnumber);
00410     }
00411     args_temp = args;
00412     nargs -= nkeys;
00413     args += nkeys;
00414 
00415     /*
00416      * If we have to prepare plans ...
00417      */
00418     if (plan->nplans <= 0)
00419     {
00420         SPIPlanPtr  pplan;
00421         char        sql[8192];
00422         char      **args2 = args;
00423 
00424         plan->splan = (SPIPlanPtr *) malloc(nrefs * sizeof(SPIPlanPtr));
00425 
00426         for (r = 0; r < nrefs; r++)
00427         {
00428             relname = args2[0];
00429 
00430             /*---------
00431              * For 'R'estrict action we construct SELECT query:
00432              *
00433              *  SELECT 1
00434              *  FROM _referencing_relation_
00435              *  WHERE Fkey1 = $1 [AND Fkey2 = $2 [...]]
00436              *
00437              *  to check is tuple referenced or not.
00438              *---------
00439              */
00440             if (action == 'r')
00441 
00442                 snprintf(sql, sizeof(sql), "select 1 from %s where ", relname);
00443 
00444             /*---------
00445              * For 'C'ascade action we construct DELETE query
00446              *
00447              *  DELETE
00448              *  FROM _referencing_relation_
00449              *  WHERE Fkey1 = $1 [AND Fkey2 = $2 [...]]
00450              *
00451              * to delete all referencing tuples.
00452              *---------
00453              */
00454 
00455             /*
00456              * Max : Cascade with UPDATE query i create update query that
00457              * updates new key values in referenced tables
00458              */
00459 
00460 
00461             else if (action == 'c')
00462             {
00463                 if (is_update == 1)
00464                 {
00465                     int         fn;
00466                     char       *nv;
00467                     int         k;
00468 
00469                     snprintf(sql, sizeof(sql), "update %s set ", relname);
00470                     for (k = 1; k <= nkeys; k++)
00471                     {
00472                         int         is_char_type = 0;
00473                         char       *type;
00474 
00475                         fn = SPI_fnumber(tupdesc, args_temp[k - 1]);
00476                         nv = SPI_getvalue(newtuple, tupdesc, fn);
00477                         type = SPI_gettype(tupdesc, fn);
00478 
00479                         if ((strcmp(type, "text") && strcmp(type, "varchar") &&
00480                              strcmp(type, "char") && strcmp(type, "bpchar") &&
00481                              strcmp(type, "date") && strcmp(type, "timestamp")) == 0)
00482                             is_char_type = 1;
00483 #ifdef  DEBUG_QUERY
00484                         elog(DEBUG4, "check_foreign_key Debug value %s type %s %d",
00485                              nv, type, is_char_type);
00486 #endif
00487 
00488                         /*
00489                          * is_char_type =1 i set ' ' for define a new value
00490                          */
00491                         snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql),
00492                                  " %s = %s%s%s %s ",
00493                                  args2[k], (is_char_type > 0) ? "'" : "",
00494                                  nv, (is_char_type > 0) ? "'" : "", (k < nkeys) ? ", " : "");
00495                         is_char_type = 0;
00496                     }
00497                     strcat(sql, " where ");
00498 
00499                 }
00500                 else
00501                     /* DELETE */
00502                     snprintf(sql, sizeof(sql), "delete from %s where ", relname);
00503 
00504             }
00505 
00506             /*
00507              * For 'S'etnull action we construct UPDATE query - UPDATE
00508              * _referencing_relation_ SET Fkey1 null [, Fkey2 null [...]]
00509              * WHERE Fkey1 = $1 [AND Fkey2 = $2 [...]] - to set key columns in
00510              * all referencing tuples to NULL.
00511              */
00512             else if (action == 's')
00513             {
00514                 snprintf(sql, sizeof(sql), "update %s set ", relname);
00515                 for (i = 1; i <= nkeys; i++)
00516                 {
00517                     snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql),
00518                              "%s = null%s",
00519                              args2[i], (i < nkeys) ? ", " : "");
00520                 }
00521                 strcat(sql, " where ");
00522             }
00523 
00524             /* Construct WHERE qual */
00525             for (i = 1; i <= nkeys; i++)
00526             {
00527                 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s = $%d %s",
00528                          args2[i], i, (i < nkeys) ? "and " : "");
00529             }
00530 
00531             /* Prepare plan for query */
00532             pplan = SPI_prepare(sql, nkeys, argtypes);
00533             if (pplan == NULL)
00534                 /* internal error */
00535                 elog(ERROR, "check_foreign_key: SPI_prepare returned %d", SPI_result);
00536 
00537             /*
00538              * Remember that SPI_prepare places plan in current memory context
00539              * - so, we have to save plan in Top memory context for later use.
00540              */
00541             if (SPI_keepplan(pplan))
00542                 /* internal error */
00543                 elog(ERROR, "check_foreign_key: SPI_keepplan failed");
00544 
00545             plan->splan[r] = pplan;
00546 
00547             args2 += nkeys + 1; /* to the next relation */
00548         }
00549         plan->nplans = nrefs;
00550 #ifdef  DEBUG_QUERY
00551         elog(DEBUG4, "check_foreign_key Debug Query is :  %s ", sql);
00552 #endif
00553     }
00554 
00555     /*
00556      * If UPDATE and key is not changed ...
00557      */
00558     if (newtuple != NULL && isequal)
00559     {
00560         SPI_finish();
00561         return PointerGetDatum(newtuple);
00562     }
00563 
00564     /*
00565      * Ok, execute prepared plan(s).
00566      */
00567     for (r = 0; r < nrefs; r++)
00568     {
00569         /*
00570          * For 'R'estrict we may to execute plan for one tuple only, for other
00571          * actions - for all tuples.
00572          */
00573         int         tcount = (action == 'r') ? 1 : 0;
00574 
00575         relname = args[0];
00576 
00577         snprintf(ident, sizeof(ident), "%s$%u", trigger->tgname, rel->rd_id);
00578         plan = find_plan(ident, &FPlans, &nFPlans);
00579         ret = SPI_execp(plan->splan[r], kvals, NULL, tcount);
00580         /* we have no NULLs - so we pass   ^^^^  here */
00581 
00582         if (ret < 0)
00583             ereport(ERROR,
00584                     (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
00585                      errmsg("SPI_execp returned %d", ret)));
00586 
00587         /* If action is 'R'estrict ... */
00588         if (action == 'r')
00589         {
00590             /* If there is tuple returned by SELECT then ... */
00591             if (SPI_processed > 0)
00592                 ereport(ERROR,
00593                         (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
00594                          errmsg("\"%s\": tuple is referenced in \"%s\"",
00595                                 trigger->tgname, relname)));
00596         }
00597         else
00598         {
00599 #ifdef REFINT_VERBOSE
00600             elog(NOTICE, "%s: %d tuple(s) of %s are %s",
00601                  trigger->tgname, SPI_processed, relname,
00602                  (action == 'c') ? "deleted" : "set to null");
00603 #endif
00604         }
00605         args += nkeys + 1;      /* to the next relation */
00606     }
00607 
00608     SPI_finish();
00609 
00610     return PointerGetDatum((newtuple == NULL) ? trigtuple : newtuple);
00611 }
00612 
00613 static EPlan *
00614 find_plan(char *ident, EPlan **eplan, int *nplans)
00615 {
00616     EPlan      *newp;
00617     int         i;
00618 
00619     if (*nplans > 0)
00620     {
00621         for (i = 0; i < *nplans; i++)
00622         {
00623             if (strcmp((*eplan)[i].ident, ident) == 0)
00624                 break;
00625         }
00626         if (i != *nplans)
00627             return (*eplan + i);
00628         *eplan = (EPlan *) realloc(*eplan, (i + 1) * sizeof(EPlan));
00629         newp = *eplan + i;
00630     }
00631     else
00632     {
00633         newp = *eplan = (EPlan *) malloc(sizeof(EPlan));
00634         (*nplans) = i = 0;
00635     }
00636 
00637     newp->ident = (char *) malloc(strlen(ident) + 1);
00638     strcpy(newp->ident, ident);
00639     newp->nplans = 0;
00640     newp->splan = NULL;
00641     (*nplans)++;
00642 
00643     return (newp);
00644 }