jsonb_contract_test.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  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. // Binary jsonb is rejected instead of being misread as UTF-8 JSON.
  86. conn.reset()
  87. messages = runExtendedJSONBBind(t, c, conn, `SELECT $1`, 3802, 1, []byte{0x01, 0x02})
  88. errMsg = findError(messages)
  89. if errMsg == nil || errorCode(errMsg) != ErrCodeFeatureNotSupported {
  90. t.Fatalf("binary jsonb parameter error = %v, want 0A000", messages)
  91. }
  92. }
  93. // JSONB columns report jsonb metadata (OID 3802) while JSON columns keep their
  94. // text-backed behavior (OID 25).
  95. func TestJSONBColumnTypeOIDs(t *testing.T) {
  96. c, conn := arrayConnection(t)
  97. for _, sql := range []string{
  98. `CREATE TABLE docs (id INTEGER PRIMARY KEY, doc JSONB, body JSON)`,
  99. `INSERT INTO docs VALUES (1, '{"b":2, "a":1}', '{"b":2, "a":1}')`,
  100. } {
  101. if _, err := c.executor.Execute(parseStmt(t, sql)); err != nil {
  102. t.Fatalf("%s: %v", sql, err)
  103. }
  104. }
  105. conn.reset()
  106. messages := runSimple(t, c, conn, `SELECT doc, body, to_jsonb(1), doc -> 'a' FROM docs`)
  107. if errMsg := findError(messages); errMsg != nil {
  108. t.Fatalf("select failed: %s", errorCode(errMsg))
  109. }
  110. oids := rowDescriptionOIDs(t, findRowDescription(t, messages).Data)
  111. want := []int32{3802, 25, 3802, 3802}
  112. if len(oids) != len(want) {
  113. t.Fatalf("jsonb/json projection OIDs = %v, want %v", oids, want)
  114. }
  115. for i := range want {
  116. if oids[i] != want[i] {
  117. t.Fatalf("column %d OID = %d, want %d (all: %v)", i, oids[i], want[i], oids)
  118. }
  119. }
  120. if row := firstDataRow(t, messages); len(row) != 4 || row[0] != `{"a":1,"b":2}` || row[1] != `{"b":2, "a":1}` || row[3] != `1` {
  121. t.Fatalf("jsonb/json projection values = %v", row)
  122. }
  123. // Text-format parameters for JSON (114) keep their previous passthrough
  124. // behavior rather than being canonicalized.
  125. conn.reset()
  126. messages = runExtendedJSONBBind(t, c, conn, `SELECT $1`, 114, 0, []byte(`{ "b" : 1 }`))
  127. if errMsg := findError(messages); errMsg != nil {
  128. t.Fatalf("JSON parameter failed: %s", errorCode(errMsg))
  129. }
  130. if row := firstDataRow(t, messages); len(row) != 1 || row[0] != `{ "b" : 1 }` {
  131. t.Fatalf("JSON parameter value = %v, want the original text", row)
  132. }
  133. }