notify_test.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. package executor
  2. import (
  3. "testing"
  4. "time"
  5. "github.com/danfragoso/pizzasql-next/pkg/storage"
  6. )
  7. func newNotifyExec(t *testing.T, hub *storage.Hub, schema *storage.SchemaManager, table *storage.TableManager, pid int32) *Executor {
  8. t.Helper()
  9. e := newExec(schema, table)
  10. e.SetBackendPID(pid)
  11. e.SetNotificationHub(hub)
  12. t.Cleanup(func() { _ = e.Close() })
  13. return e
  14. }
  15. func recvNotification(t *testing.T, e *Executor) storage.Notification {
  16. t.Helper()
  17. select {
  18. case n := <-e.NotificationChannel():
  19. return n
  20. case <-time.After(2 * time.Second):
  21. t.Fatal("timed out waiting for notification")
  22. return storage.Notification{}
  23. }
  24. }
  25. func expectNoNotification(t *testing.T, e *Executor) {
  26. t.Helper()
  27. select {
  28. case n := <-e.NotificationChannel():
  29. t.Fatalf("unexpected notification: %+v", n)
  30. case <-time.After(50 * time.Millisecond):
  31. }
  32. }
  33. func TestNotifyDeliversToListener(t *testing.T) {
  34. _, schema, table := newTestDB(t)
  35. hub := storage.NewHub()
  36. listener := newNotifyExec(t, hub, schema, table, 101)
  37. sender := newNotifyExec(t, hub, schema, table, 202)
  38. execMust(t, listener, "LISTEN updates")
  39. execMust(t, sender, "NOTIFY updates, 'hello'")
  40. n := recvNotification(t, listener)
  41. if n.Channel != "updates" || n.Payload != "hello" || n.PID != 202 {
  42. t.Fatalf("unexpected notification: %+v", n)
  43. }
  44. }
  45. func TestListenTakesEffectAtCommit(t *testing.T) {
  46. _, schema, table := newTestDB(t)
  47. hub := storage.NewHub()
  48. listener := newNotifyExec(t, hub, schema, table, 101)
  49. sender := newNotifyExec(t, hub, schema, table, 202)
  50. execMust(t, listener, "BEGIN")
  51. execMust(t, listener, "LISTEN late")
  52. execMust(t, sender, "NOTIFY late, 'before'")
  53. expectNoNotification(t, listener)
  54. execMust(t, listener, "COMMIT")
  55. execMust(t, sender, "NOTIFY late, 'after'")
  56. n := recvNotification(t, listener)
  57. if n.Payload != "after" {
  58. t.Fatalf("payload = %q, want after", n.Payload)
  59. }
  60. }
  61. func TestListenRollbackDiscardsSubscription(t *testing.T) {
  62. _, schema, table := newTestDB(t)
  63. hub := storage.NewHub()
  64. listener := newNotifyExec(t, hub, schema, table, 101)
  65. sender := newNotifyExec(t, hub, schema, table, 202)
  66. execMust(t, listener, "BEGIN")
  67. execMust(t, listener, "LISTEN rolledback")
  68. execMust(t, listener, "ROLLBACK")
  69. execMust(t, sender, "NOTIFY rolledback, 'nope'")
  70. expectNoNotification(t, listener)
  71. }
  72. func TestNotifyOnlyDeliveredAfterCommit(t *testing.T) {
  73. _, schema, table := newTestDB(t)
  74. hub := storage.NewHub()
  75. listener := newNotifyExec(t, hub, schema, table, 101)
  76. sender := newNotifyExec(t, hub, schema, table, 202)
  77. execMust(t, listener, "LISTEN events")
  78. execMust(t, sender, "BEGIN")
  79. execMust(t, sender, "NOTIFY events, 'pending'")
  80. expectNoNotification(t, listener)
  81. execMust(t, sender, "COMMIT")
  82. n := recvNotification(t, listener)
  83. if n.Payload != "pending" || n.LSN == 0 {
  84. t.Fatalf("unexpected notification: %+v", n)
  85. }
  86. }
  87. func TestNotifyRollbackDiscards(t *testing.T) {
  88. _, schema, table := newTestDB(t)
  89. hub := storage.NewHub()
  90. listener := newNotifyExec(t, hub, schema, table, 101)
  91. sender := newNotifyExec(t, hub, schema, table, 202)
  92. execMust(t, listener, "LISTEN events")
  93. execMust(t, sender, "BEGIN")
  94. execMust(t, sender, "NOTIFY events, 'gone'")
  95. execMust(t, sender, "ROLLBACK")
  96. expectNoNotification(t, listener)
  97. }
  98. func TestNotifySavepointRollback(t *testing.T) {
  99. _, schema, table := newTestDB(t)
  100. hub := storage.NewHub()
  101. listener := newNotifyExec(t, hub, schema, table, 101)
  102. sender := newNotifyExec(t, hub, schema, table, 202)
  103. execMust(t, listener, "LISTEN events")
  104. execMust(t, sender, "BEGIN")
  105. execMust(t, sender, "NOTIFY events, 'kept'")
  106. execMust(t, sender, "SAVEPOINT sp")
  107. execMust(t, sender, "NOTIFY events, 'discarded'")
  108. execMust(t, sender, "ROLLBACK TO SAVEPOINT sp")
  109. execMust(t, sender, "COMMIT")
  110. n := recvNotification(t, listener)
  111. if n.Payload != "kept" {
  112. t.Fatalf("payload = %q, want kept", n.Payload)
  113. }
  114. expectNoNotification(t, listener)
  115. }
  116. func TestNotifyDuplicatesCollapseInTransaction(t *testing.T) {
  117. _, schema, table := newTestDB(t)
  118. hub := storage.NewHub()
  119. listener := newNotifyExec(t, hub, schema, table, 101)
  120. sender := newNotifyExec(t, hub, schema, table, 202)
  121. execMust(t, listener, "LISTEN events")
  122. execMust(t, sender, "BEGIN")
  123. execMust(t, sender, "NOTIFY events, 'same'")
  124. execMust(t, sender, "NOTIFY events, 'same'")
  125. execMust(t, sender, "NOTIFY events, 'different'")
  126. execMust(t, sender, "COMMIT")
  127. first := recvNotification(t, listener)
  128. second := recvNotification(t, listener)
  129. if first.Payload != "same" || second.Payload != "different" {
  130. t.Fatalf("unexpected notifications: %+v %+v", first, second)
  131. }
  132. expectNoNotification(t, listener)
  133. }
  134. func TestUnlistenAllAndDisconnectCleanup(t *testing.T) {
  135. _, schema, table := newTestDB(t)
  136. hub := storage.NewHub()
  137. listener := newNotifyExec(t, hub, schema, table, 101)
  138. sender := newNotifyExec(t, hub, schema, table, 202)
  139. execMust(t, listener, "LISTEN a")
  140. execMust(t, listener, "LISTEN b")
  141. execMust(t, listener, "UNLISTEN *")
  142. execMust(t, sender, "NOTIFY a, 'x'")
  143. execMust(t, sender, "NOTIFY b, 'y'")
  144. expectNoNotification(t, listener)
  145. execMust(t, listener, "LISTEN c")
  146. _ = listener.Close()
  147. execMust(t, sender, "NOTIFY c, 'z'")
  148. expectNoNotification(t, listener)
  149. }
  150. func TestCommitLSNIsMonotonic(t *testing.T) {
  151. _, schema, table := newTestDB(t)
  152. e := newExec(schema, table)
  153. execMust(t, e, "CREATE TABLE lsn_test (id INTEGER PRIMARY KEY, v TEXT)")
  154. execMust(t, e, "BEGIN")
  155. execMust(t, e, "INSERT INTO lsn_test VALUES (1, 'a')")
  156. first := execMust(t, e, "COMMIT")
  157. if first.LSN == 0 {
  158. t.Fatal("first commit LSN is zero")
  159. }
  160. execMust(t, e, "BEGIN")
  161. execMust(t, e, "INSERT INTO lsn_test VALUES (2, 'b')")
  162. second := execMust(t, e, "COMMIT")
  163. if second.LSN <= first.LSN {
  164. t.Fatalf("second commit LSN %d did not advance past %d", second.LSN, first.LSN)
  165. }
  166. }