notify_test.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. package pgserver
  2. import (
  3. "bufio"
  4. "bytes"
  5. "encoding/binary"
  6. "fmt"
  7. "net"
  8. "testing"
  9. "time"
  10. "github.com/danfragoso/pizzasql-next/pkg/executor"
  11. "github.com/danfragoso/pizzasql-next/pkg/lexer"
  12. "github.com/danfragoso/pizzasql-next/pkg/parser"
  13. "github.com/danfragoso/pizzasql-next/pkg/storage"
  14. "github.com/danfragoso/pizzasql-next/pkg/testkv"
  15. )
  16. func newNotifyDatabase(t *testing.T) (*storage.DatabaseManager, *storage.DatabaseInstance) {
  17. t.Helper()
  18. kv := testkv.New(t)
  19. pool := kv.Pool(8)
  20. t.Cleanup(func() { pool.Close() })
  21. dm := storage.NewDatabaseManager(pool, &storage.DatabaseManagerConfig{
  22. DefaultDatabase: "pg_notify",
  23. AutoCreate: true,
  24. })
  25. db, err := dm.GetDatabase("pg_notify")
  26. if err != nil {
  27. t.Fatal(err)
  28. }
  29. return dm, db
  30. }
  31. func queryMessage(sql string) []byte {
  32. data := append([]byte(sql), 0)
  33. var msg bytes.Buffer
  34. msg.WriteByte(MsgQuery)
  35. _ = binary.Write(&msg, binary.BigEndian, uint32(len(data)+4))
  36. msg.Write(data)
  37. return msg.Bytes()
  38. }
  39. func writeAndReadUntilReady(t *testing.T, client net.Conn, r *bufio.Reader, payload []byte) []*Message {
  40. t.Helper()
  41. if _, err := client.Write(payload); err != nil {
  42. t.Fatalf("write: %v", err)
  43. }
  44. var msgs []*Message
  45. for {
  46. _ = client.SetReadDeadline(time.Now().Add(3 * time.Second))
  47. m, err := ReadMessage(r)
  48. if err != nil {
  49. t.Fatalf("read response: %v", err)
  50. }
  51. msgs = append(msgs, m)
  52. if m.Type == MsgReadyForQuery {
  53. return msgs
  54. }
  55. }
  56. }
  57. func startNotifyConnection(t *testing.T, dm *storage.DatabaseManager) (net.Conn, *bufio.Reader) {
  58. t.Helper()
  59. server, client := net.Pipe()
  60. c := NewConnection(server, dm, true)
  61. done := make(chan error, 1)
  62. go func() { done <- c.Handle() }()
  63. t.Cleanup(func() {
  64. client.Close()
  65. <-done
  66. })
  67. if _, err := client.Write(startupMessage(map[string]string{"user": "tester", "database": "pg_notify"})); err != nil {
  68. t.Fatalf("startup: %v", err)
  69. }
  70. r := bufio.NewReader(client)
  71. for {
  72. _ = client.SetReadDeadline(time.Now().Add(3 * time.Second))
  73. m, err := ReadMessage(r)
  74. if err != nil {
  75. t.Fatalf("read startup: %v", err)
  76. }
  77. if m.Type == MsgReadyForQuery {
  78. return client, r
  79. }
  80. }
  81. }
  82. func publishFromNewSession(t *testing.T, db *storage.DatabaseInstance, channel, payload string) {
  83. t.Helper()
  84. e := executor.New(db.Schema, db.Table)
  85. e.SetBackendPID(4242)
  86. e.SetNotificationHub(db.Hub)
  87. t.Cleanup(func() { _ = e.Close() })
  88. l := lexer.New(fmt.Sprintf("NOTIFY %s, '%s'", channel, payload))
  89. stmt, err := parser.New(l).Parse()
  90. if err != nil {
  91. t.Fatal(err)
  92. }
  93. if _, err := e.Execute(stmt); err != nil {
  94. t.Fatal(err)
  95. }
  96. }
  97. func TestIdleListenerReceivesNotification(t *testing.T) {
  98. dm, db := newNotifyDatabase(t)
  99. client, r := startNotifyConnection(t, dm)
  100. writeAndReadUntilReady(t, client, r, queryMessage("LISTEN updates"))
  101. publishFromNewSession(t, db, "updates", "hello")
  102. _ = client.SetReadDeadline(time.Now().Add(3 * time.Second))
  103. m, err := ReadMessage(r)
  104. if err != nil {
  105. t.Fatalf("read notification: %v", err)
  106. }
  107. if m.Type != MsgNotificationResponse {
  108. t.Fatalf("message type = %c, want NotificationResponse", m.Type)
  109. }
  110. if len(m.Data) < 6 {
  111. t.Fatalf("short notification frame: %v", m.Data)
  112. }
  113. pid := int32(binary.BigEndian.Uint32(m.Data[:4]))
  114. if pid != 4242 {
  115. t.Fatalf("pid = %d, want 4242", pid)
  116. }
  117. pos := 4
  118. channel, pos, err := readCString(m.Data, pos)
  119. if err != nil || channel != "updates" {
  120. t.Fatalf("channel = %q err=%v", channel, err)
  121. }
  122. payload, _, err := readCString(m.Data, pos)
  123. if err != nil || payload != "hello" {
  124. t.Fatalf("payload = %q err=%v", payload, err)
  125. }
  126. }
  127. func TestExtendedProtocolDeliversNotificationAfterSync(t *testing.T) {
  128. dm, db := newNotifyDatabase(t)
  129. client, r := startNotifyConnection(t, dm)
  130. // Parse + Bind + Execute, then Flush. The connection remains in an
  131. // extended-protocol cycle, so a notification published now must not be
  132. // written until Sync.
  133. var buf bytes.Buffer
  134. parseBody := []byte("stmt\x00LISTEN ext\x00\x00\x00")
  135. buf.WriteByte(MsgParse)
  136. _ = binary.Write(&buf, binary.BigEndian, uint32(len(parseBody)+4))
  137. buf.Write(parseBody)
  138. bindBody := []byte("portal\x00stmt\x00\x00\x00\x00\x00\x00\x00")
  139. buf.WriteByte(MsgBind)
  140. _ = binary.Write(&buf, binary.BigEndian, uint32(len(bindBody)+4))
  141. buf.Write(bindBody)
  142. execBody := []byte("portal\x00\x00\x00\x00\x00")
  143. buf.WriteByte(MsgExecute)
  144. _ = binary.Write(&buf, binary.BigEndian, uint32(len(execBody)+4))
  145. buf.Write(execBody)
  146. buf.WriteByte(MsgFlush)
  147. _ = binary.Write(&buf, binary.BigEndian, uint32(4))
  148. if _, err := client.Write(buf.Bytes()); err != nil {
  149. t.Fatal(err)
  150. }
  151. for {
  152. _ = client.SetReadDeadline(time.Now().Add(3 * time.Second))
  153. m, err := ReadMessage(r)
  154. if err != nil {
  155. t.Fatalf("read extended response: %v", err)
  156. }
  157. if m.Type == MsgCommandComplete {
  158. break
  159. }
  160. if m.Type == MsgErrorResponse {
  161. t.Fatalf("extended protocol error: %+v", m.Data)
  162. }
  163. }
  164. publishFromNewSession(t, db, "ext", "during-extended")
  165. // The notification is queued in the subscriber but must not be written
  166. // before Sync closes the cycle.
  167. _ = client.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
  168. if m, err := ReadMessage(r); err == nil {
  169. t.Fatalf("notification interleaved into extended cycle: type %c", m.Type)
  170. }
  171. var syncMsg bytes.Buffer
  172. syncMsg.WriteByte(MsgSync)
  173. _ = binary.Write(&syncMsg, binary.BigEndian, uint32(4))
  174. if _, err := client.Write(syncMsg.Bytes()); err != nil {
  175. t.Fatal(err)
  176. }
  177. var sawNotification bool
  178. for {
  179. _ = client.SetReadDeadline(time.Now().Add(3 * time.Second))
  180. m, err := ReadMessage(r)
  181. if err != nil {
  182. t.Fatalf("read sync response: %v", err)
  183. }
  184. if m.Type == MsgNotificationResponse {
  185. sawNotification = true
  186. }
  187. if m.Type == MsgReadyForQuery {
  188. break
  189. }
  190. }
  191. if !sawNotification {
  192. // Notifications are emitted after ReadyForQuery, so the frame follows the
  193. // Ready message that closed the extended cycle.
  194. _ = client.SetReadDeadline(time.Now().Add(3 * time.Second))
  195. m, err := ReadMessage(r)
  196. if err != nil {
  197. t.Fatalf("notification was not delivered after Sync: %v", err)
  198. }
  199. if m.Type != MsgNotificationResponse {
  200. t.Fatalf("post-Sync message type = %c, want NotificationResponse", m.Type)
  201. }
  202. }
  203. }
  204. func TestCommitLSNParameterStatus(t *testing.T) {
  205. dm, _ := newNotifyDatabase(t)
  206. client, r := startNotifyConnection(t, dm)
  207. writeAndReadUntilReady(t, client, r, queryMessage("CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)"))
  208. writeAndReadUntilReady(t, client, r, queryMessage("BEGIN"))
  209. writeAndReadUntilReady(t, client, r, queryMessage("INSERT INTO t VALUES (1, 'a')"))
  210. msgs := writeAndReadUntilReady(t, client, r, queryMessage("COMMIT"))
  211. found := false
  212. for _, m := range msgs {
  213. if m.Type != MsgParameterStatus {
  214. continue
  215. }
  216. name, pos, err := readCString(m.Data, 0)
  217. if err != nil || name != "pizzasql.lsn" {
  218. continue
  219. }
  220. value, _, err := readCString(m.Data, pos)
  221. if err != nil || value == "" || value == "0" {
  222. t.Fatalf("invalid lsn value %q", value)
  223. }
  224. found = true
  225. }
  226. if !found {
  227. t.Fatal("COMMIT did not expose pizzasql.lsn")
  228. }
  229. }
  230. func TestNotificationFrameEncoding(t *testing.T) {
  231. c, _ := newTestConnection(t)
  232. var out bytes.Buffer
  233. c.writer = bufio.NewWriter(&out)
  234. if err := c.sendNotification(storage.Notification{PID: 7, Channel: "ch", Payload: "p"}); err != nil {
  235. t.Fatal(err)
  236. }
  237. if err := c.writer.Flush(); err != nil {
  238. t.Fatal(err)
  239. }
  240. m, err := ReadMessage(bytes.NewReader(out.Bytes()))
  241. if err != nil {
  242. t.Fatal(err)
  243. }
  244. if m.Type != MsgNotificationResponse {
  245. t.Fatalf("type = %c", m.Type)
  246. }
  247. channel, pos, _ := readCString(m.Data, 4)
  248. payload, _, _ := readCString(m.Data, pos)
  249. if channel != "ch" || payload != "p" {
  250. t.Fatalf("decoded %q %q", channel, payload)
  251. }
  252. }
  253. func TestHandleStartupDrainsNotificationsOnDisconnect(t *testing.T) {
  254. dm, db := newNotifyDatabase(t)
  255. client, r := startNotifyConnection(t, dm)
  256. writeAndReadUntilReady(t, client, r, queryMessage("LISTEN cleanup"))
  257. if len(db.Hub.Channels()) != 1 {
  258. t.Fatalf("channels = %v", db.Hub.Channels())
  259. }
  260. // Terminate and give Handle time to run its cleanup defer.
  261. if _, err := client.Write([]byte{MsgTerminate, 0, 0, 0, 4}); err != nil {
  262. t.Fatal(err)
  263. }
  264. deadline := time.Now().Add(3 * time.Second)
  265. for time.Now().Before(deadline) {
  266. if len(db.Hub.Channels()) == 0 {
  267. return
  268. }
  269. time.Sleep(10 * time.Millisecond)
  270. }
  271. t.Fatalf("subscription not cleaned up: %v", db.Hub.Channels())
  272. }