LCOV - code coverage report
Current view: top level - contrib/tcn - tcn.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 57 67 85.1 %
Date: 2025-04-01 16:15:31 Functions: 4 4 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * tcn.c
       4             :  *    triggered change notification support for PostgreSQL
       5             :  *
       6             :  * Portions Copyright (c) 2011-2025, 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           4 : 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          30 : strcpy_quoted(StringInfo r, const char *s, const char q)
      37             : {
      38          30 :     appendStringInfoCharMacro(r, q);
      39         140 :     while (*s)
      40             :     {
      41         110 :         if (*s == q)
      42           0 :             appendStringInfoCharMacro(r, q);
      43         110 :         appendStringInfoCharMacro(r, *s);
      44         110 :         s++;
      45             :     }
      46          30 :     appendStringInfoCharMacro(r, q);
      47          30 : }
      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           4 : PG_FUNCTION_INFO_V1(triggered_change_notification);
      57             : 
      58             : Datum
      59          10 : triggered_change_notification(PG_FUNCTION_ARGS)
      60             : {
      61          10 :     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          10 :     StringInfo  payload = makeStringInfo();
      70             :     bool        foundPK;
      71             : 
      72             :     List       *indexoidlist;
      73             :     ListCell   *indexoidscan;
      74             : 
      75             :     /* make sure it's called as a trigger */
      76          10 :     if (!CALLED_AS_TRIGGER(fcinfo))
      77           0 :         ereport(ERROR,
      78             :                 (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
      79             :                  errmsg("triggered_change_notification: must be called as trigger")));
      80             : 
      81             :     /* and that it's called after the change */
      82          10 :     if (!TRIGGER_FIRED_AFTER(trigdata->tg_event))
      83           0 :         ereport(ERROR,
      84             :                 (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
      85             :                  errmsg("triggered_change_notification: must be called after the change")));
      86             : 
      87             :     /* and that it's called for each row */
      88          10 :     if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
      89           0 :         ereport(ERROR,
      90             :                 (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
      91             :                  errmsg("triggered_change_notification: must be called for each row")));
      92             : 
      93          10 :     if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
      94           4 :         operation = 'I';
      95           6 :     else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
      96           2 :         operation = 'U';
      97           4 :     else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
      98           4 :         operation = 'D';
      99             :     else
     100             :     {
     101           0 :         elog(ERROR, "triggered_change_notification: trigger fired by unrecognized operation");
     102             :         operation = 'X';        /* silence compiler warning */
     103             :     }
     104             : 
     105          10 :     trigger = trigdata->tg_trigger;
     106          10 :     nargs = trigger->tgnargs;
     107          10 :     if (nargs > 1)
     108           0 :         ereport(ERROR,
     109             :                 (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
     110             :                  errmsg("triggered_change_notification: must not be called with more than one parameter")));
     111             : 
     112          10 :     if (nargs == 0)
     113           0 :         channel = "tcn";
     114             :     else
     115          10 :         channel = trigger->tgargs[0];
     116             : 
     117             :     /* get tuple data */
     118          10 :     trigtuple = trigdata->tg_trigtuple;
     119          10 :     rel = trigdata->tg_relation;
     120          10 :     tupdesc = rel->rd_att;
     121             : 
     122          10 :     foundPK = false;
     123             : 
     124             :     /*
     125             :      * Get the list of index OIDs for the table from the relcache, and look up
     126             :      * each one in the pg_index syscache until we find one marked primary key
     127             :      * (hopefully there isn't more than one such).
     128             :      */
     129          10 :     indexoidlist = RelationGetIndexList(rel);
     130             : 
     131          10 :     foreach(indexoidscan, indexoidlist)
     132             :     {
     133          10 :         Oid         indexoid = lfirst_oid(indexoidscan);
     134             :         HeapTuple   indexTuple;
     135             :         Form_pg_index index;
     136             : 
     137          10 :         indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid));
     138          10 :         if (!HeapTupleIsValid(indexTuple))  /* should not happen */
     139           0 :             elog(ERROR, "cache lookup failed for index %u", indexoid);
     140          10 :         index = (Form_pg_index) GETSTRUCT(indexTuple);
     141             :         /* we're only interested if it is the primary key and valid */
     142          10 :         if (index->indisprimary && index->indisvalid)
     143             :         {
     144          10 :             int         indnkeyatts = index->indnkeyatts;
     145             : 
     146          10 :             if (indnkeyatts > 0)
     147             :             {
     148             :                 int         i;
     149             : 
     150          10 :                 foundPK = true;
     151             : 
     152          10 :                 strcpy_quoted(payload, RelationGetRelationName(rel), '"');
     153          10 :                 appendStringInfoCharMacro(payload, ',');
     154          10 :                 appendStringInfoCharMacro(payload, operation);
     155             : 
     156          20 :                 for (i = 0; i < indnkeyatts; i++)
     157             :                 {
     158          10 :                     int         colno = index->indkey.values[i];
     159          10 :                     Form_pg_attribute attr = TupleDescAttr(tupdesc, colno - 1);
     160             : 
     161          10 :                     appendStringInfoCharMacro(payload, ',');
     162          10 :                     strcpy_quoted(payload, NameStr(attr->attname), '"');
     163          10 :                     appendStringInfoCharMacro(payload, '=');
     164          10 :                     strcpy_quoted(payload, SPI_getvalue(trigtuple, tupdesc, colno), '\'');
     165             :                 }
     166             : 
     167          10 :                 Async_Notify(channel, payload->data);
     168             :             }
     169          10 :             ReleaseSysCache(indexTuple);
     170          10 :             break;
     171             :         }
     172           0 :         ReleaseSysCache(indexTuple);
     173             :     }
     174             : 
     175          10 :     list_free(indexoidlist);
     176             : 
     177          10 :     if (!foundPK)
     178           0 :         ereport(ERROR,
     179             :                 (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
     180             :                  errmsg("triggered_change_notification: must be called on a table with a primary key")));
     181             : 
     182          10 :     return PointerGetDatum(NULL);   /* after trigger; value doesn't matter */
     183             : }

Generated by: LCOV version 1.14