LCOV - code coverage report
Current view: top level - contrib/spi - refint.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 83.8 % 210 176
Test Date: 2026-03-03 02:14:47 Functions: 100.0 % 6 6
Legend: Lines:     hit not hit

            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            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              : 
      47            2 : PG_FUNCTION_INFO_V1(check_primary_key);
      48              : 
      49              : Datum
      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 */
      60           19 :     HeapTuple   tuple = NULL;   /* tuple to return */
      61              :     TupleDesc   tupdesc;        /* tuple description */
      62              :     EPlan      *plan;           /* prepared plan */
      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 ? */
      78           19 :     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           19 :     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           19 :     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           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 */
      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            3 :         tuple = trigdata->tg_newtuple;
     103              : 
     104           19 :     trigger = trigdata->tg_trigger;
     105           19 :     nargs = trigger->tgnargs;
     106           19 :     args = trigger->tgargs;
     107              : 
     108           19 :     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           19 :     nkeys = nargs / 2;
     113           19 :     relname = args[nkeys];
     114           19 :     rel = trigdata->tg_relation;
     115           19 :     tupdesc = rel->rd_att;
     116              : 
     117              :     /* Connect to SPI manager */
     118           19 :     SPI_connect();
     119              : 
     120              :     /*
     121              :      * We use SPI plan preparation feature, so allocate space to place key
     122              :      * values.
     123              :      */
     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              :      */
     130           19 :     snprintf(ident, sizeof(ident), "%s$%u", trigger->tgname, rel->rd_id);
     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 */
     144           31 :         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           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              :         {
     160            0 :             SPI_finish();
     161            0 :             return PointerGetDatum(tuple);
     162              :         }
     163              : 
     164           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              :          */
     180            3 :         snprintf(sql, sizeof(sql), "select 1 from %s where ", relname);
     181            8 :         for (i = 0; i < nkeys; i++)
     182              :         {
     183            5 :             snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s = $%d %s",
     184           10 :                      args[i + nkeys + 1], i + 1, (i < nkeys - 1) ? "and " : "");
     185              :         }
     186              : 
     187              :         /* Prepare plan for query */
     188            3 :         pplan = SPI_prepare(sql, nkeys, argtypes);
     189            3 :         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            3 :         if (SPI_keepplan(pplan))
     198              :             /* internal error */
     199            0 :             elog(ERROR, "check_primary_key: SPI_keepplan failed");
     200            3 :         plan->splan = (SPIPlanPtr *) MemoryContextAlloc(TopMemoryContext,
     201              :                                                         sizeof(SPIPlanPtr));
     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 */
     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           19 :     if (SPI_processed == 0)
     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              : 
     225           16 :     SPI_finish();
     226              : 
     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              : 
     242            2 : PG_FUNCTION_INFO_V1(check_foreign_key);
     243              : 
     244              : Datum
     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 */
     258            6 :     HeapTuple   trigtuple = NULL;   /* tuple to being changed */
     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 */
     264            6 :     bool        isequal = true; /* are keys in both tuples equal (in UPDATE) */
     265              :     char        ident[2 * NAMEDATALEN]; /* to identify myself */
     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 ? */
     280            6 :     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            6 :     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            6 :     if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
     291              :         /* internal error */
     292            0 :         elog(ERROR, "check_foreign_key: cannot process INSERT events");
     293              : 
     294            6 :     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            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              :      */
     305            6 :     is_update = 0;
     306            6 :     if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
     307              :     {
     308            2 :         newtuple = trigdata->tg_newtuple;
     309            2 :         is_update = 1;
     310              :     }
     311            6 :     trigger = trigdata->tg_trigger;
     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 */
     318            0 :         elog(ERROR, "check_foreign_key: too short %d (< 5) list of arguments", nargs);
     319              : 
     320            6 :     nrefs = pg_strtoint32(args[0]);
     321            6 :     if (nrefs < 1)
     322              :         /* internal error */
     323            0 :         elog(ERROR, "check_foreign_key: %d (< 1) number of references specified", nrefs);
     324            6 :     action = pg_ascii_tolower((unsigned char) *(args[1]));
     325            6 :     if (action != 'r' && action != 'c' && action != 's')
     326              :         /* internal error */
     327            0 :         elog(ERROR, "check_foreign_key: invalid action %s", args[1]);
     328            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 */
     333            0 :         elog(ERROR, "check_foreign_key: invalid number of arguments %d for %d references",
     334              :              nargs + 2, nrefs);
     335              : 
     336            6 :     rel = trigdata->tg_relation;
     337            6 :     tupdesc = rel->rd_att;
     338              : 
     339              :     /* Connect to SPI manager */
     340            6 :     SPI_connect();
     341              : 
     342              :     /*
     343              :      * We use SPI plan preparation feature, so allocate space to place key
     344              :      * values.
     345              :      */
     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              :      */
     352            6 :     snprintf(ident, sizeof(ident), "%s$%u$%c", trigger->tgname, rel->rd_id, is_update ? 'U' : 'D');
     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 */
     364            0 :         elog(ERROR, "%s: check_foreign_key: # of plans changed in meantime",
     365              :              trigger->tgname);
     366              : 
     367              :     /* For each column in key ... */
     368           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 */
     374            9 :         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            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              :         {
     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            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 */
     407            0 :                 elog(ERROR, "check_foreign_key: SPI_getvalue returned %s", SPI_result_code_string(SPI_result));
     408            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              :     }
     416            6 :     args_temp = args;
     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];
     427            4 :         char      **args2 = args;
     428              : 
     429            4 :         plan->splan = (SPIPlanPtr *) MemoryContextAlloc(TopMemoryContext,
     430              :                                                         nrefs * sizeof(SPIPlanPtr));
     431              : 
     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              : 
     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              : 
     467            4 :             else if (action == 'c')
     468              :             {
     469            4 :                 if (is_update == 1)
     470              :                 {
     471              :                     int         fn;
     472              :                     char       *nv;
     473              :                     int         k;
     474              : 
     475            2 :                     snprintf(sql, sizeof(sql), "update %s set ", relname);
     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]);
     482              :                         Assert(fn > 0); /* already checked above */
     483            4 :                         nv = SPI_getvalue(newtuple, tupdesc, fn);
     484            4 :                         type = SPI_gettype(tupdesc, fn);
     485              : 
     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)
     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              :                          */
     501            4 :                         snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql),
     502              :                                  " %s = %s%s%s %s ",
     503            4 :                                  args2[k], (is_char_type > 0) ? "'" : "",
     504              :                                  nv, (is_char_type > 0) ? "'" : "", (k < nkeys) ? ", " : "");
     505              :                     }
     506            2 :                     strcat(sql, " where ");
     507              :                 }
     508              :                 else
     509              :                     /* DELETE */
     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              :              */
     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           16 :             for (i = 1; i <= nkeys; i++)
     533              :             {
     534           10 :                 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s = $%d %s",
     535           10 :                          args2[i], i, (i < nkeys) ? "and " : "");
     536              :             }
     537              : 
     538              :             /* Prepare plan for query */
     539            6 :             pplan = SPI_prepare(sql, nkeys, argtypes);
     540            6 :             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            6 :             if (SPI_keepplan(pplan))
     549              :                 /* internal error */
     550            0 :                 elog(ERROR, "check_foreign_key: SPI_keepplan failed");
     551              : 
     552            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();
     568            1 :         return PointerGetDatum(newtuple);
     569              :     }
     570              : 
     571              :     /*
     572              :      * Ok, execute prepared plan(s).
     573              :      */
     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)
     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            7 :         if (action == 'r')
     594              :         {
     595              :             /* If there is tuple returned by SELECT then ... */
     596            2 :             if (SPI_processed > 0)
     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              : 
     607            5 :             if (action == 'c')
     608            5 :                 operation = is_update ? "updated" : "deleted";
     609              :             else
     610            0 :                 operation = "set to null";
     611              : 
     612            5 :             elog(NOTICE, "%s: " UINT64_FORMAT " tuple(s) of %s are %s",
     613              :                  trigger->tgname, SPI_processed, relname, operation);
     614              : #endif
     615              :         }
     616            6 :         args += nkeys + 1;      /* to the next relation */
     617              :     }
     618              : 
     619            3 :     SPI_finish();
     620              : 
     621            3 :     return PointerGetDatum((newtuple == NULL) ? trigtuple : newtuple);
     622              : }
     623              : 
     624              : static EPlan *
     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              :      */
     635           25 :     oldcontext = MemoryContextSwitchTo(TopMemoryContext);
     636              : 
     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              :         {
     646           18 :             MemoryContextSwitchTo(oldcontext);
     647           18 :             return (*eplan + i);
     648              :         }
     649            5 :         *eplan = (EPlan *) repalloc(*eplan, (i + 1) * sizeof(EPlan));
     650            5 :         newp = *eplan + i;
     651              :     }
     652              :     else
     653              :     {
     654            2 :         newp = *eplan = palloc_object(EPlan);
     655            2 :         (*nplans) = i = 0;
     656              :     }
     657              : 
     658            7 :     newp->ident = pstrdup(ident);
     659            7 :     newp->nplans = 0;
     660            7 :     newp->splan = NULL;
     661            7 :     (*nplans)++;
     662              : 
     663            7 :     MemoryContextSwitchTo(oldcontext);
     664            7 :     return newp;
     665              : }
        

Generated by: LCOV version 2.0-1