oid_test.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. package pgserver
  2. import (
  3. "bytes"
  4. "encoding/binary"
  5. "net"
  6. "testing"
  7. "github.com/danfragoso/pizzasql-next/pkg/executor"
  8. "github.com/danfragoso/pizzasql-next/pkg/parser"
  9. )
  10. func TestGetOIDForTypeDatetime(t *testing.T) {
  11. c := &Connection{}
  12. cases := map[string]int32{
  13. "DATETIME": 1184,
  14. "TIMESTAMP": 1184,
  15. "DATE": 1082,
  16. "SMALLINT": 21,
  17. "INT2": 21,
  18. "TINYINT": 21,
  19. "MEDIUMINT": 23,
  20. "INTEGER": 23,
  21. "INT": 23,
  22. "INT4": 23,
  23. "BIGINT": 20,
  24. "INT8": 20,
  25. "NUMERIC": 1700,
  26. "DECIMAL": 1700,
  27. "REAL": 700,
  28. "FLOAT": 700,
  29. "DOUBLE": 701,
  30. "TEXT": 25,
  31. "VARCHAR": 25,
  32. "CHAR": 25,
  33. "UUID": 25,
  34. "BOOLEAN": 16,
  35. "BOOL": 16,
  36. "BLOB": 17,
  37. }
  38. for name, want := range cases {
  39. if got := c.getOIDForType(name); got != want {
  40. t.Errorf("getOIDForType(%q) = %d, want %d", name, got, want)
  41. }
  42. }
  43. }
  44. func TestSendRowDescriptionTimestampOID(t *testing.T) {
  45. c, cc := newCountingTestConnection(t)
  46. if err := c.sendRowDescription([]string{"created_at", "id"}, []string{"TIMESTAMP", "BIGINT"}); err != nil {
  47. t.Fatal(err)
  48. }
  49. if err := c.sendReadyForQuery(); err != nil {
  50. t.Fatal(err)
  51. }
  52. msgs := readAllMessages(t, cc.bytes())
  53. rd := findRowDescription(t, msgs)
  54. oids := rowDescriptionOIDs(t, rd.Data)
  55. if len(oids) != 2 {
  56. t.Fatalf("got %d columns, want 2", len(oids))
  57. }
  58. if oids[0] != 1184 {
  59. t.Errorf("timestamp OID = %d, want 1184", oids[0])
  60. }
  61. if oids[1] != 20 {
  62. t.Errorf("bigint OID = %d, want 20", oids[1])
  63. }
  64. }
  65. func TestSendResultEmptyProjectionSendsRowDescription(t *testing.T) {
  66. c, cc := newCountingTestConnection(t)
  67. result := executor.NewResult("SELECT")
  68. result.AddColumnWithType("id", "BIGINT")
  69. result.AddColumnWithType("created_at", "DATETIME")
  70. if err := c.sendResult(result, &parser.SelectStmt{}); err != nil {
  71. t.Fatal(err)
  72. }
  73. if err := c.sendReadyForQuery(); err != nil {
  74. t.Fatal(err)
  75. }
  76. msgs := readAllMessages(t, cc.bytes())
  77. rd := findRowDescription(t, msgs)
  78. oids := rowDescriptionOIDs(t, rd.Data)
  79. if len(oids) != 2 || oids[0] != 20 || oids[1] != 1184 {
  80. t.Fatalf("oids = %v, want [20 1184]", oids)
  81. }
  82. }
  83. func findRowDescription(t *testing.T, msgs []*Message) *Message {
  84. t.Helper()
  85. for _, m := range msgs {
  86. if m.Type == MsgRowDescription {
  87. return m
  88. }
  89. }
  90. t.Fatal("no RowDescription message found")
  91. return nil
  92. }
  93. func rowDescriptionOIDs(t *testing.T, data []byte) []int32 {
  94. t.Helper()
  95. if len(data) < 2 {
  96. t.Fatalf("row description too short")
  97. }
  98. count := int(binary.BigEndian.Uint16(data[:2]))
  99. pos := 2
  100. oids := make([]int32, 0, count)
  101. for i := 0; i < count; i++ {
  102. end := bytes.IndexByte(data[pos:], 0)
  103. if end < 0 {
  104. t.Fatalf("unterminated column name")
  105. }
  106. pos += end + 1 // name
  107. pos += 4 // table OID
  108. pos += 2 // column attribute number
  109. if pos+4 > len(data) {
  110. t.Fatalf("truncated row description")
  111. }
  112. oids = append(oids, int32(binary.BigEndian.Uint32(data[pos:pos+4])))
  113. pos += 4 // type OID
  114. pos += 2 // type size
  115. pos += 4 // type modifier
  116. pos += 2 // format code
  117. }
  118. return oids
  119. }
  120. func TestGetTypeSizeForType(t *testing.T) {
  121. c := &Connection{}
  122. cases := map[string]int16{
  123. "SMALLINT": 2,
  124. "INTEGER": 4,
  125. "BIGINT": 8,
  126. "REAL": 4,
  127. "DOUBLE": 8,
  128. "BOOLEAN": 1,
  129. "DATE": 4,
  130. "TIMESTAMP": 8,
  131. "NUMERIC": -1,
  132. "TEXT": -1,
  133. "BLOB": -1,
  134. }
  135. for name, want := range cases {
  136. if got := c.getTypeSizeForType(name); got != want {
  137. t.Errorf("getTypeSizeForType(%q) = %d, want %d", name, got, want)
  138. }
  139. }
  140. }
  141. // projectionOIDs runs sql and returns the type OIDs in its RowDescription.
  142. func projectionOIDs(t *testing.T, c *Connection, client net.Conn, sql string) []int32 {
  143. t.Helper()
  144. msgs := resultMessages(runQuery(t, c, client, &Message{Type: MsgQuery, Data: append([]byte(sql), 0)}))
  145. return rowDescriptionOIDs(t, findRowDescription(t, msgs).Data)
  146. }
  147. // TestRowDescriptionProjectionOIDs asserts the wire OIDs for column
  148. // projections, expression projections, and empty result sets.
  149. func TestRowDescriptionProjectionOIDs(t *testing.T) {
  150. c, client := newDataConnection(t)
  151. runQuery(t, c, client, &Message{Type: MsgQuery, Data: append([]byte(
  152. `CREATE TABLE typed (
  153. i INTEGER PRIMARY KEY,
  154. big BIGINT,
  155. small SMALLINT,
  156. price NUMERIC,
  157. flag BOOLEAN,
  158. name TEXT,
  159. data BLOB,
  160. created_at TIMESTAMP,
  161. day DATE
  162. )`), 0)})
  163. runQuery(t, c, client, &Message{Type: MsgQuery, Data: append([]byte(
  164. `INSERT INTO typed VALUES (1, 9000000000, 7, 19.5, TRUE, 'x', X'00', '2024-05-06 07:08:09', '2024-05-06')`), 0)})
  165. cases := []struct {
  166. name string
  167. sql string
  168. want []int32
  169. }{
  170. {
  171. name: "columns",
  172. sql: "SELECT i, big, small, price, flag, name, data, created_at, day FROM typed",
  173. want: []int32{23, 20, 21, 1700, 16, 25, 17, 1184, 1082},
  174. },
  175. {name: "count star", sql: "SELECT count(*) FROM typed", want: []int32{20}},
  176. {name: "sum integer", sql: "SELECT sum(i) FROM typed", want: []int32{20}},
  177. {name: "avg integer", sql: "SELECT avg(i) FROM typed", want: []int32{1700}},
  178. {name: "comparison", sql: "SELECT i = 1 FROM typed", want: []int32{16}},
  179. {name: "string literal", sql: "SELECT 'text'", want: []int32{25}},
  180. {name: "integer literal", sql: "SELECT 42", want: []int32{23}},
  181. {name: "wide integer literal", sql: "SELECT 4294967296", want: []int32{20}},
  182. {name: "numeric literal", sql: "SELECT 1.5", want: []int32{1700}},
  183. {name: "cast", sql: "SELECT CAST(i AS BIGINT) FROM typed", want: []int32{20}},
  184. {
  185. name: "empty result",
  186. sql: "SELECT i, created_at, i + 1 FROM typed WHERE i = 999",
  187. want: []int32{23, 1184, 20},
  188. },
  189. {
  190. name: "aggregate empty result",
  191. sql: "SELECT count(*), max(created_at) FROM typed WHERE i = 999",
  192. want: []int32{20, 1184},
  193. },
  194. }
  195. for _, tc := range cases {
  196. t.Run(tc.name, func(t *testing.T) {
  197. got := projectionOIDs(t, c, client, tc.sql)
  198. if len(got) != len(tc.want) {
  199. t.Fatalf("%s: OIDs = %v, want %v", tc.sql, got, tc.want)
  200. }
  201. for i := range tc.want {
  202. if got[i] != tc.want[i] {
  203. t.Fatalf("%s: OIDs = %v, want %v", tc.sql, got, tc.want)
  204. }
  205. }
  206. })
  207. }
  208. }