1 /*-------------------------------------------------------------------------
4 * triggered change notification support for PostgreSQL
6 * Portions Copyright (c) 2011-2012, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
13 *-------------------------------------------------------------------------
18 #include "access/htup_details.h"
19 #include "executor/spi.h"
20 #include "commands/async.h"
21 #include "commands/trigger.h"
22 #include "lib/stringinfo.h"
23 #include "utils/rel.h"
24 #include "utils/syscache.h"
30 /* forward declarations */
31 Datum triggered_change_notification(PG_FUNCTION_ARGS);
35 * Copy from s (for source) to r (for result), wrapping with q (quote)
36 * characters and doubling any quote characters found.
39 strcpy_quoted(StringInfo r, const char *s, const char q)
41 appendStringInfoCharMacro(r, q);
45 appendStringInfoCharMacro(r, q);
46 appendStringInfoCharMacro(r, *s);
49 appendStringInfoCharMacro(r, q);
53 * triggered_change_notification
55 * This trigger function will send a notification of data modification with
56 * primary key values. The channel will be "tcn" unless the trigger is
57 * created with a parameter, in which case that parameter will be used.
59 PG_FUNCTION_INFO_V1(triggered_change_notification);
62 triggered_change_notification(PG_FUNCTION_ARGS)
64 TriggerData *trigdata = (TriggerData *) fcinfo->context;
72 StringInfo payload = makeStringInfo();
76 ListCell *indexoidscan;
78 /* make sure it's called as a trigger */
79 if (!CALLED_AS_TRIGGER(fcinfo))
81 (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
82 errmsg("triggered_change_notification: must be called as trigger")));
84 /* and that it's called after the change */
85 if (!TRIGGER_FIRED_AFTER(trigdata->tg_event))
87 (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
88 errmsg("triggered_change_notification: must be called after the change")));
90 /* and that it's called for each row */
91 if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
93 (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
94 errmsg("triggered_change_notification: must be called for each row")));
96 if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
98 else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
100 else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
104 elog(ERROR, "triggered_change_notification: trigger fired by unrecognized operation");
105 operation = 'X'; /* silence compiler warning */
108 trigger = trigdata->tg_trigger;
109 nargs = trigger->tgnargs;
112 (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
113 errmsg("triggered_change_notification: must not be called with more than one parameter")));
118 channel = trigger->tgargs[0];
121 trigtuple = trigdata->tg_trigtuple;
122 rel = trigdata->tg_relation;
123 tupdesc = rel->rd_att;
128 * Get the list of index OIDs for the table from the relcache, and look up
129 * each one in the pg_index syscache until we find one marked primary key
130 * (hopefully there isn't more than one such).
132 indexoidlist = RelationGetIndexList(rel);
134 foreach(indexoidscan, indexoidlist)
136 Oid indexoid = lfirst_oid(indexoidscan);
137 HeapTuple indexTuple;
140 indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid));
141 if (!HeapTupleIsValid(indexTuple)) /* should not happen */
142 elog(ERROR, "cache lookup failed for index %u", indexoid);
143 index = (Form_pg_index) GETSTRUCT(indexTuple);
144 /* we're only interested if it is the primary key */
145 if (index->indisprimary)
147 int numatts = index->indnatts;
155 strcpy_quoted(payload, RelationGetRelationName(rel), '"');
156 appendStringInfoCharMacro(payload, ',');
157 appendStringInfoCharMacro(payload, operation);
159 for (i = 0; i < numatts; i++)
161 int colno = index->indkey.values[i];
163 appendStringInfoCharMacro(payload, ',');
164 strcpy_quoted(payload, NameStr((tupdesc->attrs[colno - 1])->attname), '"');
165 appendStringInfoCharMacro(payload, '=');
166 strcpy_quoted(payload, SPI_getvalue(trigtuple, tupdesc, colno), '\'');
169 Async_Notify(channel, payload->data);
171 ReleaseSysCache(indexTuple);
174 ReleaseSysCache(indexTuple);
177 list_free(indexoidlist);
181 (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
182 errmsg("triggered_change_notification: must be called on a table with a primary key")));
184 return PointerGetDatum(NULL); /* after trigger; value doesn't matter */