connection_test.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. package pgserver
  2. import (
  3. "bufio"
  4. "bytes"
  5. "net"
  6. "testing"
  7. "github.com/danfragoso/pizzasql-next/pkg/executor"
  8. "github.com/danfragoso/pizzasql-next/pkg/lexer"
  9. "github.com/danfragoso/pizzasql-next/pkg/parser"
  10. )
  11. func TestBindQuery(t *testing.T) {
  12. query, err := bindQuery(
  13. "SELECT $1, $2, $3, $4, '$5'",
  14. []boundParameter{
  15. {value: []byte("O'Reilly")},
  16. {value: []byte("42")},
  17. {null: true},
  18. {value: []byte("true")},
  19. },
  20. )
  21. if err != nil {
  22. t.Fatal(err)
  23. }
  24. want := "SELECT 'O''Reilly', 42, NULL, TRUE, '$5'"
  25. if query != want {
  26. t.Fatalf("bound query = %q, want %q", query, want)
  27. }
  28. }
  29. func TestBindQueryRequiresEveryParameter(t *testing.T) {
  30. if _, err := bindQuery("SELECT $2", []boundParameter{{value: []byte("one")}}); err == nil {
  31. t.Fatal("expected missing parameter error")
  32. }
  33. }
  34. func parseStmt(t *testing.T, sql string) parser.Statement {
  35. t.Helper()
  36. l := lexer.New(sql)
  37. p := parser.New(l)
  38. stmt, err := p.Parse()
  39. if err != nil {
  40. t.Fatalf("parse %q: %v", sql, err)
  41. }
  42. return stmt
  43. }
  44. func TestGetCommandTagSavepointKeepsTransaction(t *testing.T) {
  45. c := &Connection{txStatus: TxStatusIdle}
  46. tag := c.getCommandTag(parseStmt(t, "SAVEPOINT sp1"), executor.NewResult("SAVEPOINT"))
  47. if tag != "SAVEPOINT" {
  48. t.Fatalf("tag = %q, want %q", tag, "SAVEPOINT")
  49. }
  50. if c.txStatus != TxStatusInBlock {
  51. t.Fatalf("txStatus = %c, want %c", c.txStatus, TxStatusInBlock)
  52. }
  53. }
  54. func TestGetCommandTagReleaseKeepsTransaction(t *testing.T) {
  55. c := &Connection{txStatus: TxStatusInBlock}
  56. tag := c.getCommandTag(parseStmt(t, "RELEASE SAVEPOINT sp1"), executor.NewResult("RELEASE"))
  57. if tag != "RELEASE" {
  58. t.Fatalf("tag = %q, want %q", tag, "RELEASE")
  59. }
  60. if c.txStatus != TxStatusInBlock {
  61. t.Fatalf("txStatus = %c, want %c", c.txStatus, TxStatusInBlock)
  62. }
  63. }
  64. func TestGetCommandTagRollbackToSavepointKeepsTransaction(t *testing.T) {
  65. c := &Connection{txStatus: TxStatusFailed}
  66. tag := c.getCommandTag(parseStmt(t, "ROLLBACK TO SAVEPOINT sp1"), executor.NewResult("ROLLBACK"))
  67. if tag != "ROLLBACK" {
  68. t.Fatalf("tag = %q, want %q", tag, "ROLLBACK")
  69. }
  70. if c.txStatus != TxStatusInBlock {
  71. t.Fatalf("txStatus = %c, want %c", c.txStatus, TxStatusInBlock)
  72. }
  73. }
  74. func TestGetCommandTagFullRollbackEndsTransaction(t *testing.T) {
  75. c := &Connection{txStatus: TxStatusFailed}
  76. tag := c.getCommandTag(parseStmt(t, "ROLLBACK"), executor.NewResult("ROLLBACK"))
  77. if tag != "ROLLBACK" {
  78. t.Fatalf("tag = %q, want %q", tag, "ROLLBACK")
  79. }
  80. if c.txStatus != TxStatusIdle {
  81. t.Fatalf("txStatus = %c, want %c", c.txStatus, TxStatusIdle)
  82. }
  83. }
  84. func newTestConnection(t *testing.T) (*Connection, net.Conn) {
  85. t.Helper()
  86. server, client := net.Pipe()
  87. t.Cleanup(func() {
  88. server.Close()
  89. client.Close()
  90. })
  91. c := &Connection{
  92. conn: server,
  93. reader: bufio.NewReader(server),
  94. writer: bufio.NewWriter(server),
  95. params: map[string]string{"user": "tester"},
  96. statements: make(map[string]*preparedStatement),
  97. portals: make(map[string]*portal),
  98. txStatus: TxStatusIdle,
  99. quiet: true,
  100. }
  101. return c, client
  102. }
  103. func runQuery(t *testing.T, c *Connection, client net.Conn, msg *Message) []*Message {
  104. t.Helper()
  105. done := make(chan error, 1)
  106. go func() { done <- c.handleQuery(msg) }()
  107. reader := bufio.NewReader(client)
  108. var msgs []*Message
  109. for {
  110. m, err := ReadMessage(reader)
  111. if err != nil {
  112. t.Fatalf("read response: %v", err)
  113. }
  114. msgs = append(msgs, m)
  115. if m.Type == MsgReadyForQuery {
  116. break
  117. }
  118. }
  119. if err := <-done; err != nil {
  120. t.Fatalf("handleQuery: %v", err)
  121. }
  122. return msgs
  123. }
  124. func errorCode(msg *Message) string {
  125. if msg == nil || msg.Type != MsgErrorResponse {
  126. return ""
  127. }
  128. data := msg.Data
  129. for i := 0; i < len(data); {
  130. if data[i] == 0 {
  131. break
  132. }
  133. field := data[i]
  134. i++
  135. end := bytes.IndexByte(data[i:], 0)
  136. if end < 0 {
  137. break
  138. }
  139. value := string(data[i : i+end])
  140. if field == ErrorFieldCode {
  141. return value
  142. }
  143. i += end + 1
  144. }
  145. return ""
  146. }
  147. func TestHandleQueryEmptyPayloadReturnsProtocolError(t *testing.T) {
  148. c, client := newTestConnection(t)
  149. msgs := runQuery(t, c, client, &Message{Type: MsgQuery, Data: []byte{}})
  150. if len(msgs) != 2 {
  151. t.Fatalf("got %d messages, want 2", len(msgs))
  152. }
  153. if code := errorCode(msgs[0]); code != ErrCodeProtocolViolation {
  154. t.Fatalf("error code = %q, want %q", code, ErrCodeProtocolViolation)
  155. }
  156. }
  157. func TestHandleQueryMissingNullTerminatorReturnsProtocolError(t *testing.T) {
  158. c, client := newTestConnection(t)
  159. msgs := runQuery(t, c, client, &Message{Type: MsgQuery, Data: []byte("SELECT 1")})
  160. if len(msgs) != 2 {
  161. t.Fatalf("got %d messages, want 2", len(msgs))
  162. }
  163. if code := errorCode(msgs[0]); code != ErrCodeProtocolViolation {
  164. t.Fatalf("error code = %q, want %q", code, ErrCodeProtocolViolation)
  165. }
  166. }
  167. func TestHandleQueryEmptyQueryReturnsEmptyQueryResponse(t *testing.T) {
  168. for _, data := range [][]byte{{0}, []byte(";\x00"), []byte(" \x00")} {
  169. c, client := newTestConnection(t)
  170. msgs := runQuery(t, c, client, &Message{Type: MsgQuery, Data: data})
  171. if len(msgs) != 2 {
  172. t.Fatalf("payload %q: got %d messages, want 2", data, len(msgs))
  173. }
  174. if msgs[0].Type != MsgEmptyQueryResponse {
  175. t.Fatalf("payload %q: first message type = %c, want %c", data, msgs[0].Type, MsgEmptyQueryResponse)
  176. }
  177. }
  178. }
  179. func TestHandleQueryFailedTransactionInterceptsSpecialQueries(t *testing.T) {
  180. queries := []string{
  181. "SELECT version()",
  182. "SELECT current_user",
  183. "SHOW server_version",
  184. "SELECT * FROM information_schema.tables",
  185. }
  186. for _, q := range queries {
  187. c, client := newTestConnection(t)
  188. c.txStatus = TxStatusFailed
  189. msgs := runQuery(t, c, client, &Message{Type: MsgQuery, Data: []byte(q + "\x00")})
  190. if len(msgs) != 2 {
  191. t.Fatalf("query %q: got %d messages, want 2", q, len(msgs))
  192. }
  193. if code := errorCode(msgs[0]); code != ErrCodeTransactionAborted {
  194. t.Fatalf("query %q: error code = %q, want %q", q, code, ErrCodeTransactionAborted)
  195. }
  196. }
  197. }
  198. func TestIsRollbackStatement(t *testing.T) {
  199. for _, sql := range []string{"ROLLBACK", "ROLLBACK TO SAVEPOINT sp1", "rollback;", " rollback ;"} {
  200. if !isRollbackStatement(sql) {
  201. t.Errorf("isRollbackStatement(%q) = false, want true", sql)
  202. }
  203. }
  204. for _, sql := range []string{"SELECT version()", "SHOW server_version", "SAVEPOINT sp1"} {
  205. if isRollbackStatement(sql) {
  206. t.Errorf("isRollbackStatement(%q) = true, want false", sql)
  207. }
  208. }
  209. for _, sql := range []string{"ROLLBACKBOGUS", "ROLLBACK; SELECT 1"} {
  210. if isRollbackStatement(sql) {
  211. t.Errorf("isRollbackStatement(%q) = true, want false", sql)
  212. }
  213. }
  214. }