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