2
0

notify.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. package executor
  2. import (
  3. "fmt"
  4. "github.com/danfragoso/pizzasql-next/pkg/parser"
  5. "github.com/danfragoso/pizzasql-next/pkg/storage"
  6. )
  7. // listenOp records one LISTEN/UNLISTEN statement so its effect can be applied
  8. // at transaction commit. PostgreSQL makes subscription changes visible only
  9. // after commit; a rollback leaves the previous subscription set untouched.
  10. type listenOp struct {
  11. kind listenOpKind
  12. channel string
  13. }
  14. type listenOpKind int
  15. const (
  16. listenOpListen listenOpKind = iota
  17. listenOpUnlisten
  18. listenOpUnlistenAll
  19. )
  20. // notificationSnapshot captures the staged notification state at a savepoint.
  21. type notificationSnapshot struct {
  22. listenOps int
  23. notifyQueue int
  24. }
  25. // SetNotificationHub installs the per-database notification registry and
  26. // creates this session's subscriber. It must be called before the executor
  27. // processes LISTEN or NOTIFY. Safe to call once per connection.
  28. func (e *Executor) SetNotificationHub(hub *storage.Hub) {
  29. e.hub = hub
  30. if hub == nil {
  31. return
  32. }
  33. if e.subscriber == nil {
  34. e.subscriber = storage.NewSubscriber(e.backendPID, 0)
  35. }
  36. }
  37. // SetBackendPID sets the backend PID reported in NotificationResponse frames.
  38. // It also applies to an already-created subscriber.
  39. func (e *Executor) SetBackendPID(pid int32) {
  40. e.backendPID = pid
  41. if e.subscriber == nil && e.hub != nil {
  42. e.subscriber = storage.NewSubscriber(pid, 0)
  43. }
  44. }
  45. // NotificationChannel returns the receive side of this session's notification
  46. // queue, or nil when no hub is configured.
  47. func (e *Executor) NotificationChannel() <-chan storage.Notification {
  48. if e.subscriber == nil {
  49. return nil
  50. }
  51. return e.subscriber.Notifications()
  52. }
  53. // NotificationOverflow is closed when the session's outbound queue is full and
  54. // the hub has dropped it. Protocol layers should disconnect the connection.
  55. func (e *Executor) NotificationOverflow() <-chan struct{} {
  56. if e.subscriber == nil {
  57. return nil
  58. }
  59. return e.subscriber.Overflow()
  60. }
  61. // Close releases this session's subscriptions. It is the disconnect path; it
  62. // is safe to call more than once.
  63. func (e *Executor) Close() error {
  64. if e.hub != nil && e.subscriber != nil {
  65. e.hub.UnsubscribeAll(e.subscriber)
  66. }
  67. e.listens = make(map[string]bool)
  68. e.listenOps = nil
  69. e.notifyQueue = nil
  70. e.notificationSaves = nil
  71. return e.RollbackActive()
  72. }
  73. func (e *Executor) executeListen(stmt *parser.ListenStmt) (*Result, error) {
  74. if stmt.Channel == "" {
  75. return nil, fmt.Errorf("channel name cannot be empty")
  76. }
  77. if e.inTransaction {
  78. e.listenOps = append(e.listenOps, listenOp{kind: listenOpListen, channel: stmt.Channel})
  79. return NewResult("LISTEN"), nil
  80. }
  81. e.applyListenOp(listenOp{kind: listenOpListen, channel: stmt.Channel})
  82. return NewResult("LISTEN"), nil
  83. }
  84. func (e *Executor) executeUnlisten(stmt *parser.UnlistenStmt) (*Result, error) {
  85. op := listenOp{kind: listenOpUnlisten, channel: stmt.Channel}
  86. if stmt.All {
  87. op = listenOp{kind: listenOpUnlistenAll}
  88. } else if stmt.Channel == "" {
  89. return nil, fmt.Errorf("channel name cannot be empty")
  90. }
  91. if e.inTransaction {
  92. e.listenOps = append(e.listenOps, op)
  93. return NewResult("UNLISTEN"), nil
  94. }
  95. e.applyListenOp(op)
  96. return NewResult("UNLISTEN"), nil
  97. }
  98. func (e *Executor) executeNotify(stmt *parser.NotifyStmt) (*Result, error) {
  99. if len(stmt.Payload) > 8000 {
  100. return nil, fmt.Errorf("notification payload is longer than 8000 bytes")
  101. }
  102. n := storage.Notification{
  103. PID: e.backendPID,
  104. Channel: stmt.Channel,
  105. Payload: stmt.Payload,
  106. }
  107. if !e.inTransaction {
  108. e.publish(n)
  109. return NewResult("NOTIFY"), nil
  110. }
  111. // PostgreSQL collapses duplicate (channel, payload) pairs within one
  112. // transaction, regardless of how many times they were raised.
  113. for _, queued := range e.notifyQueue {
  114. if queued.Channel == n.Channel && queued.Payload == n.Payload {
  115. return NewResult("NOTIFY"), nil
  116. }
  117. }
  118. e.notifyQueue = append(e.notifyQueue, n)
  119. return NewResult("NOTIFY"), nil
  120. }
  121. func (e *Executor) applyListenOp(op listenOp) {
  122. if e.hub == nil || e.subscriber == nil {
  123. return
  124. }
  125. switch op.kind {
  126. case listenOpListen:
  127. if !e.listens[op.channel] {
  128. e.hub.Subscribe(e.subscriber, op.channel)
  129. e.listens[op.channel] = true
  130. }
  131. case listenOpUnlisten:
  132. if e.listens[op.channel] {
  133. e.hub.Unsubscribe(e.subscriber, op.channel)
  134. delete(e.listens, op.channel)
  135. }
  136. case listenOpUnlistenAll:
  137. for channel := range e.listens {
  138. e.hub.Unsubscribe(e.subscriber, channel)
  139. }
  140. e.listens = make(map[string]bool)
  141. }
  142. }
  143. func (e *Executor) publish(n storage.Notification) {
  144. if e.hub == nil {
  145. return
  146. }
  147. e.hub.Publish(n)
  148. }
  149. // finishCommit applies staged subscription changes and releases queued
  150. // notifications. It runs only after the storage commit has succeeded.
  151. func (e *Executor) finishCommit(lsn uint64) {
  152. for _, op := range e.listenOps {
  153. e.applyListenOp(op)
  154. }
  155. for _, n := range e.notifyQueue {
  156. n.LSN = lsn
  157. e.publish(n)
  158. }
  159. e.clearNotificationState()
  160. }
  161. // finishRollback discards staged subscription changes and notifications.
  162. func (e *Executor) finishRollback() {
  163. e.clearNotificationState()
  164. }
  165. func (e *Executor) clearNotificationState() {
  166. e.listenOps = nil
  167. e.notifyQueue = nil
  168. e.notificationSaves = nil
  169. }
  170. func (e *Executor) notificationSnapshot() notificationSnapshot {
  171. return notificationSnapshot{
  172. listenOps: len(e.listenOps),
  173. notifyQueue: len(e.notifyQueue),
  174. }
  175. }
  176. func (e *Executor) rollbackNotificationsTo(s notificationSnapshot) {
  177. if s.listenOps <= len(e.listenOps) {
  178. e.listenOps = e.listenOps[:s.listenOps]
  179. }
  180. if s.notifyQueue <= len(e.notifyQueue) {
  181. e.notifyQueue = e.notifyQueue[:s.notifyQueue]
  182. }
  183. }