rowcodec_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. package storage
  2. import (
  3. "bytes"
  4. "encoding/binary"
  5. "errors"
  6. "reflect"
  7. "testing"
  8. "github.com/goccy/go-json"
  9. )
  10. func TestEncodeRowDeterministic(t *testing.T) {
  11. row := Row{
  12. "b": int64(2),
  13. "a": int64(1),
  14. "c": int64(3),
  15. }
  16. first, err := encodeRow(row)
  17. if err != nil {
  18. t.Fatalf("encodeRow: %v", err)
  19. }
  20. // Rebuild with the same pairs in a different insertion order.
  21. rowAgain := Row{}
  22. rowAgain["c"] = int64(3)
  23. rowAgain["a"] = int64(1)
  24. rowAgain["b"] = int64(2)
  25. second, err := encodeRow(rowAgain)
  26. if err != nil {
  27. t.Fatalf("encodeRow again: %v", err)
  28. }
  29. if !bytes.Equal(first, second) {
  30. t.Fatalf("encoding is not deterministic:\n%x\n%x", first, second)
  31. }
  32. }
  33. func TestEncodeRowBinaryBytes(t *testing.T) {
  34. got, err := encodeRow(Row{"a": int64(1)})
  35. if err != nil {
  36. t.Fatalf("encodeRow: %v", err)
  37. }
  38. want := []byte{
  39. 'P', 'Z', 'S', 'Q', 'L', 'R', 'O', 'W', // magic
  40. 0x01, // version
  41. 0x01, 0x00, 0x00, 0x00, // count = 1
  42. 0x01, 0x00, 0x00, 0x00, // nameLen = 1
  43. 'a', // name
  44. 0x03, // tagInt
  45. 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // int64(1)
  46. }
  47. if !bytes.Equal(got, want) {
  48. t.Fatalf("binary bytes = %x, want %x", got, want)
  49. }
  50. }
  51. func TestEncodeDecodeRoundTripAllTypes(t *testing.T) {
  52. in := Row{
  53. "nil": nil,
  54. "bt": true,
  55. "bf": false,
  56. "i": int(42),
  57. "i8": int8(-8),
  58. "i16": int16(-1600),
  59. "i32": int32(-70000),
  60. "i64": int64(-9000000000000000000),
  61. "u": uint(7),
  62. "u8": uint8(200),
  63. "u16": uint16(60000),
  64. "u32": uint32(4000000000),
  65. "u64": uint64(18446744073709551615),
  66. "f32": float32(1.5),
  67. "f64": float64(-2.25),
  68. "str": "hello",
  69. "bytes": []byte{0x00, 0xff, 0x01, '\n'},
  70. "num": json.Number("12345678901234567890"),
  71. }
  72. want := Row{
  73. "nil": nil,
  74. "bt": true,
  75. "bf": false,
  76. "i": int64(42),
  77. "i8": int64(-8),
  78. "i16": int64(-1600),
  79. "i32": int64(-70000),
  80. "i64": int64(-9000000000000000000),
  81. "u": uint64(7),
  82. "u8": uint64(200),
  83. "u16": uint64(60000),
  84. "u32": uint64(4000000000),
  85. "u64": uint64(18446744073709551615),
  86. "f32": float32(1.5),
  87. "f64": float64(-2.25),
  88. "str": "hello",
  89. "bytes": []byte{0x00, 0xff, 0x01, '\n'},
  90. "num": json.Number("12345678901234567890"),
  91. }
  92. data, err := encodeRow(in)
  93. if err != nil {
  94. t.Fatalf("encodeRow: %v", err)
  95. }
  96. if len(data) < len(rowMagic) || string(data[:len(rowMagic)]) != rowMagic {
  97. t.Fatalf("binary row missing magic prefix: %x", data)
  98. }
  99. got, err := decodeRow(data)
  100. if err != nil {
  101. t.Fatalf("decodeRow: %v", err)
  102. }
  103. if !reflect.DeepEqual(got, want) {
  104. t.Fatalf("round trip mismatch:\n got = %#v\nwant = %#v", got, want)
  105. }
  106. }
  107. func TestEncodeRowJSONNumberExact(t *testing.T) {
  108. // A decimal that would lose precision as a float64 must round-trip exactly.
  109. row := Row{"n": json.Number("0.123456789012345678901234567890")}
  110. data, err := encodeRow(row)
  111. if err != nil {
  112. t.Fatalf("encodeRow: %v", err)
  113. }
  114. got, err := decodeRow(data)
  115. if err != nil {
  116. t.Fatalf("decodeRow: %v", err)
  117. }
  118. if got["n"] != json.Number("0.123456789012345678901234567890") {
  119. t.Fatalf("number = %#v, want exact json.Number", got["n"])
  120. }
  121. }
  122. func TestDecodeLegacyJSON(t *testing.T) {
  123. legacy := []byte(`{"_rowid_":7,"name":"alice","score":12.5,"active":true,"extra":null}`)
  124. got, err := decodeRow(legacy)
  125. if err != nil {
  126. t.Fatalf("decodeRow: %v", err)
  127. }
  128. if got["_rowid_"] != float64(7) {
  129. t.Fatalf("_rowid_ = %#v, want float64(7)", got["_rowid_"])
  130. }
  131. if got["name"] != "alice" {
  132. t.Fatalf("name = %#v", got["name"])
  133. }
  134. if got["score"] != float64(12.5) {
  135. t.Fatalf("score = %#v", got["score"])
  136. }
  137. if got["active"] != true {
  138. t.Fatalf("active = %#v", got["active"])
  139. }
  140. if got["extra"] != nil {
  141. t.Fatalf("extra = %#v", got["extra"])
  142. }
  143. }
  144. func TestEncodeRowUnsupportedValueFallsBackToJSON(t *testing.T) {
  145. row := Row{"id": int64(1), "tags": []string{"a", "b"}}
  146. data, err := encodeRow(row)
  147. if err != nil {
  148. t.Fatalf("encodeRow: %v", err)
  149. }
  150. if len(data) >= len(rowMagic) && string(data[:len(rowMagic)]) == rowMagic {
  151. t.Fatalf("expected JSON fallback, got binary magic: %x", data)
  152. }
  153. var decoded Row
  154. if err := json.Unmarshal(data, &decoded); err != nil {
  155. t.Fatalf("fallback is not valid JSON: %v", err)
  156. }
  157. if decoded["id"] != float64(1) {
  158. t.Fatalf("id = %#v", decoded["id"])
  159. }
  160. tags, ok := decoded["tags"].([]interface{})
  161. if !ok || len(tags) != 2 || tags[0] != "a" || tags[1] != "b" {
  162. t.Fatalf("tags = %#v", decoded["tags"])
  163. }
  164. }
  165. func TestEncodeRowJSONFallbackRoundTripsThroughDecode(t *testing.T) {
  166. row := Row{"nested": map[string]interface{}{"x": 1, "y": []interface{}{true, nil}}}
  167. data, err := encodeRow(row)
  168. if err != nil {
  169. t.Fatalf("encodeRow: %v", err)
  170. }
  171. got, err := decodeRow(data)
  172. if err != nil {
  173. t.Fatalf("decodeRow: %v", err)
  174. }
  175. if _, ok := got["nested"].(map[string]interface{}); !ok {
  176. t.Fatalf("nested = %#v, want map", got["nested"])
  177. }
  178. }
  179. func TestDecodeBinaryRowTruncated(t *testing.T) {
  180. data, err := encodeRow(Row{"name": "alice", "id": int64(5), "payload": []byte("data")})
  181. if err != nil {
  182. t.Fatalf("encodeRow: %v", err)
  183. }
  184. for _, n := range []int{1, len(rowMagic), rowHeaderLen, rowHeaderLen + 1, len(data) - 1} {
  185. trunc := data[:n]
  186. if _, err := decodeRow(trunc); err == nil {
  187. t.Fatalf("decodeRow(truncated to %d bytes) succeeded, want error", n)
  188. }
  189. }
  190. }
  191. func TestDecodeBinaryRowTrailingBytes(t *testing.T) {
  192. data, err := encodeRow(Row{"id": int64(1)})
  193. if err != nil {
  194. t.Fatalf("encodeRow: %v", err)
  195. }
  196. withTrailing := append(append([]byte(nil), data...), 0x00, 0x01, 0x02)
  197. if _, err := decodeRow(withTrailing); err == nil {
  198. t.Fatalf("decodeRow with trailing bytes succeeded, want error")
  199. }
  200. }
  201. func TestDecodeBinaryRowUnknownTag(t *testing.T) {
  202. var buf []byte
  203. buf = append(buf, rowMagic...)
  204. buf = append(buf, rowVersion)
  205. buf = appendU32(buf, 1)
  206. buf = appendU32(buf, 2)
  207. buf = append(buf, "id"...)
  208. buf = append(buf, 0x7f) // unknown tag
  209. buf = append(buf, 0, 0, 0, 0, 0, 0, 0, 0)
  210. if _, err := decodeRow(buf); !errors.Is(err, errMalformedRow) {
  211. t.Fatalf("err = %v, want errMalformedRow", err)
  212. }
  213. }
  214. func TestDecodeBinaryRowUnknownVersion(t *testing.T) {
  215. data, err := encodeRow(Row{"id": int64(1)})
  216. if err != nil {
  217. t.Fatalf("encodeRow: %v", err)
  218. }
  219. corrupted := append([]byte(nil), data...)
  220. corrupted[len(rowMagic)] = 0x7f
  221. if _, err := decodeRow(corrupted); !errors.Is(err, errMalformedRow) {
  222. t.Fatalf("err = %v, want errMalformedRow", err)
  223. }
  224. }
  225. func TestDecodeBinaryRowOversizedNameLength(t *testing.T) {
  226. var buf []byte
  227. buf = append(buf, rowMagic...)
  228. buf = append(buf, rowVersion)
  229. buf = appendU32(buf, 1)
  230. buf = appendU32(buf, uint32(maxRowFieldLen+1)) // oversized name length
  231. buf = append(buf, 'x')
  232. if _, err := decodeRow(buf); err == nil {
  233. t.Fatalf("decodeRow with oversized name length succeeded, want error")
  234. }
  235. }
  236. func TestDecodeBinaryRowOversizedValueLength(t *testing.T) {
  237. var buf []byte
  238. buf = append(buf, rowMagic...)
  239. buf = append(buf, rowVersion)
  240. buf = appendU32(buf, 1)
  241. buf = appendU32(buf, 1)
  242. buf = append(buf, 'a')
  243. buf = append(buf, tagString)
  244. buf = appendU32(buf, uint32(maxRowFieldLen+1)) // oversized string length
  245. if _, err := decodeRow(buf); err == nil {
  246. t.Fatalf("decodeRow with oversized value length succeeded, want error")
  247. }
  248. }
  249. func TestDecodeBinaryRowStringLengthExceedsInput(t *testing.T) {
  250. var buf []byte
  251. buf = append(buf, rowMagic...)
  252. buf = append(buf, rowVersion)
  253. buf = appendU32(buf, 1)
  254. buf = appendU32(buf, 1)
  255. buf = append(buf, 'a')
  256. buf = append(buf, tagString)
  257. buf = appendU32(buf, 100) // claims 100 bytes but only 0 follow
  258. if _, err := decodeRow(buf); err == nil {
  259. t.Fatalf("decodeRow with lying string length succeeded, want error")
  260. }
  261. }
  262. func TestDecodeBinaryRowImpossibleFieldCount(t *testing.T) {
  263. var buf []byte
  264. buf = append(buf, rowMagic...)
  265. buf = append(buf, rowVersion)
  266. buf = appendU32(buf, 0xffffffff) // far more fields than bytes available
  267. if _, err := decodeRow(buf); err == nil {
  268. t.Fatalf("decodeRow with impossible field count succeeded, want error")
  269. }
  270. }
  271. func TestDecodeMalformedTaggedBinaryNotReinterpretedAsJSON(t *testing.T) {
  272. // Bytes carrying the magic prefix must never fall back to the JSON path,
  273. // even if the tail happens to look JSON-ish.
  274. corrupted := append([]byte(nil), rowMagic...)
  275. corrupted = append(corrupted, rowVersion)
  276. corrupted = append(corrupted, 0xff, 0xff, 0xff, 0xff) // bogus count
  277. corrupted = append(corrupted, 'g', 'a', 'r', 'b', 'a', 'g', 'e')
  278. if _, err := decodeRow(corrupted); err == nil {
  279. t.Fatalf("decodeRow succeeded on malformed tagged binary, want error")
  280. }
  281. }
  282. func TestDecodeNonJSONNonBinaryInput(t *testing.T) {
  283. // No magic prefix and not valid JSON must fail rather than panic or return
  284. // a partial row.
  285. if _, err := decodeRow([]byte{0x01, 0x02, 0x03, 0x04}); err == nil {
  286. t.Fatalf("decodeRow on garbage succeeded, want error")
  287. }
  288. }
  289. func TestEncodeDecodeEmptyRow(t *testing.T) {
  290. data, err := encodeRow(Row{})
  291. if err != nil {
  292. t.Fatalf("encodeRow: %v", err)
  293. }
  294. got, err := decodeRow(data)
  295. if err != nil {
  296. t.Fatalf("decodeRow: %v", err)
  297. }
  298. if len(got) != 0 {
  299. t.Fatalf("empty row decoded to %#v", got)
  300. }
  301. }
  302. func TestEncodeDecodeNilRow(t *testing.T) {
  303. data, err := encodeRow(nil)
  304. if err != nil {
  305. t.Fatalf("encodeRow(nil): %v", err)
  306. }
  307. got, err := decodeRow(data)
  308. if err != nil {
  309. t.Fatalf("decodeRow: %v", err)
  310. }
  311. if len(got) != 0 {
  312. t.Fatalf("nil row decoded to %#v", got)
  313. }
  314. }
  315. func TestEncodeRowSortedFieldNames(t *testing.T) {
  316. data, err := encodeRow(Row{"z": int64(3), "a": int64(1), "m": int64(2)})
  317. if err != nil {
  318. t.Fatalf("encodeRow: %v", err)
  319. }
  320. // Verify the field names appear in sorted order by walking the encoding.
  321. pos := rowHeaderLen
  322. count := binary.LittleEndian.Uint32(data[pos-4 : pos])
  323. names := make([]string, 0, count)
  324. for i := uint32(0); i < count; i++ {
  325. nameLen := binary.LittleEndian.Uint32(data[pos : pos+4])
  326. pos += 4
  327. names = append(names, string(data[pos:pos+int(nameLen)]))
  328. pos += int(nameLen)
  329. pos++ // skip tag
  330. switch data[pos-1] {
  331. case tagInt, tagUint, tagFloat64:
  332. pos += 8
  333. case tagFloat32:
  334. pos += 4
  335. case tagString, tagBytes, tagNumber:
  336. l := binary.LittleEndian.Uint32(data[pos : pos+4])
  337. pos += 4 + int(l)
  338. }
  339. }
  340. if !reflect.DeepEqual(names, []string{"a", "m", "z"}) {
  341. t.Fatalf("field names = %v, want [a m z]", names)
  342. }
  343. }
  344. func BenchmarkRowCodec(b *testing.B) {
  345. row := Row{
  346. "_rowid_": int64(4812),
  347. "id": int64(4812),
  348. "symbol": "PIZZA",
  349. "price": 104.25,
  350. "active": true,
  351. "payload": []byte{0, 1, 2, '|', '\r', '\n'},
  352. }
  353. binaryRow, err := encodeRow(row)
  354. if err != nil {
  355. b.Fatal(err)
  356. }
  357. jsonRow, err := json.Marshal(row)
  358. if err != nil {
  359. b.Fatal(err)
  360. }
  361. b.Run("encode_binary", func(b *testing.B) {
  362. b.ReportAllocs()
  363. for b.Loop() {
  364. if _, err := encodeRow(row); err != nil {
  365. b.Fatal(err)
  366. }
  367. }
  368. })
  369. b.Run("encode_json", func(b *testing.B) {
  370. b.ReportAllocs()
  371. for b.Loop() {
  372. if _, err := json.Marshal(row); err != nil {
  373. b.Fatal(err)
  374. }
  375. }
  376. })
  377. b.Run("decode_binary", func(b *testing.B) {
  378. b.ReportAllocs()
  379. for b.Loop() {
  380. if _, err := decodeRow(binaryRow); err != nil {
  381. b.Fatal(err)
  382. }
  383. }
  384. })
  385. b.Run("decode_json", func(b *testing.B) {
  386. b.ReportAllocs()
  387. for b.Loop() {
  388. if _, err := decodeRow(jsonRow); err != nil {
  389. b.Fatal(err)
  390. }
  391. }
  392. })
  393. }