blob_test.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. package DB
  2. import (
  3. "bytes"
  4. "path/filepath"
  5. "testing"
  6. "git.ycl.pub/yl32940/TsDB2/DB/core"
  7. "git.ycl.pub/yl32940/TsDB2/DB/core/types"
  8. )
  9. // blobOrmRow blob 列在 ORM 层的载体: []byte + `type:blob` 标签。
  10. //
  11. // 载体只能是 []byte (见 types.go 的 blob 载体校验: string 载体按定长串语义会被第一个
  12. // 0x00 截断), 所以这里也不拿 string 试。
  13. type blobOrmRow struct {
  14. BaseModel
  15. Name string `vrtx:"maxlen:32"`
  16. Body []byte `vrtx:"type:blob"`
  17. }
  18. // TestBlobColumnORMRoundTrip blob 列在 ORM 层的完整往返。
  19. //
  20. // 钉三件事:
  21. // 1. 写: []byte 直接 Insert 即可, 调用方不用手动开事务 / 调 BlobCommit —— ORM 自动接管
  22. // 这个边界 (Table.Insert 里那个 tx), 内容落链页;
  23. // 2. 读: []byte 载体拿到的是【内容】而不是 8 字节引用 —— 引用只活在行内, 读回侧不把链
  24. // 解开的话字段永远是 nil (写进去什么也读不回来);
  25. // 3. 持久: 关库重开之后照样读得回来 (表是从盘上加载的, 读链要靠 db 反向注入)。
  26. func TestBlobColumnORMRoundTrip(t *testing.T) {
  27. dbPath := filepath.Join(t.TempDir(), "blob_orm.db")
  28. db, err := Open(dbPath)
  29. if err != nil {
  30. t.Fatalf("打开: %v", err)
  31. }
  32. // 不 defer 关这个句柄: 本用例中途要显式关掉再重开 (见下), 重复 Close 会走一遍
  33. // 已经关掉的 file。fail-fast 时留下的句柄由进程退出回收。
  34. db.SetAutoCommitDelay(0)
  35. tbl, err := UseTable[blobOrmRow](db, "bloborm")
  36. if err != nil {
  37. t.Fatalf("建表: %v", err)
  38. }
  39. // big: 特意超过 Dynamicbyte 的单值上限 (内容整块落一个堆页), 只有 blob 链装得下。
  40. big := make([]byte, core.MaxBlobSize*2+5)
  41. for i := range big {
  42. big[i] = byte(i * 13 % 251)
  43. }
  44. // zeros: 内容含 0x00 (二进制里合法, 按字符串语义读会被静默截断)。
  45. zeros := []byte{0x00, 0x01, 0x00, 0xFF, 0x00, 0x7F}
  46. rows := []*blobOrmRow{
  47. {Name: "small", Body: []byte("hello blob")},
  48. {Name: "big", Body: big},
  49. {Name: "zeros", Body: zeros},
  50. {Name: "empty", Body: []byte{}},
  51. }
  52. for _, r := range rows {
  53. if err := tbl.Insert(r); err != nil {
  54. t.Fatalf("Insert(%s): %v", r.Name, err)
  55. }
  56. }
  57. if err := db.Commit(); err != nil {
  58. t.Fatalf("提交: %v", err)
  59. }
  60. check := func(stage string) {
  61. for _, want := range rows {
  62. got, err := tbl.Find(want.ID)
  63. if err != nil {
  64. t.Fatalf("%s: Find(%d, %s): %v", stage, want.ID, want.Name, err)
  65. }
  66. if !bytes.Equal(got.Body, want.Body) {
  67. t.Fatalf("%s: %s 行内容不一致: 读回 %d 字节, 写入 %d 字节",
  68. stage, want.Name, len(got.Body), len(want.Body))
  69. }
  70. }
  71. }
  72. check("提交后")
  73. // 行内只放 8 字节引用: 内容不跟着行走 —— 这是"内容可以跨页"的前提。
  74. coreTbl, ok := db.CoreDB().FindTable("bloborm")
  75. if !ok {
  76. t.Fatal("core 找不到表 bloborm")
  77. }
  78. fvs, err := coreTbl.FindByPK(rows[1].ID)
  79. if err != nil {
  80. t.Fatalf("FindByPK: %v", err)
  81. }
  82. var ref types.BlobRef
  83. var seen bool
  84. for _, fv := range fvs {
  85. if r, isRef := fv.Value.(types.BlobRef); isRef {
  86. ref, seen = r, true
  87. }
  88. }
  89. if !seen {
  90. t.Fatalf("行内 blob 列的值不是 types.BlobRef: %+v (内容被内联进行里了?)", fvs)
  91. }
  92. if int(ref.TotalLen) != len(big) {
  93. t.Errorf("ref.TotalLen = %d, 期望 %d", ref.TotalLen, len(big))
  94. }
  95. // 关库重开: 盘上加载出来的表读 blob 依赖 db 反向注入 (bindDB)。
  96. if err := db.Close(); err != nil {
  97. t.Fatalf("关库: %v", err)
  98. }
  99. db2, err := Open(dbPath)
  100. if err != nil {
  101. t.Fatalf("重开: %v", err)
  102. }
  103. defer func() { _ = db2.Close() }()
  104. db2.SetAutoCommitDelay(0)
  105. tbl2, err := UseTable[blobOrmRow](db2, "bloborm")
  106. if err != nil {
  107. t.Fatalf("重开建表: %v", err)
  108. }
  109. for _, want := range rows {
  110. got, err := tbl2.Find(want.ID)
  111. if err != nil {
  112. t.Fatalf("重开: Find(%d, %s): %v", want.ID, want.Name, err)
  113. }
  114. if !bytes.Equal(got.Body, want.Body) {
  115. t.Fatalf("重开: %s 行内容不一致: 读回 %d 字节, 写入 %d 字节",
  116. want.Name, len(got.Body), len(want.Body))
  117. }
  118. }
  119. }
  120. // TestBlobColumnORMUpdateReplacesChain blob 列更新到新的内容后, 读回的是新内容。
  121. //
  122. // 更新走的是另一条路径 (serializeRowInternal 的 oldBlobRefs 释放 + 新链落地), 与插入
  123. // 不是同一段代码 —— 只在插入上验一遍, 更新时"旧链没释放 / 新值没落链"都不会被发现。
  124. func TestBlobColumnORMUpdateReplacesChain(t *testing.T) {
  125. db, err := Open(filepath.Join(t.TempDir(), "blob_update.db"))
  126. if err != nil {
  127. t.Fatalf("打开: %v", err)
  128. }
  129. defer func() { _ = db.Close() }()
  130. db.SetAutoCommitDelay(0)
  131. tbl, err := UseTable[blobOrmRow](db, "blobupd")
  132. if err != nil {
  133. t.Fatalf("建表: %v", err)
  134. }
  135. obj := &blobOrmRow{Name: "n", Body: []byte("first")}
  136. if err := tbl.Insert(obj); err != nil {
  137. t.Fatalf("Insert: %v", err)
  138. }
  139. next := make([]byte, core.MaxBlobSize+3)
  140. for i := range next {
  141. next[i] = byte(i % 97)
  142. }
  143. obj.Body = next
  144. if err := tbl.Update(obj); err != nil {
  145. t.Fatalf("Update: %v", err)
  146. }
  147. if err := db.Commit(); err != nil {
  148. t.Fatalf("提交: %v", err)
  149. }
  150. got, err := tbl.Find(obj.ID)
  151. if err != nil {
  152. t.Fatalf("Find: %v", err)
  153. }
  154. if !bytes.Equal(got.Body, next) {
  155. t.Fatalf("更新后内容不一致: 读回 %d 字节, 写入 %d 字节", len(got.Body), len(next))
  156. }
  157. // 再读一次仍是同一份内容 (旧链若被误释放, 这里会读出损坏或短读)。
  158. again, err := tbl.Find(obj.ID)
  159. if err != nil {
  160. t.Fatalf("二次 Find: %v", err)
  161. }
  162. if !bytes.Equal(again.Body, next) {
  163. t.Fatalf("二次读内容不一致: %d 字节", len(again.Body))
  164. }
  165. }