You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

tcn.c 5.0KB

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