LCOV - code coverage report
Current view: top level - contrib/tcn - tcn.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 85.1 % 67 57
Test Date: 2026-07-03 19:57:34 Functions: 100.0 % 4 4
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 44.3 % 70 31

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  *
       3                 :             :  * tcn.c
       4                 :             :  *    triggered change notification support for PostgreSQL
       5                 :             :  *
       6                 :             :  * Portions Copyright (c) 2011-2026, PostgreSQL Global Development Group
       7                 :             :  * Portions Copyright (c) 1994, Regents of the University of California
       8                 :             :  *
       9                 :             :  *
      10                 :             :  * IDENTIFICATION
      11                 :             :  *    contrib/tcn/tcn.c
      12                 :             :  *
      13                 :             :  *-------------------------------------------------------------------------
      14                 :             :  */
      15                 :             : 
      16                 :             : #include "postgres.h"
      17                 :             : 
      18                 :             : #include "access/htup_details.h"
      19                 :             : #include "commands/async.h"
      20                 :             : #include "commands/trigger.h"
      21                 :             : #include "executor/spi.h"
      22                 :             : #include "lib/stringinfo.h"
      23                 :             : #include "utils/rel.h"
      24                 :             : #include "utils/syscache.h"
      25                 :             : 
      26                 :           2 : PG_MODULE_MAGIC_EXT(
      27                 :             :                     .name = "tcn",
      28                 :             :                     .version = PG_VERSION
      29                 :             : );
      30                 :             : 
      31                 :             : /*
      32                 :             :  * Copy from s (for source) to r (for result), wrapping with q (quote)
      33                 :             :  * characters and doubling any quote characters found.
      34                 :             :  */
      35                 :             : static void
      36                 :          15 : strcpy_quoted(StringInfo r, const char *s, const char q)
      37                 :             : {
      38         [ -  + ]:          15 :     appendStringInfoCharMacro(r, q);
      39         [ +  + ]:          70 :     while (*s)
      40                 :             :     {
      41         [ -  + ]:          55 :         if (*s == q)
      42         [ #  # ]:           0 :             appendStringInfoCharMacro(r, q);
      43         [ -  + ]:          55 :         appendStringInfoCharMacro(r, *s);
      44                 :          55 :         s++;
      45                 :             :     }
      46         [ -  + ]:          15 :     appendStringInfoCharMacro(r, q);
      47                 :          15 : }
      48                 :             : 
      49                 :             : /*
      50                 :             :  * triggered_change_notification
      51                 :             :  *
      52                 :             :  * This trigger function will send a notification of data modification with
      53                 :             :  * primary key values.  The channel will be "tcn" unless the trigger is
      54                 :             :  * created with a parameter, in which case that parameter will be used.
      55                 :             :  */
      56                 :           2 : PG_FUNCTION_INFO_V1(triggered_change_notification);
      57                 :             : 
      58                 :             : Datum
      59                 :           5 : triggered_change_notification(PG_FUNCTION_ARGS)
      60                 :             : {
      61                 :           5 :     TriggerData *trigdata = (TriggerData *) fcinfo->context;
      62                 :             :     Trigger    *trigger;
      63                 :             :     int         nargs;
      64                 :             :     HeapTuple   trigtuple;
      65                 :             :     Relation    rel;
      66                 :             :     TupleDesc   tupdesc;
      67                 :             :     char       *channel;
      68                 :             :     char        operation;
      69                 :             :     StringInfoData payload;
      70                 :             :     bool        foundPK;
      71                 :             : 
      72                 :             :     List       *indexoidlist;
      73                 :             :     ListCell   *indexoidscan;
      74                 :             : 
      75                 :           5 :     initStringInfo(&payload);
      76                 :             :     /* make sure it's called as a trigger */
      77   [ +  -  -  + ]:           5 :     if (!CALLED_AS_TRIGGER(fcinfo))
      78         [ #  # ]:           0 :         ereport(ERROR,
      79                 :             :                 (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
      80                 :             :                  errmsg("triggered_change_notification: must be called as trigger")));
      81                 :             : 
      82                 :             :     /* and that it's called after the change */
      83         [ -  + ]:           5 :     if (!TRIGGER_FIRED_AFTER(trigdata->tg_event))
      84         [ #  # ]:           0 :         ereport(ERROR,
      85                 :             :                 (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
      86                 :             :                  errmsg("triggered_change_notification: must be called after the change")));
      87                 :             : 
      88                 :             :     /* and that it's called for each row */
      89         [ -  + ]:           5 :     if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
      90         [ #  # ]:           0 :         ereport(ERROR,
      91                 :             :                 (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
      92                 :             :                  errmsg("triggered_change_notification: must be called for each row")));
      93                 :             : 
      94         [ +  + ]:           5 :     if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
      95                 :           2 :         operation = 'I';
      96         [ +  + ]:           3 :     else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
      97                 :           1 :         operation = 'U';
      98         [ +  - ]:           2 :     else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
      99                 :           2 :         operation = 'D';
     100                 :             :     else
     101                 :             :     {
     102         [ #  # ]:           0 :         elog(ERROR, "triggered_change_notification: trigger fired by unrecognized operation");
     103                 :             :         operation = 'X';        /* silence compiler warning */
     104                 :             :     }
     105                 :             : 
     106                 :           5 :     trigger = trigdata->tg_trigger;
     107                 :           5 :     nargs = trigger->tgnargs;
     108         [ -  + ]:           5 :     if (nargs > 1)
     109         [ #  # ]:           0 :         ereport(ERROR,
     110                 :             :                 (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
     111                 :             :                  errmsg("triggered_change_notification: must not be called with more than one parameter")));
     112                 :             : 
     113         [ -  + ]:           5 :     if (nargs == 0)
     114                 :           0 :         channel = "tcn";
     115                 :             :     else
     116                 :           5 :         channel = trigger->tgargs[0];
     117                 :             : 
     118                 :             :     /* get tuple data */
     119                 :           5 :     trigtuple = trigdata->tg_trigtuple;
     120                 :           5 :     rel = trigdata->tg_relation;
     121                 :           5 :     tupdesc = rel->rd_att;
     122                 :             : 
     123                 :           5 :     foundPK = false;
     124                 :             : 
     125                 :             :     /*
     126                 :             :      * Get the list of index OIDs for the table from the relcache, and look up
     127                 :             :      * each one in the pg_index syscache until we find one marked primary key
     128                 :             :      * (hopefully there isn't more than one such).
     129                 :             :      */
     130                 :           5 :     indexoidlist = RelationGetIndexList(rel);
     131                 :             : 
     132   [ +  -  +  -  :           5 :     foreach(indexoidscan, indexoidlist)
                   +  - ]
     133                 :             :     {
     134                 :           5 :         Oid         indexoid = lfirst_oid(indexoidscan);
     135                 :             :         HeapTuple   indexTuple;
     136                 :             :         Form_pg_index index;
     137                 :             : 
     138                 :           5 :         indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid));
     139         [ -  + ]:           5 :         if (!HeapTupleIsValid(indexTuple))  /* should not happen */
     140         [ #  # ]:           0 :             elog(ERROR, "cache lookup failed for index %u", indexoid);
     141                 :           5 :         index = (Form_pg_index) GETSTRUCT(indexTuple);
     142                 :             :         /* we're only interested if it is the primary key and valid */
     143   [ +  -  +  - ]:           5 :         if (index->indisprimary && index->indisvalid)
     144                 :             :         {
     145                 :           5 :             int         indnkeyatts = index->indnkeyatts;
     146                 :             : 
     147         [ +  - ]:           5 :             if (indnkeyatts > 0)
     148                 :             :             {
     149                 :             :                 int         i;
     150                 :             : 
     151                 :           5 :                 foundPK = true;
     152                 :             : 
     153                 :           5 :                 strcpy_quoted(&payload, RelationGetRelationName(rel), '"');
     154         [ -  + ]:           5 :                 appendStringInfoCharMacro(&payload, ',');
     155         [ -  + ]:           5 :                 appendStringInfoCharMacro(&payload, operation);
     156                 :             : 
     157         [ +  + ]:          10 :                 for (i = 0; i < indnkeyatts; i++)
     158                 :             :                 {
     159                 :           5 :                     int         colno = index->indkey.values[i];
     160                 :           5 :                     Form_pg_attribute attr = TupleDescAttr(tupdesc, colno - 1);
     161                 :             : 
     162         [ -  + ]:           5 :                     appendStringInfoCharMacro(&payload, ',');
     163                 :           5 :                     strcpy_quoted(&payload, NameStr(attr->attname), '"');
     164         [ -  + ]:           5 :                     appendStringInfoCharMacro(&payload, '=');
     165                 :           5 :                     strcpy_quoted(&payload, SPI_getvalue(trigtuple, tupdesc, colno), '\'');
     166                 :             :                 }
     167                 :             : 
     168                 :           5 :                 Async_Notify(channel, payload.data);
     169                 :             :             }
     170                 :           5 :             ReleaseSysCache(indexTuple);
     171                 :           5 :             break;
     172                 :             :         }
     173                 :           0 :         ReleaseSysCache(indexTuple);
     174                 :             :     }
     175                 :             : 
     176                 :           5 :     list_free(indexoidlist);
     177                 :             : 
     178         [ -  + ]:           5 :     if (!foundPK)
     179         [ #  # ]:           0 :         ereport(ERROR,
     180                 :             :                 (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
     181                 :             :                  errmsg("triggered_change_notification: must be called on a table with a primary key")));
     182                 :             : 
     183                 :           5 :     return PointerGetDatum(NULL);   /* after trigger; value doesn't matter */
     184                 :             : }
        

Generated by: LCOV version 2.0-1