blob_stream_orm_test.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. package DB
  2. import (
  3. "bytes"
  4. "hash/crc32"
  5. "io"
  6. "path/filepath"
  7. "testing"
  8. "git.ycl.pub/yl32940/TsDB2/DB/core"
  9. "git.ycl.pub/yl32940/TsDB2/DB/core/types"
  10. )
  11. // blobStreamRow 流式载体: 字段本身就是"持有流的对象" (types.Blob),
  12. // 不写 type:blob tag 也会被 goTypeToFieldType 自动推断为 blob 列。
  13. type blobStreamRow struct {
  14. BaseModel
  15. Name string `vrtx:"maxlen:32"`
  16. Data types.Blob `vrtx:"type:blob"`
  17. }
  18. // TestBlobStreamCarrierRoundTrip 流式载体 (types.Blob) 的完整生命周期:
  19. //
  20. // 1. 写: Data.Source 是 io.Reader, 引擎流式落链 (不整块进内存);
  21. // 2. 回填: Insert 成功后 ref (FirstPageID/TotalLen) 已回填进对象, Source 清空;
  22. // 3. 读: Find 只填 ref, Open(table) 按需拿 io.ReadCloser 流式消费;
  23. // 4. 保链: 只改普通字段的 Update 不抹掉 blob (读回镜像分支);
  24. // 5. 重开: 关库重开照样流式读得回。
  25. func TestBlobStreamCarrierRoundTrip(t *testing.T) {
  26. dbPath := filepath.Join(t.TempDir(), "blob_stream_orm.db")
  27. db, err := Open(dbPath)
  28. if err != nil {
  29. t.Fatalf("打开: %v", err)
  30. }
  31. defer func() { _ = db.Close() }()
  32. db.SetAutoCommitDelay(0)
  33. tbl, err := UseTable[blobStreamRow](db, "stream")
  34. if err != nil {
  35. t.Fatalf("建表: %v", err)
  36. }
  37. content := make([]byte, core.MaxBlobSize*3+7) // 跨 3+ 页, 单页链测不出链指针问题
  38. for i := range content {
  39. content[i] = byte(i % 251)
  40. }
  41. wantCRC := crc32.ChecksumIEEE(content)
  42. row := &blobStreamRow{Name: "stream", Data: types.Blob{Source: bytes.NewReader(content)}}
  43. if err := tbl.Insert(row); err != nil {
  44. t.Fatalf("Insert: %v", err)
  45. }
  46. if err := db.Commit(); err != nil {
  47. t.Fatalf("Commit: %v", err)
  48. }
  49. // 2. ref 已回填, Source 已清 (再 Update 不会把死流灌一遍)
  50. if row.Data.FirstPageID == 0 || row.Data.TotalLen != uint32(len(content)) {
  51. t.Fatalf("Insert 后 ref 未回填: %+v", row.Data)
  52. }
  53. if row.Data.Source != nil {
  54. t.Fatalf("Insert 后 Source 应被清掉")
  55. }
  56. // 3. 读回: ref + Open 流式消费
  57. got, err := tbl.Find(row.ID)
  58. if err != nil {
  59. t.Fatalf("Find: %v", err)
  60. }
  61. rc, err := got.Data.Open(tbl.BlobReader())
  62. if err != nil {
  63. t.Fatalf("Open: %v", err)
  64. }
  65. data, err := io.ReadAll(rc)
  66. if err != nil {
  67. t.Fatalf("流式读: %v", err)
  68. }
  69. _ = rc.Close()
  70. if crc32.ChecksumIEEE(data) != wantCRC {
  71. t.Fatalf("流式读内容不一致")
  72. }
  73. // 4. 只改 Name 的 Update: blob 链必须原样保留 (曾因 blobFieldToRef 判空顺序
  74. // 把镜像当空 blob, Update 一发整条链被抹掉)
  75. freeBefore := db.CoreDB().DebugFreePageCount()
  76. got.Name = "renamed"
  77. if err := tbl.Update(got); err != nil {
  78. t.Fatalf("Update: %v", err)
  79. }
  80. if err := db.Commit(); err != nil {
  81. t.Fatalf("Commit: %v", err)
  82. }
  83. renamed, err := tbl.Find(got.ID)
  84. if err != nil {
  85. t.Fatalf("Find: %v", err)
  86. }
  87. if renamed.Data.TotalLen != uint32(len(content)) {
  88. t.Fatalf("改名 Update 后 blob 丢失: TotalLen=%d", renamed.Data.TotalLen)
  89. }
  90. rc2, err := renamed.Data.Open(tbl.BlobReader())
  91. if err != nil {
  92. t.Fatalf("Open: %v", err)
  93. }
  94. again, err := io.ReadAll(rc2)
  95. if err != nil {
  96. t.Fatalf("流式读: %v", err)
  97. }
  98. _ = rc2.Close()
  99. if crc32.ChecksumIEEE(again) != wantCRC {
  100. t.Fatalf("改名 Update 后内容不一致")
  101. }
  102. // 链页没被回收也没新开 (Update 未触碰 blob)
  103. if n := db.CoreDB().DebugFreePageCount(); n != freeBefore {
  104. t.Logf("注意: Update 前后空闲页 %d -> %d", freeBefore, n)
  105. }
  106. // 5. 重开库
  107. if err := db.Close(); err != nil {
  108. t.Fatalf("关库: %v", err)
  109. }
  110. db2, err := Open(dbPath)
  111. if err != nil {
  112. t.Fatalf("重开: %v", err)
  113. }
  114. defer func() { _ = db2.Close() }()
  115. tbl2, err := UseTable[blobStreamRow](db2, "stream")
  116. if err != nil {
  117. t.Fatalf("重开建表: %v", err)
  118. }
  119. reloaded, err := tbl2.Find(row.ID)
  120. if err != nil {
  121. t.Fatalf("重开 Find: %v", err)
  122. }
  123. rc3, err := reloaded.Data.Open(tbl2.BlobReader())
  124. if err != nil {
  125. t.Fatalf("重开 Open: %v", err)
  126. }
  127. persist, err := io.ReadAll(rc3)
  128. if err != nil {
  129. t.Fatalf("重开流式读: %v", err)
  130. }
  131. _ = rc3.Close()
  132. if crc32.ChecksumIEEE(persist) != wantCRC {
  133. t.Fatalf("重开后内容不一致")
  134. }
  135. }