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

Generated by: LCOV version 2.0-1