connection_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. package pgserver
  2. import (
  3. "bufio"
  4. "bytes"
  5. "io"
  6. "net"
  7. "sync"
  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. )
  14. func TestBindQuery(t *testing.T) {
  15. query, err := bindQuery(
  16. "SELECT $1, $2, $3, $4, '$5'",
  17. []boundParameter{
  18. {value: []byte("O'Reilly")},
  19. {value: []byte("42")},
  20. {null: true},
  21. {value: []byte("true")},
  22. },
  23. )
  24. if err != nil {
  25. t.Fatal(err)
  26. }
  27. want := "SELECT 'O''Reilly', 42, NULL, TRUE, '$5'"
  28. if query != want {
  29. t.Fatalf("bound query = %q, want %q", query, want)
  30. }
  31. }
  32. func TestBindQueryRequiresEveryParameter(t *testing.T) {
  33. if _, err := bindQuery("SELECT $2", []boundParameter{{value: []byte("one")}}); err == nil {
  34. t.Fatal("expected missing parameter error")
  35. }
  36. }
  37. func parseStmt(t *testing.T, sql string) parser.Statement {
  38. t.Helper()
  39. l := lexer.New(sql)
  40. p := parser.New(l)
  41. stmt, err := p.Parse()
  42. if err != nil {
  43. t.Fatalf("parse %q: %v", sql, err)
  44. }
  45. return stmt
  46. }
  47. func TestGetCommandTagSavepointKeepsTransaction(t *testing.T) {
  48. c := &Connection{txStatus: TxStatusIdle}
  49. tag := c.getCommandTag(parseStmt(t, "SAVEPOINT sp1"), executor.NewResult("SAVEPOINT"))
  50. if tag != "SAVEPOINT" {
  51. t.Fatalf("tag = %q, want %q", tag, "SAVEPOINT")
  52. }
  53. if c.txStatus != TxStatusInBlock {
  54. t.Fatalf("txStatus = %c, want %c", c.txStatus, TxStatusInBlock)
  55. }
  56. }
  57. func TestGetCommandTagReleaseKeepsTransaction(t *testing.T) {
  58. c := &Connection{txStatus: TxStatusInBlock}
  59. tag := c.getCommandTag(parseStmt(t, "RELEASE SAVEPOINT sp1"), executor.NewResult("RELEASE"))
  60. if tag != "RELEASE" {
  61. t.Fatalf("tag = %q, want %q", tag, "RELEASE")
  62. }
  63. if c.txStatus != TxStatusInBlock {
  64. t.Fatalf("txStatus = %c, want %c", c.txStatus, TxStatusInBlock)
  65. }
  66. }
  67. func TestGetCommandTagRollbackToSavepointKeepsTransaction(t *testing.T) {
  68. c := &Connection{txStatus: TxStatusFailed}
  69. tag := c.getCommandTag(parseStmt(t, "ROLLBACK TO SAVEPOINT sp1"), executor.NewResult("ROLLBACK"))
  70. if tag != "ROLLBACK" {
  71. t.Fatalf("tag = %q, want %q", tag, "ROLLBACK")
  72. }
  73. if c.txStatus != TxStatusInBlock {
  74. t.Fatalf("txStatus = %c, want %c", c.txStatus, TxStatusInBlock)
  75. }
  76. }
  77. func TestGetCommandTagFullRollbackEndsTransaction(t *testing.T) {
  78. c := &Connection{txStatus: TxStatusFailed}
  79. tag := c.getCommandTag(parseStmt(t, "ROLLBACK"), executor.NewResult("ROLLBACK"))
  80. if tag != "ROLLBACK" {
  81. t.Fatalf("tag = %q, want %q", tag, "ROLLBACK")
  82. }
  83. if c.txStatus != TxStatusIdle {
  84. t.Fatalf("txStatus = %c, want %c", c.txStatus, TxStatusIdle)
  85. }
  86. }
  87. func newTestConnection(t *testing.T) (*Connection, net.Conn) {
  88. t.Helper()
  89. server, client := net.Pipe()
  90. t.Cleanup(func() {
  91. server.Close()
  92. client.Close()
  93. })
  94. c := &Connection{
  95. conn: server,
  96. reader: bufio.NewReader(server),
  97. writer: bufio.NewWriter(server),
  98. params: map[string]string{"user": "tester"},
  99. statements: make(map[string]*preparedStatement),
  100. portals: make(map[string]*portal),
  101. txStatus: TxStatusIdle,
  102. quiet: true,
  103. }
  104. return c, client
  105. }
  106. func runQuery(t *testing.T, c *Connection, client net.Conn, msg *Message) []*Message {
  107. t.Helper()
  108. done := make(chan error, 1)
  109. go func() { done <- c.handleQuery(msg) }()
  110. reader := bufio.NewReader(client)
  111. var msgs []*Message
  112. for {
  113. m, err := ReadMessage(reader)
  114. if err != nil {
  115. t.Fatalf("read response: %v", err)
  116. }
  117. msgs = append(msgs, m)
  118. if m.Type == MsgReadyForQuery {
  119. break
  120. }
  121. }
  122. if err := <-done; err != nil {
  123. t.Fatalf("handleQuery: %v", err)
  124. }
  125. return msgs
  126. }
  127. func errorCode(msg *Message) string {
  128. if msg == nil || msg.Type != MsgErrorResponse {
  129. return ""
  130. }
  131. data := msg.Data
  132. for i := 0; i < len(data); {
  133. if data[i] == 0 {
  134. break
  135. }
  136. field := data[i]
  137. i++
  138. end := bytes.IndexByte(data[i:], 0)
  139. if end < 0 {
  140. break
  141. }
  142. value := string(data[i : i+end])
  143. if field == ErrorFieldCode {
  144. return value
  145. }
  146. i += end + 1
  147. }
  148. return ""
  149. }
  150. func TestHandleQueryEmptyPayloadReturnsProtocolError(t *testing.T) {
  151. c, client := newTestConnection(t)
  152. msgs := runQuery(t, c, client, &Message{Type: MsgQuery, Data: []byte{}})
  153. if len(msgs) != 2 {
  154. t.Fatalf("got %d messages, want 2", len(msgs))
  155. }
  156. if code := errorCode(msgs[0]); code != ErrCodeProtocolViolation {
  157. t.Fatalf("error code = %q, want %q", code, ErrCodeProtocolViolation)
  158. }
  159. }
  160. func TestHandleQueryMissingNullTerminatorReturnsProtocolError(t *testing.T) {
  161. c, client := newTestConnection(t)
  162. msgs := runQuery(t, c, client, &Message{Type: MsgQuery, Data: []byte("SELECT 1")})
  163. if len(msgs) != 2 {
  164. t.Fatalf("got %d messages, want 2", len(msgs))
  165. }
  166. if code := errorCode(msgs[0]); code != ErrCodeProtocolViolation {
  167. t.Fatalf("error code = %q, want %q", code, ErrCodeProtocolViolation)
  168. }
  169. }
  170. func TestHandleQueryEmptyQueryReturnsEmptyQueryResponse(t *testing.T) {
  171. for _, data := range [][]byte{{0}, []byte(";\x00"), []byte(" \x00")} {
  172. c, client := newTestConnection(t)
  173. msgs := runQuery(t, c, client, &Message{Type: MsgQuery, Data: data})
  174. if len(msgs) != 2 {
  175. t.Fatalf("payload %q: got %d messages, want 2", data, len(msgs))
  176. }
  177. if msgs[0].Type != MsgEmptyQueryResponse {
  178. t.Fatalf("payload %q: first message type = %c, want %c", data, msgs[0].Type, MsgEmptyQueryResponse)
  179. }
  180. }
  181. }
  182. func TestHandleQueryFailedTransactionInterceptsSpecialQueries(t *testing.T) {
  183. queries := []string{
  184. "SELECT version()",
  185. "SELECT current_user",
  186. "SHOW server_version",
  187. "SELECT * FROM information_schema.tables",
  188. }
  189. for _, q := range queries {
  190. c, client := newTestConnection(t)
  191. c.txStatus = TxStatusFailed
  192. msgs := runQuery(t, c, client, &Message{Type: MsgQuery, Data: []byte(q + "\x00")})
  193. if len(msgs) != 2 {
  194. t.Fatalf("query %q: got %d messages, want 2", q, len(msgs))
  195. }
  196. if code := errorCode(msgs[0]); code != ErrCodeTransactionAborted {
  197. t.Fatalf("query %q: error code = %q, want %q", q, code, ErrCodeTransactionAborted)
  198. }
  199. }
  200. }
  201. func TestIsRollbackStatement(t *testing.T) {
  202. for _, sql := range []string{"ROLLBACK", "ROLLBACK TO SAVEPOINT sp1", "rollback;", " rollback ;"} {
  203. if !isRollbackStatement(sql) {
  204. t.Errorf("isRollbackStatement(%q) = false, want true", sql)
  205. }
  206. }
  207. for _, sql := range []string{"SELECT version()", "SHOW server_version", "SAVEPOINT sp1"} {
  208. if isRollbackStatement(sql) {
  209. t.Errorf("isRollbackStatement(%q) = true, want false", sql)
  210. }
  211. }
  212. for _, sql := range []string{"ROLLBACKBOGUS", "ROLLBACK; SELECT 1"} {
  213. if isRollbackStatement(sql) {
  214. t.Errorf("isRollbackStatement(%q) = true, want false", sql)
  215. }
  216. }
  217. }
  218. // countingConn is an in-memory net.Conn that records how many times the
  219. // underlying stream is written. bufio.Writer flushes produce exactly one write
  220. // call each, so this counts flushes without blocking like net.Pipe does.
  221. type countingConn struct {
  222. mu sync.Mutex
  223. buf bytes.Buffer
  224. writes int
  225. }
  226. func (c *countingConn) Read(p []byte) (int, error) { return 0, io.EOF }
  227. func (c *countingConn) Write(p []byte) (int, error) {
  228. c.mu.Lock()
  229. defer c.mu.Unlock()
  230. c.writes++
  231. return c.buf.Write(p)
  232. }
  233. func (c *countingConn) Close() error { return nil }
  234. func (c *countingConn) LocalAddr() net.Addr { return &net.TCPAddr{} }
  235. func (c *countingConn) RemoteAddr() net.Addr { return &net.TCPAddr{} }
  236. func (c *countingConn) SetDeadline(t time.Time) error { return nil }
  237. func (c *countingConn) SetReadDeadline(t time.Time) error { return nil }
  238. func (c *countingConn) SetWriteDeadline(t time.Time) error { return nil }
  239. func (c *countingConn) bytes() []byte {
  240. c.mu.Lock()
  241. defer c.mu.Unlock()
  242. return append([]byte(nil), c.buf.Bytes()...)
  243. }
  244. func newCountingTestConnection(t *testing.T) (*Connection, *countingConn) {
  245. t.Helper()
  246. cc := &countingConn{}
  247. c := &Connection{
  248. conn: cc,
  249. reader: bufio.NewReader(cc),
  250. writer: bufio.NewWriter(cc),
  251. params: map[string]string{"user": "tester"},
  252. statements: make(map[string]*preparedStatement),
  253. portals: make(map[string]*portal),
  254. txStatus: TxStatusIdle,
  255. quiet: true,
  256. }
  257. return c, cc
  258. }
  259. func readAllMessages(t *testing.T, data []byte) []*Message {
  260. t.Helper()
  261. r := bytes.NewReader(data)
  262. var msgs []*Message
  263. for r.Len() > 0 {
  264. m, err := ReadMessage(r)
  265. if err != nil {
  266. t.Fatalf("read message: %v", err)
  267. }
  268. msgs = append(msgs, m)
  269. }
  270. return msgs
  271. }
  272. func TestWriteMessageDoesNotFlush(t *testing.T) {
  273. c, cc := newCountingTestConnection(t)
  274. if err := c.writeMessage(MsgCommandComplete, []byte("SELECT 0")); err != nil {
  275. t.Fatal(err)
  276. }
  277. if cc.writes != 0 {
  278. t.Fatalf("writeMessage flushed %d times, want 0", cc.writes)
  279. }
  280. }
  281. func TestReadyForQueryFlushesBufferedMessages(t *testing.T) {
  282. c, cc := newCountingTestConnection(t)
  283. if err := c.writeMessage(MsgCommandComplete, []byte("SELECT 1")); err != nil {
  284. t.Fatal(err)
  285. }
  286. if err := c.sendReadyForQuery(); err != nil {
  287. t.Fatal(err)
  288. }
  289. if cc.writes == 0 {
  290. t.Fatal("sendReadyForQuery did not flush buffered messages")
  291. }
  292. msgs := readAllMessages(t, cc.bytes())
  293. if len(msgs) != 2 {
  294. t.Fatalf("got %d messages, want 2", len(msgs))
  295. }
  296. if msgs[0].Type != MsgCommandComplete || msgs[1].Type != MsgReadyForQuery {
  297. t.Fatalf("unexpected message sequence: %c, %c", msgs[0].Type, msgs[1].Type)
  298. }
  299. }
  300. func TestErrorResponseFlushes(t *testing.T) {
  301. c, cc := newCountingTestConnection(t)
  302. if err := c.sendError("ERROR", ErrCodeSyntaxError, "boom"); err != nil {
  303. t.Fatal(err)
  304. }
  305. if cc.writes == 0 {
  306. t.Fatal("sendError did not flush")
  307. }
  308. msgs := readAllMessages(t, cc.bytes())
  309. if len(msgs) != 1 || msgs[0].Type != MsgErrorResponse {
  310. t.Fatalf("expected a single error response, got %d messages", len(msgs))
  311. }
  312. }
  313. func TestFlushMessageFlushes(t *testing.T) {
  314. c, cc := newCountingTestConnection(t)
  315. if err := c.writeMessage(MsgCommandComplete, []byte("SELECT 1")); err != nil {
  316. t.Fatal(err)
  317. }
  318. if err := c.handleMessage(&Message{Type: MsgFlush}); err != nil {
  319. t.Fatal(err)
  320. }
  321. if cc.writes == 0 {
  322. t.Fatal("Flush message did not flush buffered data")
  323. }
  324. }
  325. func TestMultiRowResultBuffersRows(t *testing.T) {
  326. c, cc := newCountingTestConnection(t)
  327. result := executor.NewResult("SELECT")
  328. result.AddColumnWithType("n", "INTEGER")
  329. const rows = 500
  330. for i := 0; i < rows; i++ {
  331. result.AddRow(int64(i))
  332. }
  333. if err := c.sendResult(result, &parser.SelectStmt{}); err != nil {
  334. t.Fatal(err)
  335. }
  336. // sendResult emits RowDescription + rows DataRows + CommandComplete. With
  337. // per-message flushing that would be rows+2 underlying writes; buffered it
  338. // is bounded by the bufio.Writer capacity (a few flushes at most).
  339. numMessages := rows + 2
  340. if cc.writes >= numMessages {
  341. t.Fatalf("sendResult flushed %d times, want fewer than %d messages", cc.writes, numMessages)
  342. }
  343. if cc.writes >= rows {
  344. t.Fatalf("sendResult flushed %d times for %d rows, expected buffering", cc.writes, rows)
  345. }
  346. }