array_param_test.go 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. package pgserver
  2. import (
  3. "bufio"
  4. "testing"
  5. )
  6. // arrayConnection returns a connection whose output is captured in memory, so
  7. // both simple and extended protocol responses can be decoded.
  8. func arrayConnection(t *testing.T) (*Connection, *countingConn) {
  9. t.Helper()
  10. c, _ := newDataConnection(t)
  11. conn := &countingConn{}
  12. c.conn = conn
  13. c.writer = bufio.NewWriter(conn)
  14. return c, conn
  15. }
  16. // runSimple sends a simple Query and returns the decoded response messages.
  17. func runSimple(t *testing.T, c *Connection, conn *countingConn, sql string) []*Message {
  18. t.Helper()
  19. if err := c.handleMessage(&Message{Type: MsgQuery, Data: append([]byte(sql), 0)}); err != nil {
  20. t.Fatalf("handleQuery %q: %v", sql, err)
  21. }
  22. return readAllMessages(t, conn.bytes())
  23. }
  24. // runExtendedParseBindExecute drives one extended-protocol cycle with a single
  25. // text-format parameter of the given OID.
  26. func runExtendedParseBindExecute(t *testing.T, c *Connection, conn *countingConn, sql string, oid int32, value string) []*Message {
  27. t.Helper()
  28. parse := NewMessageBuilder()
  29. parse.WriteString("s1")
  30. parse.WriteString(sql)
  31. parse.WriteInt16(1)
  32. parse.WriteInt32(oid)
  33. bind := NewMessageBuilder()
  34. bind.WriteString("p1")
  35. bind.WriteString("s1")
  36. bind.WriteInt16(0) // all-text parameter formats
  37. bind.WriteInt16(1)
  38. bind.WriteInt32(int32(len(value)))
  39. bind.WriteBytes([]byte(value))
  40. bind.WriteInt16(0) // all-text result formats
  41. execute := NewMessageBuilder()
  42. execute.WriteString("p1")
  43. execute.WriteInt32(0)
  44. for _, msg := range []*Message{
  45. {Type: MsgParse, Data: parse.Bytes()},
  46. {Type: MsgBind, Data: bind.Bytes()},
  47. {Type: MsgExecute, Data: execute.Bytes()},
  48. {Type: MsgSync},
  49. } {
  50. if err := c.handleMessage(msg); err != nil {
  51. t.Fatalf("handle %c: %v", msg.Type, err)
  52. }
  53. }
  54. return readAllMessages(t, conn.bytes())
  55. }
  56. func firstDataRow(t *testing.T, messages []*Message) []string {
  57. t.Helper()
  58. for _, msg := range messages {
  59. if msg.Type == MsgDataRow {
  60. return dataRowValues(t, msg)
  61. }
  62. }
  63. return nil
  64. }
  65. func findError(messages []*Message) *Message {
  66. for _, msg := range messages {
  67. if msg.Type == MsgErrorResponse {
  68. return msg
  69. }
  70. }
  71. return nil
  72. }
  73. // A pg client that binds text[] for "id = ANY($1)" gets the rows the array
  74. // describes instead of a rewritten query.
  75. func TestArrayParameterOverExtendedQuery(t *testing.T) {
  76. c, conn := arrayConnection(t)
  77. for _, sql := range []string{
  78. `CREATE TABLE t (id INTEGER PRIMARY KEY, tags TEXT[])`,
  79. `INSERT INTO t VALUES (1, '{a}'), (2, '{b}'), (3, '{c}')`,
  80. } {
  81. if _, err := c.executor.Execute(parseStmt(t, sql)); err != nil {
  82. t.Fatalf("%s: %v", sql, err)
  83. }
  84. }
  85. messages := runExtendedParseBindExecute(t, c, conn, `SELECT id FROM t WHERE id = ANY($1) ORDER BY id`, 1007, "{1,3}")
  86. if errMsg := findError(messages); errMsg != nil {
  87. t.Fatalf("extended ANY($1) failed: %s", errorCode(errMsg))
  88. }
  89. rows := [][]string{}
  90. for _, msg := range messages {
  91. if msg.Type == MsgDataRow {
  92. rows = append(rows, dataRowValues(t, msg))
  93. }
  94. }
  95. if len(rows) != 2 || rows[0][0] != "1" || rows[1][0] != "3" {
  96. t.Fatalf("ANY($1) rows = %v, want [[1] [3]]", rows)
  97. }
  98. // text[] uses the same text binding path.
  99. conn.reset()
  100. messages = runExtendedParseBindExecute(t, c, conn, `SELECT id FROM t WHERE id = ANY($1)`, 1009, "{1,2}")
  101. if errMsg := findError(messages); errMsg != nil {
  102. t.Fatalf("extended text[] ANY($1) failed: %s", errorCode(errMsg))
  103. }
  104. if row := firstDataRow(t, messages); len(row) != 1 {
  105. t.Fatalf("text[] ANY($1) returned %v", row)
  106. }
  107. // An unspecified parameter OID keeps the array literal untyped; ANY still
  108. // reads it as array text, which is what drivers that omit OIDs rely on.
  109. conn.reset()
  110. messages = runExtendedParseBindExecute(t, c, conn, `SELECT id FROM t WHERE id = ANY($1) ORDER BY id`, 0, "{2,3}")
  111. if errMsg := findError(messages); errMsg != nil {
  112. t.Fatalf("extended untyped ANY($1) failed: %s", errorCode(errMsg))
  113. }
  114. rows = nil
  115. for _, msg := range messages {
  116. if msg.Type == MsgDataRow {
  117. rows = append(rows, dataRowValues(t, msg))
  118. }
  119. }
  120. if len(rows) != 2 || rows[0][0] != "2" || rows[1][0] != "3" {
  121. t.Fatalf("untyped ANY($1) rows = %v, want [[2] [3]]", rows)
  122. }
  123. // NULL elements follow three-valued logic through the parameter path: rows
  124. // whose comparison is NULL are not returned, rows that match are.
  125. conn.reset()
  126. messages = runExtendedParseBindExecute(t, c, conn, `SELECT id FROM t WHERE id = ANY($1) ORDER BY id`, 1007, "{NULL,2}")
  127. if errMsg := findError(messages); errMsg != nil {
  128. t.Fatalf("extended NULL-element ANY($1) failed: %s", errorCode(errMsg))
  129. }
  130. if row := firstDataRow(t, messages); len(row) != 1 || row[0] != "2" {
  131. t.Fatalf("NULL-element ANY($1) row = %v, want [2]", row)
  132. }
  133. // An empty array parameter matches nothing and is not an error.
  134. conn.reset()
  135. messages = runExtendedParseBindExecute(t, c, conn, `SELECT id FROM t WHERE id = ANY($1)`, 1007, "{}")
  136. if errMsg := findError(messages); errMsg != nil {
  137. t.Fatalf("extended empty-array ANY($1) failed: %s", errorCode(errMsg))
  138. }
  139. if row := firstDataRow(t, messages); len(row) != 0 {
  140. t.Fatalf("empty-array ANY($1) returned %v, want no rows", row)
  141. }
  142. // ALL keeps its dual semantics through the parameter path.
  143. conn.reset()
  144. messages = runExtendedParseBindExecute(t, c, conn, `SELECT id FROM t WHERE id <> ALL($1)`, 1007, "{1,2}")
  145. if errMsg := findError(messages); errMsg != nil {
  146. t.Fatalf("extended ALL($1) failed: %s", errorCode(errMsg))
  147. }
  148. if row := firstDataRow(t, messages); len(row) != 1 || row[0] != "3" {
  149. t.Fatalf("ALL($1) row = %v, want [3]", row)
  150. }
  151. }
  152. func TestArrayParameterOverSimpleQuery(t *testing.T) {
  153. c, conn := arrayConnection(t)
  154. if _, err := c.executor.Execute(parseStmt(t, `CREATE TABLE t (id INTEGER PRIMARY KEY)`)); err != nil {
  155. t.Fatal(err)
  156. }
  157. for _, sql := range []string{
  158. `INSERT INTO t VALUES (1), (2), (3)`,
  159. } {
  160. if _, err := c.executor.Execute(parseStmt(t, sql)); err != nil {
  161. t.Fatalf("%s: %v", sql, err)
  162. }
  163. }
  164. messages := runSimple(t, c, conn, `SELECT id FROM t WHERE id = ANY('{1,3}') ORDER BY id`)
  165. if errMsg := findError(messages); errMsg != nil {
  166. t.Fatalf("simple ANY failed: %s", errorCode(errMsg))
  167. }
  168. rows := [][]string{}
  169. for _, msg := range messages {
  170. if msg.Type == MsgDataRow {
  171. rows = append(rows, dataRowValues(t, msg))
  172. }
  173. }
  174. if len(rows) != 2 || rows[0][0] != "1" || rows[1][0] != "3" {
  175. t.Fatalf("simple ANY rows = %v", rows)
  176. }
  177. }
  178. func TestArrayResultMetadataOIDs(t *testing.T) {
  179. c, conn := arrayConnection(t)
  180. for _, sql := range []string{
  181. `CREATE TABLE t (id INTEGER PRIMARY KEY, tags TEXT[], nums INTEGER[], big BIGINT[], small SMALLINT[], flags BOOLEAN[], reals REAL[], doubles DOUBLE PRECISION[], nums2 NUMERIC[], names VARCHAR(10)[])`,
  182. `INSERT INTO t VALUES (1, '{a}', '{1}', '{1}', '{1}', '{t}', '{1.5}', '{1.5}', '{1.5}', '{x}')`,
  183. } {
  184. if _, err := c.executor.Execute(parseStmt(t, sql)); err != nil {
  185. t.Fatalf("%s: %v", sql, err)
  186. }
  187. }
  188. messages := runSimple(t, c, conn, `SELECT tags, nums, big, small, flags, reals, doubles, nums2, names FROM t`)
  189. rd := findRowDescription(t, messages)
  190. oids := rowDescriptionOIDs(t, rd.Data)
  191. want := []int32{1009, 1007, 1016, 1016, 1000, 1021, 1022, 1231, 1015}
  192. if len(oids) != len(want) {
  193. t.Fatalf("array OIDs = %v, want %v", oids, want)
  194. }
  195. for i, oid := range want {
  196. if oids[i] != oid {
  197. t.Errorf("column %d OID = %d, want %d", i, oids[i], oid)
  198. }
  199. }
  200. conn.reset()
  201. messages = runSimple(t, c, conn, `SELECT '{1,2}'::INTEGER[] AS arr`)
  202. rd = findRowDescription(t, messages)
  203. if oid := rowDescriptionOIDs(t, rd.Data)[0]; oid != 1007 {
  204. t.Fatalf("cast array OID = %d, want 1007", oid)
  205. }
  206. if row := firstDataRow(t, messages); row[0] != "{1,2}" {
  207. t.Fatalf("cast array text = %v, want {1,2}", row)
  208. }
  209. }
  210. func TestArrayParameterBinaryFormatRejected(t *testing.T) {
  211. c, conn := arrayConnection(t)
  212. if _, err := c.executor.Execute(parseStmt(t, `CREATE TABLE t (id INTEGER PRIMARY KEY)`)); err != nil {
  213. t.Fatal(err)
  214. }
  215. parse := NewMessageBuilder()
  216. parse.WriteString("s1")
  217. parse.WriteString(`SELECT id FROM t WHERE id = ANY($1)`)
  218. parse.WriteInt16(1)
  219. parse.WriteInt32(1007) // int4[]
  220. bind := NewMessageBuilder()
  221. bind.WriteString("p1")
  222. bind.WriteString("s1")
  223. bind.WriteInt16(1) // one parameter format code
  224. bind.WriteInt16(1) // binary
  225. bind.WriteInt16(1) // one parameter
  226. bind.WriteInt32(8)
  227. bind.WriteBytes([]byte{0, 0, 0, 1, 0, 0, 0, 2})
  228. bind.WriteInt16(0)
  229. for _, msg := range []*Message{
  230. {Type: MsgParse, Data: parse.Bytes()},
  231. {Type: MsgBind, Data: bind.Bytes()},
  232. {Type: MsgSync},
  233. } {
  234. if err := c.handleMessage(msg); err != nil {
  235. t.Fatalf("handle %c: %v", msg.Type, err)
  236. }
  237. }
  238. messages := readAllMessages(t, conn.bytes())
  239. errMsg := findError(messages)
  240. if errMsg == nil {
  241. t.Fatal("binary array parameter was accepted")
  242. }
  243. if code := errorCode(errMsg); code != ErrCodeFeatureNotSupported {
  244. t.Fatalf("binary array error code = %q, want %q", code, ErrCodeFeatureNotSupported)
  245. }
  246. }
  247. func TestMalformedArrayParameterReportsInvalidText(t *testing.T) {
  248. c, conn := arrayConnection(t)
  249. for _, sql := range []string{
  250. `CREATE TABLE t (id INTEGER PRIMARY KEY)`,
  251. `INSERT INTO t VALUES (1)`,
  252. } {
  253. if _, err := c.executor.Execute(parseStmt(t, sql)); err != nil {
  254. t.Fatalf("%s: %v", sql, err)
  255. }
  256. }
  257. messages := runExtendedParseBindExecute(t, c, conn, `SELECT id FROM t WHERE id = ANY($1)`, 1007, "{1,2")
  258. errMsg := findError(messages)
  259. if errMsg == nil {
  260. t.Fatal("malformed array parameter was accepted")
  261. }
  262. if code := errorCode(errMsg); code != ErrCodeInvalidText {
  263. t.Fatalf("malformed array error code = %q, want %q", code, ErrCodeInvalidText)
  264. }
  265. conn.reset()
  266. messages = runExtendedParseBindExecute(t, c, conn, `SELECT id FROM t WHERE id = ANY($1)`, 1007, "{not-an-int}")
  267. errMsg = findError(messages)
  268. if errMsg == nil {
  269. t.Fatal("non-integer element was accepted for int4[]")
  270. }
  271. if code := errorCode(errMsg); code != ErrCodeInvalidText {
  272. t.Fatalf("invalid element error code = %q, want %q", code, ErrCodeInvalidText)
  273. }
  274. }
  275. // reset discards captured output so one test can inspect several responses.
  276. func (c *countingConn) reset() {
  277. c.mu.Lock()
  278. defer c.mu.Unlock()
  279. c.buf.Reset()
  280. }