result_binary_test.go 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076
  1. package pgserver
  2. import (
  3. "bufio"
  4. "bytes"
  5. "encoding/binary"
  6. "math"
  7. "testing"
  8. )
  9. // extendedResultConnection returns a connection whose protocol output is
  10. // captured in memory, so Bind/Execute responses can be decoded.
  11. func extendedResultConnection(t *testing.T) (*Connection, *countingConn) {
  12. t.Helper()
  13. c, _ := newDataConnection(t)
  14. conn := &countingConn{}
  15. c.conn = conn
  16. c.writer = bufio.NewWriter(conn)
  17. return c, conn
  18. }
  19. func parseMessage(name, sql string) *Message {
  20. mb := NewMessageBuilder()
  21. mb.WriteString(name)
  22. mb.WriteString(sql)
  23. mb.WriteInt16(0) // no parameter OIDs
  24. return &Message{Type: MsgParse, Data: mb.Bytes()}
  25. }
  26. // bindResultMessage binds a portal with text parameters and the given result
  27. // format list.
  28. func bindResultMessage(portal, statement string, params []string, resultFormats []int16) *Message {
  29. mb := NewMessageBuilder()
  30. mb.WriteString(portal)
  31. mb.WriteString(statement)
  32. mb.WriteInt16(0) // all-text parameter formats
  33. mb.WriteInt16(int16(len(params)))
  34. for _, param := range params {
  35. mb.WriteInt32(int32(len(param)))
  36. mb.WriteBytes([]byte(param))
  37. }
  38. mb.WriteInt16(int16(len(resultFormats)))
  39. for _, format := range resultFormats {
  40. mb.WriteInt16(format)
  41. }
  42. return &Message{Type: MsgBind, Data: mb.Bytes()}
  43. }
  44. func executeMessage(portal string) *Message {
  45. mb := NewMessageBuilder()
  46. mb.WriteString(portal)
  47. mb.WriteInt32(0)
  48. return &Message{Type: MsgExecute, Data: mb.Bytes()}
  49. }
  50. func sendExtended(t *testing.T, c *Connection, messages ...*Message) {
  51. t.Helper()
  52. for _, message := range messages {
  53. if err := c.handleMessage(message); err != nil {
  54. t.Fatalf("handle %c: %v", message.Type, err)
  55. }
  56. }
  57. }
  58. // dataRowRawValues splits a DataRow into its raw field bytes; nil means SQL
  59. // NULL.
  60. func dataRowRawValues(t *testing.T, msg *Message) [][]byte {
  61. t.Helper()
  62. if msg.Type != MsgDataRow {
  63. t.Fatalf("message type = %c, want DataRow", msg.Type)
  64. }
  65. if len(msg.Data) < 2 {
  66. t.Fatal("short DataRow")
  67. }
  68. count := int(binary.BigEndian.Uint16(msg.Data[:2]))
  69. pos := 2
  70. values := make([][]byte, 0, count)
  71. for i := 0; i < count; i++ {
  72. if pos+4 > len(msg.Data) {
  73. t.Fatal("short DataRow field length")
  74. }
  75. length := int32(binary.BigEndian.Uint32(msg.Data[pos : pos+4]))
  76. pos += 4
  77. if length == -1 {
  78. values = append(values, nil)
  79. continue
  80. }
  81. if length < 0 || pos+int(length) > len(msg.Data) {
  82. t.Fatalf("field %d length %d out of range", i, length)
  83. }
  84. values = append(values, msg.Data[pos:pos+int(length)])
  85. pos += int(length)
  86. }
  87. if pos != len(msg.Data) {
  88. t.Fatalf("DataRow has %d trailing bytes", len(msg.Data)-pos)
  89. }
  90. return values
  91. }
  92. func dataRowsRaw(t *testing.T, messages []*Message) [][][]byte {
  93. t.Helper()
  94. var rows [][][]byte
  95. for _, message := range messages {
  96. if message.Type == MsgDataRow {
  97. rows = append(rows, dataRowRawValues(t, message))
  98. }
  99. }
  100. return rows
  101. }
  102. func rowDescriptionFormats(t *testing.T, data []byte) []int16 {
  103. t.Helper()
  104. if len(data) < 2 {
  105. t.Fatal("row description too short")
  106. }
  107. count := int(binary.BigEndian.Uint16(data[:2]))
  108. pos := 2
  109. formats := make([]int16, 0, count)
  110. for i := 0; i < count; i++ {
  111. end := bytes.IndexByte(data[pos:], 0)
  112. if end < 0 {
  113. t.Fatal("unterminated column name")
  114. }
  115. pos += end + 1 // name
  116. pos += 4 // table OID
  117. pos += 2 // column attribute number
  118. pos += 4 // type OID
  119. pos += 2 // type size
  120. pos += 4 // type modifier
  121. if pos+2 > len(data) {
  122. t.Fatal("truncated row description")
  123. }
  124. formats = append(formats, int16(binary.BigEndian.Uint16(data[pos:pos+2])))
  125. pos += 2
  126. }
  127. return formats
  128. }
  129. func messageTypes(messages []*Message) string {
  130. var types []byte
  131. for _, message := range messages {
  132. types = append(types, message.Type)
  133. }
  134. return string(types)
  135. }
  136. func prepareBinaryResultTable(t *testing.T, c *Connection) {
  137. t.Helper()
  138. for _, sql := range []string{
  139. `CREATE TABLE t (id INTEGER PRIMARY KEY, flag BOOLEAN, name TEXT, amount REAL, score DOUBLE, doc JSONB, data BLOB, tags TEXT[], nums NUMERIC[], flags BOOLEAN[])`,
  140. `INSERT INTO t VALUES (1, 1, 'alpha', 1.5, 2.25, '{"a": 1}', X'00ff', '{a,"b c"}', '{1.10,12345678901234567890}', '{t,f}')`,
  141. `INSERT INTO t VALUES (2, 0, NULL, NULL, NULL, NULL, NULL, '{}', '{NULL,2}', '{f,NULL}')`,
  142. } {
  143. if _, err := c.executor.Execute(parseStmt(t, sql)); err != nil {
  144. t.Fatalf("%s: %v", sql, err)
  145. }
  146. }
  147. }
  148. const binaryResultSelect = `SELECT id, flag, name, amount, score, doc, data, tags, nums, flags FROM t ORDER BY id`
  149. // An empty Bind result format list keeps every column in text format, and the
  150. // RowDescription reports format 0 for all of them.
  151. func TestBinaryResultsAllTextByDefault(t *testing.T) {
  152. c, conn := extendedResultConnection(t)
  153. prepareBinaryResultTable(t, c)
  154. sendExtended(t, c,
  155. parseMessage("s", binaryResultSelect),
  156. bindResultMessage("p", "s", nil, nil),
  157. executeMessage("p"),
  158. &Message{Type: MsgSync},
  159. )
  160. messages := readAllMessages(t, conn.bytes())
  161. if msg := findError(messages); msg != nil {
  162. t.Fatalf("text result failed: %s", errorCode(msg))
  163. }
  164. rd := findRowDescription(t, messages)
  165. for i, format := range rowDescriptionFormats(t, rd.Data) {
  166. if format != 0 {
  167. t.Fatalf("column %d format = %d, want text", i, format)
  168. }
  169. }
  170. rows := dataRowsRaw(t, messages)
  171. if len(rows) != 2 {
  172. t.Fatalf("got %d rows, want 2", len(rows))
  173. }
  174. if string(rows[0][2]) != "alpha" || string(rows[0][3]) != "1.5" {
  175. t.Fatalf("text row = %q", rows[0])
  176. }
  177. }
  178. // A single result format code repeats for every column, so one binary entry
  179. // makes the whole result binary. Every supported scalar and array type is
  180. // checked against its exact wire representation.
  181. func TestBinaryResultsSingleFormatRepeats(t *testing.T) {
  182. c, conn := extendedResultConnection(t)
  183. prepareBinaryResultTable(t, c)
  184. sendExtended(t, c,
  185. parseMessage("s", binaryResultSelect),
  186. bindResultMessage("p", "s", nil, []int16{1}),
  187. executeMessage("p"),
  188. &Message{Type: MsgSync},
  189. )
  190. messages := readAllMessages(t, conn.bytes())
  191. if msg := findError(messages); msg != nil {
  192. t.Fatalf("binary result failed: %s", errorCode(msg))
  193. }
  194. rd := findRowDescription(t, messages)
  195. formats := rowDescriptionFormats(t, rd.Data)
  196. if len(formats) != 10 {
  197. t.Fatalf("RowDescription has %d columns, want 10", len(formats))
  198. }
  199. for i, format := range formats {
  200. if format != 1 {
  201. t.Fatalf("column %d format = %d, want binary", i, format)
  202. }
  203. }
  204. // The OIDs do not change with the format.
  205. oids := rowDescriptionOIDs(t, rd.Data)
  206. wantOIDs := []int32{23, 16, 25, 700, 701, 3802, 17, 1009, 1231, 1000}
  207. for i, want := range wantOIDs {
  208. if oids[i] != want {
  209. t.Errorf("column %d OID = %d, want %d", i, oids[i], want)
  210. }
  211. }
  212. rows := dataRowsRaw(t, messages)
  213. if len(rows) != 2 {
  214. t.Fatalf("got %d rows, want 2", len(rows))
  215. }
  216. row := rows[0]
  217. assertBytes(t, "int4", row[0], []byte{0, 0, 0, 1})
  218. assertBytes(t, "bool", row[1], []byte{1})
  219. assertBytes(t, "text", row[2], []byte("alpha"))
  220. assertBytes(t, "float4", row[3], binary.BigEndian.AppendUint32(nil, math.Float32bits(1.5)))
  221. assertBytes(t, "float8", row[4], binary.BigEndian.AppendUint64(nil, math.Float64bits(2.25)))
  222. if len(row[5]) == 0 || row[5][0] != 1 {
  223. t.Fatalf("jsonb value %v does not start with the version byte", row[5])
  224. }
  225. if !bytes.Contains(row[5][1:], []byte(`"a"`)) {
  226. t.Fatalf("jsonb value %q does not carry the JSON text", row[5])
  227. }
  228. assertBytes(t, "bytea", row[6], []byte{0x00, 0xff})
  229. // text[]: one dimension, two elements, lower bound 1.
  230. assertArrayHeader(t, "text[]", row[7], 25, 2, false)
  231. if got := arrayElements(t, row[7]); len(got) != 2 || string(got[0]) != "a" || string(got[1]) != "b c" {
  232. t.Fatalf("text[] elements = %q", got)
  233. }
  234. // numeric[]: 1.10 keeps its scale, and the 20-digit value stays exact.
  235. assertArrayHeader(t, "numeric[]", row[8], 1700, 2, false)
  236. numericElements := arrayElements(t, row[8])
  237. assertNumericBinary(t, "1.10", numericElements[0], 2, 0, 2, []uint16{1, 1000})
  238. assertNumericBinary(t, "12345678901234567890", numericElements[1], 5, 4, 0, []uint16{1234, 5678, 9012, 3456, 7890})
  239. // bool[]: t/f.
  240. assertArrayHeader(t, "bool[]", row[9], 16, 2, false)
  241. if got := arrayElements(t, row[9]); len(got) != 2 || got[0][0] != 1 || got[1][0] != 0 {
  242. t.Fatalf("bool[] elements = %v", got)
  243. }
  244. // Row 2 exercises NULL columns, an empty array, and NULL array elements.
  245. second := rows[1]
  246. assertBytes(t, "int4 second row", second[0], []byte{0, 0, 0, 2})
  247. assertBytes(t, "bool second row", second[1], []byte{0})
  248. for _, index := range []int{2, 3, 4, 5, 6} {
  249. if second[index] != nil {
  250. t.Errorf("column %d = %v, want NULL", index, second[index])
  251. }
  252. }
  253. // An empty array has no dimension header.
  254. assertBytes(t, "empty text[]", second[7], []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25})
  255. assertArrayHeader(t, "numeric[] with NULL", second[8], 1700, 2, true)
  256. numericSecond := arrayElements(t, second[8])
  257. if numericSecond[0] != nil {
  258. t.Fatalf("numeric[] first element = %v, want NULL", numericSecond[0])
  259. }
  260. assertNumericBinary(t, "2", numericSecond[1], 1, 0, 0, []uint16{2})
  261. assertArrayHeader(t, "bool[] with NULL", second[9], 16, 2, true)
  262. boolSecond := arrayElements(t, second[9])
  263. if boolSecond[0] == nil || boolSecond[0][0] != 0 || boolSecond[1] != nil {
  264. t.Fatalf("bool[] elements = %v, want [f NULL]", boolSecond)
  265. }
  266. }
  267. // Mixed lists choose text and binary per column in one result.
  268. func TestBinaryResultsMixedPerColumnFormats(t *testing.T) {
  269. c, conn := extendedResultConnection(t)
  270. prepareBinaryResultTable(t, c)
  271. // id text, flag binary, name text, amount binary, score text, doc binary,
  272. // data text, tags binary, nums text, flags binary.
  273. formats := []int16{0, 1, 0, 1, 0, 1, 0, 1, 0, 1}
  274. sendExtended(t, c,
  275. parseMessage("s", binaryResultSelect),
  276. bindResultMessage("p", "s", nil, formats),
  277. executeMessage("p"),
  278. &Message{Type: MsgSync},
  279. )
  280. messages := readAllMessages(t, conn.bytes())
  281. if msg := findError(messages); msg != nil {
  282. t.Fatalf("mixed result failed: %s", errorCode(msg))
  283. }
  284. rd := findRowDescription(t, messages)
  285. if got := rowDescriptionFormats(t, rd.Data); !equalInt16(got, formats) {
  286. t.Fatalf("RowDescription formats = %v, want %v", got, formats)
  287. }
  288. rows := dataRowsRaw(t, messages)
  289. if len(rows) != 2 {
  290. t.Fatalf("got %d rows, want 2", len(rows))
  291. }
  292. row := rows[0]
  293. // Text columns carry their text spelling.
  294. assertBytes(t, "text id", row[0], []byte("1"))
  295. assertBytes(t, "text name", row[2], []byte("alpha"))
  296. assertBytes(t, "text score", row[4], []byte("2.25"))
  297. assertBytes(t, "text data", row[6], []byte(`\x00ff`))
  298. assertBytes(t, "text nums", row[8], []byte("{1.10,12345678901234567890}"))
  299. // Binary columns carry their binary representation.
  300. assertBytes(t, "binary flag", row[1], []byte{1})
  301. assertBytes(t, "binary amount", row[3], binary.BigEndian.AppendUint32(nil, math.Float32bits(1.5)))
  302. if len(row[5]) == 0 || row[5][0] != 1 {
  303. t.Fatalf("jsonb value %v does not start with the version byte", row[5])
  304. }
  305. assertArrayHeader(t, "binary tags", row[7], 25, 2, false)
  306. assertArrayHeader(t, "binary flags", row[9], 16, 2, false)
  307. }
  308. // Formats belong to the portal, not the statement: two portals over one
  309. // prepared statement can use different formats, and re-binding a portal
  310. // replaces its list.
  311. func TestBinaryResultsArePerPortal(t *testing.T) {
  312. c, conn := extendedResultConnection(t)
  313. prepareBinaryResultTable(t, c)
  314. sql := `SELECT id, flag FROM t ORDER BY id`
  315. sendExtended(t, c,
  316. parseMessage("s", sql),
  317. bindResultMessage("text_portal", "s", nil, nil),
  318. bindResultMessage("binary_portal", "s", nil, []int16{1}),
  319. executeMessage("text_portal"),
  320. executeMessage("binary_portal"),
  321. bindResultMessage("text_portal", "s", nil, []int16{0}),
  322. executeMessage("text_portal"),
  323. &Message{Type: MsgSync},
  324. )
  325. messages := readAllMessages(t, conn.bytes())
  326. if msg := findError(messages); msg != nil {
  327. t.Fatalf("per-portal result failed: %s", errorCode(msg))
  328. }
  329. var descriptions [][]int16
  330. var rows [][][]byte
  331. for _, message := range messages {
  332. switch message.Type {
  333. case MsgRowDescription:
  334. descriptions = append(descriptions, rowDescriptionFormats(t, message.Data))
  335. case MsgDataRow:
  336. rows = append(rows, dataRowRawValues(t, message))
  337. }
  338. }
  339. if len(descriptions) != 3 {
  340. t.Fatalf("got %d RowDescriptions, want 3", len(descriptions))
  341. }
  342. if !equalInt16(descriptions[0], []int16{0, 0}) {
  343. t.Fatalf("text portal formats = %v", descriptions[0])
  344. }
  345. if !equalInt16(descriptions[1], []int16{1, 1}) {
  346. t.Fatalf("binary portal formats = %v", descriptions[1])
  347. }
  348. if !equalInt16(descriptions[2], []int16{0, 0}) {
  349. t.Fatalf("re-bound text portal formats = %v", descriptions[2])
  350. }
  351. // The two text portals put "1" and "1" in the first row; the binary portal
  352. // puts four bytes.
  353. if len(rows) != 6 {
  354. t.Fatalf("got %d rows, want 6", len(rows))
  355. }
  356. if string(rows[0][0]) != "1" || string(rows[1][0]) != "2" {
  357. t.Fatalf("text portal rows = %q %q", rows[0][0], rows[1][0])
  358. }
  359. assertBytes(t, "binary portal id", rows[2][0], []byte{0, 0, 0, 1})
  360. if string(rows[4][0]) != "1" || string(rows[5][0]) != "2" {
  361. t.Fatalf("re-bound text portal rows = %q %q", rows[4][0], rows[5][0])
  362. }
  363. }
  364. // A format list whose length is neither one nor the column count is a protocol
  365. // violation, and no RowDescription or DataRow is sent before the error.
  366. func TestBinaryResultsFormatCountMismatch(t *testing.T) {
  367. c, conn := extendedResultConnection(t)
  368. prepareBinaryResultTable(t, c)
  369. sendExtended(t, c,
  370. parseMessage("s", `SELECT id, flag FROM t`),
  371. bindResultMessage("p", "s", nil, []int16{0, 1, 0}),
  372. executeMessage("p"),
  373. &Message{Type: MsgSync},
  374. )
  375. messages := readAllMessages(t, conn.bytes())
  376. if got := messageTypes(messages); got != "12"+string(MsgErrorResponse)+string(MsgReadyForQuery) {
  377. t.Fatalf("messages = %q", got)
  378. }
  379. if code := errorCode(findError(messages)); code != ErrCodeProtocolViolation {
  380. t.Fatalf("SQLSTATE = %s, want %s", code, ErrCodeProtocolViolation)
  381. }
  382. if c.extendedFailed {
  383. t.Fatal("Sync did not clear the extended-failure state")
  384. }
  385. // The connection is reusable after the rejected cycle.
  386. conn.reset()
  387. sendExtended(t, c,
  388. parseMessage("s2", `SELECT id FROM t ORDER BY id`),
  389. bindResultMessage("p2", "s2", nil, []int16{1}),
  390. executeMessage("p2"),
  391. &Message{Type: MsgSync},
  392. )
  393. messages = readAllMessages(t, conn.bytes())
  394. if msg := findError(messages); msg != nil {
  395. t.Fatalf("query after format error failed: %s", errorCode(msg))
  396. }
  397. rows := dataRowsRaw(t, messages)
  398. if len(rows) != 2 || !bytes.Equal(rows[0][0], []byte{0, 0, 0, 1}) {
  399. t.Fatalf("rows after format error = %v", rows)
  400. }
  401. }
  402. // A binary request for a type the wire layer cannot encode fails with 0A000
  403. // before any result frame, including when the statement has rows to return.
  404. func TestBinaryResultsUnsupportedOID(t *testing.T) {
  405. c, conn := extendedResultConnection(t)
  406. if _, err := c.executor.Execute(parseStmt(t, `CREATE TABLE stamps (id INTEGER PRIMARY KEY, created_at TIMESTAMP)`)); err != nil {
  407. t.Fatal(err)
  408. }
  409. if _, err := c.executor.Execute(parseStmt(t, `INSERT INTO stamps VALUES (1, '2024-01-02 03:04:05')`)); err != nil {
  410. t.Fatal(err)
  411. }
  412. sendExtended(t, c,
  413. parseMessage("s", `SELECT id, created_at FROM stamps`),
  414. bindResultMessage("p", "s", nil, []int16{1}),
  415. executeMessage("p"),
  416. &Message{Type: MsgSync},
  417. )
  418. messages := readAllMessages(t, conn.bytes())
  419. if got := messageTypes(messages); got != "12"+string(MsgErrorResponse)+string(MsgReadyForQuery) {
  420. t.Fatalf("messages = %q", got)
  421. }
  422. if code := errorCode(findError(messages)); code != ErrCodeFeatureNotSupported {
  423. t.Fatalf("SQLSTATE = %s, want %s", code, ErrCodeFeatureNotSupported)
  424. }
  425. // Text format for the same statement still works.
  426. conn.reset()
  427. sendExtended(t, c,
  428. parseMessage("s2", `SELECT created_at FROM stamps`),
  429. bindResultMessage("p2", "s2", nil, nil),
  430. executeMessage("p2"),
  431. &Message{Type: MsgSync},
  432. )
  433. messages = readAllMessages(t, conn.bytes())
  434. if msg := findError(messages); msg != nil {
  435. t.Fatalf("text query after unsupported binary failed: %s", errorCode(msg))
  436. }
  437. if rows := dataRowsRaw(t, messages); len(rows) != 1 || string(rows[0][0]) != "2024-01-02 03:04:05" {
  438. t.Fatalf("text rows = %q", rows)
  439. }
  440. }
  441. // A rejected format inside an explicit transaction block marks the transaction
  442. // failed, as any statement error does, and no rows are emitted.
  443. func TestBinaryResultsErrorFailsTransaction(t *testing.T) {
  444. c, conn := extendedResultConnection(t)
  445. if _, err := c.executor.Execute(parseStmt(t, `CREATE TABLE t (id INTEGER PRIMARY KEY)`)); err != nil {
  446. t.Fatal(err)
  447. }
  448. sendExtended(t, c,
  449. &Message{Type: MsgQuery, Data: []byte("BEGIN\x00")},
  450. parseMessage("s", `SELECT id FROM t`),
  451. bindResultMessage("p", "s", nil, []int16{0, 1}),
  452. executeMessage("p"),
  453. &Message{Type: MsgSync},
  454. )
  455. messages := readAllMessages(t, conn.bytes())
  456. ready := messages[len(messages)-1]
  457. if ready.Type != MsgReadyForQuery || ready.Data[0] != TxStatusFailed {
  458. t.Fatalf("transaction status = %q, want failed", messageTypes(messages))
  459. }
  460. if code := errorCode(findError(messages)); code != ErrCodeProtocolViolation {
  461. t.Fatalf("SQLSTATE = %s, want %s", code, ErrCodeProtocolViolation)
  462. }
  463. // While failed, the next statement is refused with 25P02.
  464. conn.reset()
  465. sendExtended(t, c,
  466. parseMessage("s2", `SELECT id FROM t`),
  467. bindResultMessage("p2", "s2", nil, nil),
  468. executeMessage("p2"),
  469. &Message{Type: MsgSync},
  470. )
  471. messages = readAllMessages(t, conn.bytes())
  472. if code := errorCode(findError(messages)); code != ErrCodeTransactionAborted {
  473. t.Fatalf("SQLSTATE = %s, want %s", code, ErrCodeTransactionAborted)
  474. }
  475. }
  476. // appendNumericText encodes exact decimal text into base-10000 groups, keeping
  477. // the display scale and rejecting values that are not decimal numbers.
  478. func TestAppendNumericTextEncoding(t *testing.T) {
  479. cases := []struct {
  480. text string
  481. ndigits uint16
  482. weight int16
  483. sign uint16
  484. dscale uint16
  485. digits []uint16
  486. }{
  487. {"0", 0, 0, 0, 0, nil},
  488. {"1.10", 2, 0, 0, 2, []uint16{1, 1000}},
  489. {"-2.5", 2, 0, 0x4000, 1, []uint16{2, 5000}},
  490. {"12345678901234567890", 5, 4, 0, 0, []uint16{1234, 5678, 9012, 3456, 7890}},
  491. {"1e5", 1, 1, 0, 0, []uint16{10}},
  492. {"0.00001", 1, -2, 0, 5, []uint16{1000}},
  493. {"-0.000", 0, 0, 0, 3, nil},
  494. }
  495. for _, tc := range cases {
  496. t.Run(tc.text, func(t *testing.T) {
  497. encoded, err := appendNumericText(nil, tc.text)
  498. if err != nil {
  499. t.Fatalf("appendNumericText(%q): %v", tc.text, err)
  500. }
  501. assertNumericBinary(t, tc.text, encoded, tc.ndigits, tc.weight, tc.dscale, tc.digits, tc.sign)
  502. })
  503. }
  504. for _, text := range []string{"", "abc", "1.2.3", "NaN", "1e999999"} {
  505. if _, err := appendNumericText(nil, text); err == nil {
  506. t.Errorf("appendNumericText(%q) succeeded, want an error", text)
  507. }
  508. }
  509. }
  510. // assertNumericBinary checks a binary numeric payload.
  511. func assertNumericBinary(t *testing.T, label string, data []byte, ndigits uint16, weight int16, dscale uint16, digits []uint16, sign ...uint16) {
  512. t.Helper()
  513. wantSign := uint16(0)
  514. if len(sign) > 0 {
  515. wantSign = sign[0]
  516. }
  517. if len(data) != 8+2*len(digits) {
  518. t.Fatalf("%s numeric length = %d, want %d (%v)", label, len(data), 8+2*len(digits), data)
  519. }
  520. if got := binary.BigEndian.Uint16(data[0:2]); got != ndigits {
  521. t.Errorf("%s ndigits = %d, want %d", label, got, ndigits)
  522. }
  523. if got := int16(binary.BigEndian.Uint16(data[2:4])); got != weight {
  524. t.Errorf("%s weight = %d, want %d", label, got, weight)
  525. }
  526. if got := binary.BigEndian.Uint16(data[4:6]); got != wantSign {
  527. t.Errorf("%s sign = %#x, want %#x", label, got, wantSign)
  528. }
  529. if got := binary.BigEndian.Uint16(data[6:8]); got != dscale {
  530. t.Errorf("%s dscale = %d, want %d", label, got, dscale)
  531. }
  532. for i, want := range digits {
  533. if got := binary.BigEndian.Uint16(data[8+2*i : 10+2*i]); got != want {
  534. t.Errorf("%s digit %d = %d, want %d", label, i, got, want)
  535. }
  536. }
  537. }
  538. func assertArrayHeader(t *testing.T, label string, data []byte, elementOID int32, count int, hasNull bool) {
  539. t.Helper()
  540. if len(data) < 12 {
  541. t.Fatalf("%s array is %d bytes, want a header", label, len(data))
  542. }
  543. if ndim := int32(binary.BigEndian.Uint32(data[0:4])); ndim != 1 {
  544. t.Fatalf("%s ndim = %d, want 1", label, ndim)
  545. }
  546. wantNull := uint32(0)
  547. if hasNull {
  548. wantNull = 1
  549. }
  550. if got := binary.BigEndian.Uint32(data[4:8]); got != wantNull {
  551. t.Errorf("%s hasnull = %d, want %d", label, got, wantNull)
  552. }
  553. if got := int32(binary.BigEndian.Uint32(data[8:12])); got != elementOID {
  554. t.Errorf("%s element OID = %d, want %d", label, got, elementOID)
  555. }
  556. if len(data) < 20 {
  557. t.Fatalf("%s array is %d bytes, want a dimension header", label, len(data))
  558. }
  559. if got := int32(binary.BigEndian.Uint32(data[12:16])); got != int32(count) {
  560. t.Errorf("%s dimension length = %d, want %d", label, got, count)
  561. }
  562. if got := int32(binary.BigEndian.Uint32(data[16:20])); got != 1 {
  563. t.Errorf("%s lower bound = %d, want 1", label, got)
  564. }
  565. }
  566. // arrayElements splits the element payloads of a one-dimensional binary array.
  567. func arrayElements(t *testing.T, data []byte) [][]byte {
  568. t.Helper()
  569. if len(data) < 20 {
  570. t.Fatalf("array is %d bytes, want a dimension header", len(data))
  571. }
  572. count := int(binary.BigEndian.Uint32(data[12:16]))
  573. pos := 20
  574. elements := make([][]byte, 0, count)
  575. for i := 0; i < count; i++ {
  576. if pos+4 > len(data) {
  577. t.Fatalf("array element %d is truncated", i)
  578. }
  579. length := int32(binary.BigEndian.Uint32(data[pos : pos+4]))
  580. pos += 4
  581. if length == -1 {
  582. elements = append(elements, nil)
  583. continue
  584. }
  585. if length < 0 || pos+int(length) > len(data) {
  586. t.Fatalf("array element %d length %d out of range", i, length)
  587. }
  588. elements = append(elements, data[pos:pos+int(length)])
  589. pos += int(length)
  590. }
  591. if pos != len(data) {
  592. t.Fatalf("array has %d trailing bytes", len(data)-pos)
  593. }
  594. return elements
  595. }
  596. func assertBytes(t *testing.T, label string, got, want []byte) {
  597. t.Helper()
  598. if !bytes.Equal(got, want) {
  599. t.Fatalf("%s = %q (%v), want %q (%v)", label, got, got, want, want)
  600. }
  601. }
  602. func equalInt16(left, right []int16) bool {
  603. if len(left) != len(right) {
  604. return false
  605. }
  606. for i := range left {
  607. if left[i] != right[i] {
  608. return false
  609. }
  610. }
  611. return true
  612. }
  613. // Describe never exposes the portal's bound formats: statement and portal
  614. // Describe both return NoData, and the formats travel with the RowDescription
  615. // emitted at Execute.
  616. func TestBinaryResultsDescribeStaysNoData(t *testing.T) {
  617. c, conn := extendedResultConnection(t)
  618. prepareBinaryResultTable(t, c)
  619. sendExtended(t, c,
  620. parseMessage("s", `SELECT id, flag FROM t`),
  621. bindResultMessage("p", "s", nil, []int16{1}),
  622. &Message{Type: MsgDescribe, Data: []byte{'S', 's', 0}},
  623. &Message{Type: MsgDescribe, Data: []byte{'P', 'p', 0}},
  624. &Message{Type: MsgSync},
  625. )
  626. messages := readAllMessages(t, conn.bytes())
  627. if got := messageTypes(messages); got != "12tnnZ" {
  628. t.Fatalf("messages = %q, want ParseComplete/BindComplete/ParameterDescription/NoData/NoData/ReadyForQuery", got)
  629. }
  630. }
  631. // Simple-query results keep text format; only a Bind result format list can
  632. // request binary columns.
  633. func TestSimpleQueryResultsStayText(t *testing.T) {
  634. c, conn := extendedResultConnection(t)
  635. prepareBinaryResultTable(t, c)
  636. messages := runSimple(t, c, conn, `SELECT id, flag, tags FROM t ORDER BY id`)
  637. if msg := findError(messages); msg != nil {
  638. t.Fatalf("simple query failed: %s", errorCode(msg))
  639. }
  640. rd := findRowDescription(t, messages)
  641. for i, format := range rowDescriptionFormats(t, rd.Data) {
  642. if format != 0 {
  643. t.Fatalf("column %d format = %d, want text", i, format)
  644. }
  645. }
  646. rows := dataRowsRaw(t, messages)
  647. if len(rows) != 2 || string(rows[0][2]) != `{a,"b c"}` {
  648. t.Fatalf("simple rows = %q", rows)
  649. }
  650. }
  651. // VARCHAR arrays report OID 1015 and carry element OID 1043.
  652. func TestBinaryResultsVarcharArray(t *testing.T) {
  653. c, conn := extendedResultConnection(t)
  654. if _, err := c.executor.Execute(parseStmt(t, `CREATE TABLE v (id INTEGER PRIMARY KEY, names VARCHAR(10)[])`)); err != nil {
  655. t.Fatal(err)
  656. }
  657. if _, err := c.executor.Execute(parseStmt(t, `INSERT INTO v VALUES (1, '{a,"b c"}')`)); err != nil {
  658. t.Fatal(err)
  659. }
  660. sendExtended(t, c,
  661. parseMessage("s", `SELECT names FROM v`),
  662. bindResultMessage("p", "s", nil, []int16{1}),
  663. executeMessage("p"),
  664. &Message{Type: MsgSync},
  665. )
  666. messages := readAllMessages(t, conn.bytes())
  667. if msg := findError(messages); msg != nil {
  668. t.Fatalf("varchar[] result failed: %s", errorCode(msg))
  669. }
  670. rd := findRowDescription(t, messages)
  671. if oid := rowDescriptionOIDs(t, rd.Data)[0]; oid != 1015 {
  672. t.Fatalf("varchar[] OID = %d, want 1015", oid)
  673. }
  674. rows := dataRowsRaw(t, messages)
  675. assertArrayHeader(t, "varchar[]", rows[0][0], 1043, 2, false)
  676. elements := arrayElements(t, rows[0][0])
  677. if len(elements) != 2 || string(elements[0]) != "a" || string(elements[1]) != "b c" {
  678. t.Fatalf("varchar[] elements = %q", elements)
  679. }
  680. }
  681. // TestAppendBinaryResultValue covers the encoders that no SQL type reaches
  682. // directly, such as int2 results and int2 arrays.
  683. func TestAppendBinaryResultValue(t *testing.T) {
  684. cases := []struct {
  685. name string
  686. oid int32
  687. value interface{}
  688. want []byte
  689. }{
  690. {"bool true", 16, bool(true), []byte{1}},
  691. {"bool from int64", 16, int64(0), []byte{0}},
  692. {"bool from text", 16, "yes", []byte{1}},
  693. {"int2", 21, int64(-2), []byte{0xff, 0xfe}},
  694. {"int2 from text", 21, "12", []byte{0, 12}},
  695. {"int4", 23, int64(1), []byte{0, 0, 0, 1}},
  696. {"int8", 20, int64(-1), []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}},
  697. {"float4", 700, float64(1.5), binary.BigEndian.AppendUint32(nil, math.Float32bits(1.5))},
  698. {"float8", 701, int64(2), binary.BigEndian.AppendUint64(nil, math.Float64bits(2))},
  699. {"text", 25, "alpha", []byte("alpha")},
  700. {"varchar", 1043, "beta", []byte("beta")},
  701. {"bytea bytes", 17, []byte{0x00, 0xff}, []byte{0x00, 0xff}},
  702. {"bytea string", 17, "ab", []byte("ab")},
  703. {"numeric text", 1700, "1.10", []byte{0, 2, 0, 0, 0, 0, 0, 2, 0, 1, 0x03, 0xe8}},
  704. {"numeric int", 1700, int64(2), []byte{0, 1, 0, 0, 0, 0, 0, 0, 0, 2}},
  705. {"jsonb", 3802, `{"a": 1}`, []byte(`{"a": 1}`)},
  706. }
  707. for _, tc := range cases {
  708. t.Run(tc.name, func(t *testing.T) {
  709. got, err := appendBinaryResultValue(nil, tc.oid, tc.value)
  710. if err != nil {
  711. t.Fatalf("appendBinaryResultValue(%d, %v): %v", tc.oid, tc.value, err)
  712. }
  713. want := tc.want
  714. if tc.oid == 3802 {
  715. want = append([]byte{1}, tc.want...)
  716. }
  717. assertBytes(t, tc.name, got, want)
  718. })
  719. }
  720. for _, tc := range []struct {
  721. name string
  722. oid int32
  723. value interface{}
  724. }{
  725. {"int4 from text", 23, "abc"},
  726. {"int2 out of range", 21, int64(70000)},
  727. {"int4 out of range", 23, int64(1) << 40},
  728. {"bool from text", 16, "maybe"},
  729. {"float from text", 701, "abc"},
  730. {"unsupported oid", 1184, "2024-01-02 03:04:05"},
  731. {"numeric from bool", 1700, true},
  732. } {
  733. t.Run(tc.name, func(t *testing.T) {
  734. if _, err := appendBinaryResultValue(nil, tc.oid, tc.value); err == nil {
  735. t.Fatalf("%s: encoding succeeded, want an error", tc.name)
  736. }
  737. })
  738. }
  739. }
  740. // TestBinaryResultArrayOIDs covers every array OID in the encoding table,
  741. // including the int2/varchar forms the engine reports for declared arrays.
  742. func TestBinaryResultArrayOIDs(t *testing.T) {
  743. cases := []struct {
  744. name string
  745. arrayOID int32
  746. elementOID int32
  747. text string
  748. want [][]byte
  749. }{
  750. {"bool[]", 1000, 16, "{t,f}", [][]byte{{1}, {0}}},
  751. {"int2[]", 1005, 21, "{1,-2}", [][]byte{{0, 1}, {0xff, 0xfe}}},
  752. {"int4[]", 1007, 23, "{1,2}", [][]byte{{0, 0, 0, 1}, {0, 0, 0, 2}}},
  753. {"text[]", 1009, 25, `{a,"b c"}`, [][]byte{[]byte("a"), []byte("b c")}},
  754. {"varchar[]", 1015, 1043, "{a}", [][]byte{[]byte("a")}},
  755. {"int8[]", 1016, 20, "{1}", [][]byte{{0, 0, 0, 0, 0, 0, 0, 1}}},
  756. {"float4[]", 1021, 700, "{1.5}", [][]byte{binary.BigEndian.AppendUint32(nil, math.Float32bits(1.5))}},
  757. {"float8[]", 1022, 701, "{1.5}", [][]byte{binary.BigEndian.AppendUint64(nil, math.Float64bits(1.5))}},
  758. {"numeric[]", 1231, 1700, "{1.10}", [][]byte{{0, 2, 0, 0, 0, 0, 0, 2, 0, 1, 0x03, 0xe8}}},
  759. }
  760. for _, tc := range cases {
  761. t.Run(tc.name, func(t *testing.T) {
  762. got, err := appendBinaryResultValue(nil, tc.arrayOID, tc.text)
  763. if err != nil {
  764. t.Fatalf("encode %s: %v", tc.name, err)
  765. }
  766. assertArrayHeader(t, tc.name, got, tc.elementOID, len(tc.want), false)
  767. elements := arrayElements(t, got)
  768. if len(elements) != len(tc.want) {
  769. t.Fatalf("%s elements = %d, want %d", tc.name, len(elements), len(tc.want))
  770. }
  771. for i := range tc.want {
  772. assertBytes(t, tc.name+" element", elements[i], tc.want[i])
  773. }
  774. })
  775. }
  776. // Unsupported array OIDs are not encodable.
  777. for _, oid := range []int32{1001 /* bytea[] */, 1185 /* timestamptz[] */} {
  778. if canEncodeBinaryResult(oid) {
  779. t.Errorf("canEncodeBinaryResult(%d) = true", oid)
  780. }
  781. }
  782. for _, oid := range []int32{16, 17, 20, 21, 23, 25, 700, 701, 1043, 1700, 3802, 1000, 1005, 1007, 1009, 1015, 1016, 1021, 1022, 1231} {
  783. if !canEncodeBinaryResult(oid) {
  784. t.Errorf("canEncodeBinaryResult(%d) = false", oid)
  785. }
  786. }
  787. for _, oid := range []int32{1082, 1184} {
  788. if canEncodeBinaryResult(oid) {
  789. t.Errorf("canEncodeBinaryResult(%d) = true", oid)
  790. }
  791. }
  792. }
  793. // A statement that writes and returns rows executes before its Bind result
  794. // formats are resolved, because the row metadata only exists at Execute. The
  795. // rejected binary request therefore sends no rows but does not undo the write;
  796. // this ordering is documented in the capability document.
  797. func TestBinaryResultsRejectAfterDMLExecution(t *testing.T) {
  798. c, conn := extendedResultConnection(t)
  799. if _, err := c.executor.Execute(parseStmt(t, `CREATE TABLE w (id INTEGER PRIMARY KEY, created_at TIMESTAMP)`)); err != nil {
  800. t.Fatal(err)
  801. }
  802. sendExtended(t, c,
  803. parseMessage("s", `INSERT INTO w VALUES (1, '2024-01-02 03:04:05') RETURNING created_at`),
  804. bindResultMessage("p", "s", nil, []int16{1}),
  805. executeMessage("p"),
  806. &Message{Type: MsgSync},
  807. )
  808. messages := readAllMessages(t, conn.bytes())
  809. if got := messageTypes(messages); got != "12"+string(MsgErrorResponse)+string(MsgReadyForQuery) {
  810. t.Fatalf("messages = %q, want no RowDescription or DataRow", got)
  811. }
  812. if code := errorCode(findError(messages)); code != ErrCodeFeatureNotSupported {
  813. t.Fatalf("SQLSTATE = %s, want %s", code, ErrCodeFeatureNotSupported)
  814. }
  815. result, err := c.executor.Execute(parseStmt(t, `SELECT COUNT(*) FROM w`))
  816. if err != nil {
  817. t.Fatal(err)
  818. }
  819. if result.Rows[0][0] != int64(1) {
  820. t.Fatalf("write was rolled back: %v", result.Rows)
  821. }
  822. }
  823. // Catalog emulation results use the same per-portal formats as executor
  824. // results: their text and boolean columns encode as binary.
  825. func TestBinaryResultsCatalogQuery(t *testing.T) {
  826. c, conn := extendedResultConnection(t)
  827. if _, err := c.executor.Execute(parseStmt(t, `CREATE TABLE t (id INTEGER PRIMARY KEY)`)); err != nil {
  828. t.Fatal(err)
  829. }
  830. sendExtended(t, c,
  831. parseMessage("s", `SELECT tablename, hasindexes FROM pg_tables`),
  832. bindResultMessage("p", "s", nil, []int16{1}),
  833. executeMessage("p"),
  834. &Message{Type: MsgSync},
  835. )
  836. messages := readAllMessages(t, conn.bytes())
  837. if msg := findError(messages); msg != nil {
  838. t.Fatalf("binary catalog query failed: %s", errorCode(msg))
  839. }
  840. rd := findRowDescription(t, messages)
  841. if got := rowDescriptionFormats(t, rd.Data); !equalInt16(got, []int16{1, 1}) {
  842. t.Fatalf("catalog formats = %v", got)
  843. }
  844. rows := dataRowsRaw(t, messages)
  845. if len(rows) != 1 || string(rows[0][0]) != "t" {
  846. t.Fatalf("catalog rows = %q", rows)
  847. }
  848. // hasindexes is a binary boolean.
  849. if len(rows[0][1]) != 1 || rows[0][1][0] != 0 {
  850. t.Fatalf("hasindexes = %v, want false", rows[0][1])
  851. }
  852. }
  853. // A stored value the requested binary type cannot represent fails mid-result:
  854. // the RowDescription and rows already written stay written, the failing DataRow
  855. // is never emitted, the transaction is marked failed, and the connection stays
  856. // usable after ROLLBACK.
  857. func TestBinaryResultsEncodeFailureMidResult(t *testing.T) {
  858. c, conn := extendedResultConnection(t)
  859. for _, sql := range []string{
  860. `CREATE TABLE t (id INTEGER PRIMARY KEY, x INTEGER)`,
  861. `INSERT INTO t VALUES (1, 5), (2, 'abc'), (3, 7)`,
  862. } {
  863. if _, err := c.executor.Execute(parseStmt(t, sql)); err != nil {
  864. t.Fatalf("%s: %v", sql, err)
  865. }
  866. }
  867. if msg := findError(runSimple(t, c, conn, "BEGIN")); msg != nil {
  868. t.Fatalf("BEGIN failed: %s", errorCode(msg))
  869. }
  870. conn.reset()
  871. sendExtended(t, c,
  872. parseMessage("s", `SELECT id, x FROM t ORDER BY id`),
  873. bindResultMessage("p", "s", nil, []int16{1, 1}),
  874. executeMessage("p"),
  875. &Message{Type: MsgSync},
  876. )
  877. messages := readAllMessages(t, conn.bytes())
  878. if got := messageTypes(messages); got != "12TDEZ" {
  879. t.Fatalf("messages = %q, want Parse/Bind/RowDescription/DataRow/Error/ReadyForQuery", got)
  880. }
  881. rows := dataRowsRaw(t, messages)
  882. if len(rows) != 1 {
  883. t.Fatalf("got %d DataRows, want only the rows before the failure", len(rows))
  884. }
  885. assertBytes(t, "row before the failure", rows[0][0], []byte{0, 0, 0, 1})
  886. assertBytes(t, "int4 before the failure", rows[0][1], []byte{0, 0, 0, 5})
  887. if code := errorCode(findError(messages)); code != ErrCodeInvalidText {
  888. t.Fatalf("SQLSTATE = %s, want %s", code, ErrCodeInvalidText)
  889. }
  890. ready := messages[len(messages)-1]
  891. if ready.Type != MsgReadyForQuery || ready.Data[0] != TxStatusFailed {
  892. t.Fatalf("transaction status = %c, want failed", ready.Data[0])
  893. }
  894. // The connection recovers after ROLLBACK and a new cycle.
  895. conn.reset()
  896. if err := c.handleMessage(&Message{Type: MsgQuery, Data: []byte("ROLLBACK\x00")}); err != nil {
  897. t.Fatal(err)
  898. }
  899. conn.reset()
  900. sendExtended(t, c,
  901. parseMessage("s2", `SELECT x FROM t WHERE id = 1`),
  902. bindResultMessage("p2", "s2", nil, []int16{1}),
  903. executeMessage("p2"),
  904. &Message{Type: MsgSync},
  905. )
  906. messages = readAllMessages(t, conn.bytes())
  907. if msg := findError(messages); msg != nil {
  908. t.Fatalf("query after the encode failure failed: %s", errorCode(msg))
  909. }
  910. if rows := dataRowsRaw(t, messages); len(rows) != 1 || !bytes.Equal(rows[0][0], []byte{0, 0, 0, 5}) {
  911. t.Fatalf("rows after recovery = %v", rows)
  912. }
  913. }
  914. // A value that does not fit int4 (2^40 in an INTEGER column) is an encoding
  915. // failure even when the value converted cleanly at insert time.
  916. func TestBinaryResultsEncodeFailureOutOfRange(t *testing.T) {
  917. c, conn := extendedResultConnection(t)
  918. for _, sql := range []string{
  919. `CREATE TABLE big (id INTEGER PRIMARY KEY, x INTEGER)`,
  920. `INSERT INTO big VALUES (1, 1099511627776)`,
  921. } {
  922. if _, err := c.executor.Execute(parseStmt(t, sql)); err != nil {
  923. t.Fatalf("%s: %v", sql, err)
  924. }
  925. }
  926. sendExtended(t, c,
  927. parseMessage("s", `SELECT x FROM big`),
  928. bindResultMessage("p", "s", nil, []int16{1}),
  929. executeMessage("p"),
  930. &Message{Type: MsgSync},
  931. )
  932. messages := readAllMessages(t, conn.bytes())
  933. if got := messageTypes(messages); got != "12TEZ" {
  934. t.Fatalf("messages = %q, want no DataRow for the failing value", got)
  935. }
  936. if code := errorCode(findError(messages)); code != ErrCodeInvalidText {
  937. t.Fatalf("SQLSTATE = %s, want %s", code, ErrCodeInvalidText)
  938. }
  939. }
  940. // Close('P') drops the portal and therefore its result formats; a later Bind
  941. // under the same name starts from the new list.
  942. func TestBinaryResultsCloseDropsPortalFormats(t *testing.T) {
  943. c, conn := extendedResultConnection(t)
  944. prepareBinaryResultTable(t, c)
  945. sendExtended(t, c,
  946. parseMessage("s", `SELECT id FROM t ORDER BY id`),
  947. bindResultMessage("p", "s", nil, []int16{1}),
  948. executeMessage("p"),
  949. &Message{Type: MsgClose, Data: []byte{'P', 'p', 0}},
  950. bindResultMessage("p", "s", nil, nil),
  951. executeMessage("p"),
  952. &Message{Type: MsgSync},
  953. )
  954. messages := readAllMessages(t, conn.bytes())
  955. if msg := findError(messages); msg != nil {
  956. t.Fatalf("close/re-bind failed: %s", errorCode(msg))
  957. }
  958. var formats [][]int16
  959. var rows [][][]byte
  960. for _, message := range messages {
  961. switch message.Type {
  962. case MsgRowDescription:
  963. formats = append(formats, rowDescriptionFormats(t, message.Data))
  964. case MsgDataRow:
  965. rows = append(rows, dataRowRawValues(t, message))
  966. }
  967. }
  968. if len(formats) != 2 || !equalInt16(formats[0], []int16{1}) {
  969. t.Fatalf("formats = %v, want the first portal binary", formats)
  970. }
  971. if !equalInt16(formats[1], []int16{0}) {
  972. t.Fatalf("formats after Close = %v, want text", formats[1])
  973. }
  974. if len(rows) != 4 {
  975. t.Fatalf("got %d rows, want 4", len(rows))
  976. }
  977. assertBytes(t, "binary portal id", rows[0][0], []byte{0, 0, 0, 1})
  978. if string(rows[2][0]) != "1" {
  979. t.Fatalf("re-bound text portal id = %q", rows[2][0])
  980. }
  981. }
  982. // Float results keep their exact bit patterns, including -0 and NaN; float4
  983. // rejects values whose conversion would become an infinity.
  984. func TestBinaryFloatBitPatterns(t *testing.T) {
  985. negativeZero := math.Copysign(0, -1)
  986. nan := math.NaN()
  987. positiveInfinity := math.Inf(1)
  988. negativeInfinity := math.Inf(-1)
  989. cases := []struct {
  990. name string
  991. oid int32
  992. value interface{}
  993. want []byte
  994. }{
  995. {"float8 -0", 701, negativeZero, binary.BigEndian.AppendUint64(nil, 0x8000000000000000)},
  996. {"float8 NaN", 701, nan, binary.BigEndian.AppendUint64(nil, math.Float64bits(nan))},
  997. {"float8 +Inf", 701, positiveInfinity, binary.BigEndian.AppendUint64(nil, 0x7ff0000000000000)},
  998. {"float8 -Inf", 701, negativeInfinity, binary.BigEndian.AppendUint64(nil, 0xfff0000000000000)},
  999. {"float8 Infinity text", 701, "Infinity", binary.BigEndian.AppendUint64(nil, 0x7ff0000000000000)},
  1000. {"float4 -0", 700, negativeZero, []byte{0x80, 0, 0, 0}},
  1001. {"float4 NaN", 700, nan, []byte{0x7f, 0xc0, 0, 0}},
  1002. {"int8 -2^63", 20, float64(math.MinInt64), []byte{0x80, 0, 0, 0, 0, 0, 0, 0}},
  1003. }
  1004. for _, tc := range cases {
  1005. t.Run(tc.name, func(t *testing.T) {
  1006. got, err := appendBinaryResultValue(nil, tc.oid, tc.value)
  1007. if err != nil {
  1008. t.Fatalf("encode %s: %v", tc.name, err)
  1009. }
  1010. assertBytes(t, tc.name, got, tc.want)
  1011. })
  1012. }
  1013. // float4 cannot represent an infinity, and 2^63 does not fit int8.
  1014. for _, tc := range []struct {
  1015. name string
  1016. oid int32
  1017. value interface{}
  1018. }{
  1019. {"float4 +Inf", 700, positiveInfinity},
  1020. {"float4 -Inf", 700, negativeInfinity},
  1021. {"float4 Infinity text", 700, "-Infinity"},
  1022. {"int8 2^63", 20, math.Pow(2, 63)},
  1023. } {
  1024. t.Run(tc.name, func(t *testing.T) {
  1025. if _, err := appendBinaryResultValue(nil, tc.oid, tc.value); err == nil {
  1026. t.Fatalf("%s: encoding succeeded, want an error", tc.name)
  1027. }
  1028. })
  1029. }
  1030. }