2
0

jsonb_contract_test.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. package pgserver
  2. import (
  3. "testing"
  4. )
  5. // runExtendedJSONBBind drives one extended-protocol cycle with a single
  6. // parameter of the given OID, format, and raw value.
  7. func runExtendedJSONBBind(t *testing.T, c *Connection, conn *countingConn, sql string, oid int32, format int16, value []byte) []*Message {
  8. t.Helper()
  9. parse := NewMessageBuilder()
  10. parse.WriteString("s1")
  11. parse.WriteString(sql)
  12. parse.WriteInt16(1)
  13. parse.WriteInt32(oid)
  14. bind := NewMessageBuilder()
  15. bind.WriteString("p1")
  16. bind.WriteString("s1")
  17. bind.WriteInt16(1) // one parameter format
  18. bind.WriteInt16(format)
  19. bind.WriteInt16(1) // one parameter
  20. bind.WriteInt32(int32(len(value)))
  21. bind.WriteBytes(value)
  22. bind.WriteInt16(0) // all-text result formats
  23. execute := NewMessageBuilder()
  24. execute.WriteString("p1")
  25. execute.WriteInt32(0)
  26. for _, msg := range []*Message{
  27. {Type: MsgParse, Data: parse.Bytes()},
  28. {Type: MsgBind, Data: bind.Bytes()},
  29. {Type: MsgExecute, Data: execute.Bytes()},
  30. {Type: MsgSync},
  31. } {
  32. if err := c.handleMessage(msg); err != nil {
  33. t.Fatalf("handle %c: %v", msg.Type, err)
  34. }
  35. }
  36. return readAllMessages(t, conn.bytes())
  37. }
  38. // A text-format jsonb parameter (OID 3802) is canonicalized at Bind, so the
  39. // query sees canonical jsonb bytes and an invalid document fails before
  40. // execution.
  41. func TestJSONBTextParameterOverExtendedQuery(t *testing.T) {
  42. c, conn := arrayConnection(t)
  43. conn.reset()
  44. messages := runExtendedJSONBBind(t, c, conn, `SELECT $1`, 3802, 0, []byte(`{ "b" : 2, "a" : 1.50 }`))
  45. if errMsg := findError(messages); errMsg != nil {
  46. t.Fatalf("jsonb parameter failed: %s %s", errorCode(errMsg), jsonErrorField(t, errMsg, ErrorFieldMessage))
  47. }
  48. if row := firstDataRow(t, messages); len(row) != 1 || row[0] != `{"a":1.5,"b":2}` {
  49. t.Fatalf("canonical jsonb parameter = %v", row)
  50. }
  51. if oids := rowDescriptionOIDs(t, findRowDescription(t, messages).Data); len(oids) != 1 || oids[0] != 3802 {
  52. t.Fatalf("jsonb parameter result OIDs = %v, want [3802]", oids)
  53. }
  54. // The bound value is a real jsonb value for operators and functions.
  55. conn.reset()
  56. messages = runExtendedJSONBBind(t, c, conn, `SELECT $1 -> 'a', $1 ? 'b'`, 3802, 0, []byte(`{"b":2,"a":1}`))
  57. if errMsg := findError(messages); errMsg != nil {
  58. t.Fatalf("jsonb operator over parameter failed: %s", errorCode(errMsg))
  59. }
  60. if row := firstDataRow(t, messages); len(row) != 2 || row[0] != "1" || row[1] != "true" {
  61. t.Fatalf("jsonb operators over parameter = %v", row)
  62. }
  63. // Invalid text is 22P02 at Bind.
  64. conn.reset()
  65. messages = runExtendedJSONBBind(t, c, conn, `SELECT $1`, 3802, 0, []byte(`not json`))
  66. errMsg := findError(messages)
  67. if errMsg == nil || errorCode(errMsg) != ErrCodeInvalidText {
  68. t.Fatalf("invalid jsonb parameter error = %v, want 22P02", messages)
  69. }
  70. // An INSERT binds the canonical document into the JSONB column, and a later
  71. // read returns the same canonical bytes.
  72. conn.reset()
  73. if _, err := c.executor.Execute(parseStmt(t, `CREATE TABLE docs (id INTEGER PRIMARY KEY, doc JSONB)`)); err != nil {
  74. t.Fatal(err)
  75. }
  76. messages = runExtendedJSONBBind(t, c, conn, `INSERT INTO docs VALUES (1, $1)`, 3802, 0, []byte(`{ "b" : 2, "a" : 1.50 }`))
  77. if errMsg := findError(messages); errMsg != nil {
  78. t.Fatalf("jsonb INSERT parameter failed: %s", errorCode(errMsg))
  79. }
  80. conn.reset()
  81. messages = runSimple(t, c, conn, `SELECT doc FROM docs`)
  82. if row := firstDataRow(t, messages); len(row) != 1 || row[0] != `{"a":1.5,"b":2}` {
  83. t.Fatalf("jsonb INSERT round trip = %v", row)
  84. }
  85. // PostgreSQL binary jsonb is a version byte plus the document text. pgx sends
  86. // this form by default, so it is accepted and canonicalized.
  87. conn.reset()
  88. messages = runExtendedJSONBBind(t, c, conn, `SELECT $1 -> 'a'`, 3802, 1, append([]byte{0x01}, []byte(`{ "a" : 1.50 }`)...))
  89. if errMsg := findError(messages); errMsg != nil {
  90. t.Fatalf("binary jsonb parameter failed: %s", errorCode(errMsg))
  91. }
  92. if row := firstDataRow(t, messages); len(row) != 1 || row[0] != "1.5" {
  93. t.Fatalf("binary jsonb parameter = %v, want [1.5]", row)
  94. }
  95. conn.reset()
  96. messages = runExtendedJSONBBind(t, c, conn, `SELECT $1`, 3802, 1, append([]byte{0x01}, []byte(`[1, 2]`)...))
  97. if row := firstDataRow(t, messages); len(row) != 1 || row[0] != `[1,2]` {
  98. t.Fatalf("binary jsonb canonicalization = %v, want [1,2]", row)
  99. }
  100. // A wrong version and a malformed body are invalid text (22P02), not 0A000.
  101. for _, tc := range []struct {
  102. value []byte
  103. code string
  104. }{
  105. {[]byte{0x02, '{', '}'}, ErrCodeInvalidText},
  106. {[]byte{0x01}, ErrCodeInvalidText},
  107. {[]byte{0x01, 'n', 'o', 't'}, ErrCodeInvalidText},
  108. {[]byte{}, ErrCodeInvalidText},
  109. } {
  110. conn.reset()
  111. messages = runExtendedJSONBBind(t, c, conn, `SELECT $1`, 3802, 1, tc.value)
  112. errMsg := findError(messages)
  113. if errMsg == nil || errorCode(errMsg) != tc.code {
  114. t.Fatalf("binary jsonb %x error = %v, want %s", tc.value, messages, tc.code)
  115. }
  116. }
  117. }
  118. // JSONB columns report jsonb metadata (OID 3802) while JSON columns keep their
  119. // text-backed behavior (OID 25).
  120. func TestJSONBColumnTypeOIDs(t *testing.T) {
  121. c, conn := arrayConnection(t)
  122. for _, sql := range []string{
  123. `CREATE TABLE docs (id INTEGER PRIMARY KEY, doc JSONB, body JSON)`,
  124. `INSERT INTO docs VALUES (1, '{"b":2, "a":1}', '{"b":2, "a":1}')`,
  125. } {
  126. if _, err := c.executor.Execute(parseStmt(t, sql)); err != nil {
  127. t.Fatalf("%s: %v", sql, err)
  128. }
  129. }
  130. conn.reset()
  131. messages := runSimple(t, c, conn, `SELECT doc, body, to_jsonb(1), doc -> 'a' FROM docs`)
  132. if errMsg := findError(messages); errMsg != nil {
  133. t.Fatalf("select failed: %s", errorCode(errMsg))
  134. }
  135. oids := rowDescriptionOIDs(t, findRowDescription(t, messages).Data)
  136. want := []int32{3802, 25, 3802, 3802}
  137. if len(oids) != len(want) {
  138. t.Fatalf("jsonb/json projection OIDs = %v, want %v", oids, want)
  139. }
  140. for i := range want {
  141. if oids[i] != want[i] {
  142. t.Fatalf("column %d OID = %d, want %d (all: %v)", i, oids[i], want[i], oids)
  143. }
  144. }
  145. if row := firstDataRow(t, messages); len(row) != 4 || row[0] != `{"a":1,"b":2}` || row[1] != `{"b":2, "a":1}` || row[3] != `1` {
  146. t.Fatalf("jsonb/json projection values = %v", row)
  147. }
  148. // Text-format parameters for JSON (114) keep their previous passthrough
  149. // behavior rather than being canonicalized.
  150. conn.reset()
  151. messages = runExtendedJSONBBind(t, c, conn, `SELECT $1`, 114, 0, []byte(`{ "b" : 1 }`))
  152. if errMsg := findError(messages); errMsg != nil {
  153. t.Fatalf("JSON parameter failed: %s", errorCode(errMsg))
  154. }
  155. if row := firstDataRow(t, messages); len(row) != 1 || row[0] != `{ "b" : 1 }` {
  156. t.Fatalf("JSON parameter value = %v, want the original text", row)
  157. }
  158. }