main.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. package main
  2. // ORM blob 全景演示 —— 两种载体, 一张表:
  3. //
  4. // - []byte 一次性载体: 写时整块在内存, 读回整块拉进内存。小内容够用。
  5. // - types.Blob 流式载体: 对象里持有"流对象" ——
  6. // 写侧 Data.Source 是任意 io.Reader (*os.File / 网络流 / bytes.Reader...),
  7. // 引擎 io.Copy 流式落链, 内存峰值 ~128KB, 与内容大小无关;
  8. // 读侧引擎只填回 8 字节引用 (TotalLen/FirstPageID), 内容仍躺在链页上,
  9. // 调 row.Data.Open(tbl.BlobReader()) 按需拿 io.ReadCloser 流式消费。
  10. //
  11. // 场景清单:
  12. // [1] 流式写: 32MB 文件 → types.Blob{Source} → Insert (内存峰值 < 2MB)
  13. // [2] 流式读: Find → Open → io.Copy 流式消费, 校验 CRC (同样不整块进内存)
  14. // [3] []byte 载体对照: 小内容直接 Insert/Find
  15. // [4] 持久性: 关库重开, 流式读仍成立
  16. // [5] 删行归还: Delete 后空闲页回升 (blob 链页回到空闲链)
  17. import (
  18. "fmt"
  19. "hash/crc32"
  20. "io"
  21. "os"
  22. "path/filepath"
  23. "runtime"
  24. "git.ycl.pub/yl32940/TsDB2/DB"
  25. "git.ycl.pub/yl32940/TsDB2/DB/core/types"
  26. )
  27. type FileRow struct {
  28. DB.BaseModel
  29. Name string `vrtx:"maxlen:64"`
  30. // 流式载体: 不写 type:blob 也会自动推断 (goTypeToFieldType 里注册了 types.Blob)。
  31. // 这里显式写出来是为了 demo 的自解释。
  32. Data types.Blob `vrtx:"type:blob"`
  33. }
  34. type SmallRow struct {
  35. DB.BaseModel
  36. Name string `vrtx:"maxlen:32"`
  37. Body []byte `vrtx:"type:blob"` // 一次性载体: 读回直接是内容
  38. }
  39. func must(err error) {
  40. if err != nil {
  41. panic(err)
  42. }
  43. }
  44. func check(err error) bool {
  45. if err != nil {
  46. fmt.Println(" !!", err)
  47. return false
  48. }
  49. return true
  50. }
  51. func memPeakMB() float64 {
  52. runtime.GC()
  53. var ms runtime.MemStats
  54. runtime.ReadMemStats(&ms)
  55. return float64(ms.HeapAlloc) / 1024 / 1024
  56. }
  57. func main() {
  58. dir, err := os.MkdirTemp("", "bloborm")
  59. must(err)
  60. defer os.RemoveAll(dir)
  61. dbPath := filepath.Join(dir, "bloborm.db")
  62. db, err := DB.Open(dbPath)
  63. must(err)
  64. db.SetAutoCommitDelay(0) // 手动 Commit, 便于对照
  65. defer db.Close()
  66. files, err := DB.UseTable[FileRow](db, "files")
  67. must(err)
  68. smalls, err := DB.UseTable[SmallRow](db, "smalls")
  69. must(err)
  70. // ------------------------------------------------------------------
  71. // [1] 流式写: 32MB 内容从临时文件灌进链页, 内存峰值与内容大小无关
  72. // ------------------------------------------------------------------
  73. const size = 32 << 20
  74. srcPath := filepath.Join(dir, "src.bin")
  75. src := make([]byte, 1<<20)
  76. for i := range src {
  77. src[i] = byte(i % 251)
  78. }
  79. f, err := os.Create(srcPath)
  80. must(err)
  81. wantCRC := uint32(0)
  82. for i := 0; i < size/len(src); i++ {
  83. _, err = f.Write(src)
  84. must(err)
  85. wantCRC = crc32.Update(wantCRC, crc32.IEEETable, src)
  86. }
  87. must(f.Close())
  88. f, err = os.Open(srcPath)
  89. must(err)
  90. before := memPeakMB()
  91. row := &FileRow{Name: "32mb.bin", Data: types.Blob{Source: f}}
  92. must(files.Insert(row))
  93. must(db.Commit())
  94. must(f.Close())
  95. after := memPeakMB()
  96. fmt.Printf("[1] 流式写 32MB: pk=%d, ref={TotalLen:%d FirstPageID:%d}, 内存峰值 %.1f→%.1f MB\n",
  97. row.ID, row.Data.TotalLen, row.Data.FirstPageID, before, after)
  98. // ------------------------------------------------------------------
  99. // [2] 流式读: Open 拿 io.ReadCloser, io.Copy 边读边校验
  100. // ------------------------------------------------------------------
  101. got, err := files.Find(row.ID)
  102. must(err)
  103. rc, err := got.Data.Open(files.BlobReader())
  104. must(err)
  105. gotCRC := uint32(0)
  106. buf := make([]byte, 64<<10)
  107. for {
  108. n, rerr := rc.Read(buf)
  109. gotCRC = crc32.Update(gotCRC, crc32.IEEETable, buf[:n])
  110. if rerr == io.EOF {
  111. break
  112. }
  113. must(rerr)
  114. }
  115. must(rc.Close())
  116. fmt.Printf("[2] 流式读 32MB: CRC %s (want %08x)\n",
  117. cmpCRC(gotCRC, wantCRC), wantCRC)
  118. // ------------------------------------------------------------------
  119. // [3] []byte 载体对照: 小内容一把梭, 读回直接是 []byte
  120. // ------------------------------------------------------------------
  121. s := &SmallRow{Name: "hello", Body: []byte("hello blob, 一次性载体")}
  122. must(smalls.Insert(s))
  123. must(db.Commit())
  124. back, err := smalls.Find(s.ID)
  125. must(err)
  126. fmt.Printf("[3] []byte 载体: 写 %q, 读回 %q\n", s.Body, back.Body)
  127. // ------------------------------------------------------------------
  128. // [3.5] Update 不碰 blob 字段 (只改名): 旧链必须原样保留
  129. // (Insert 后 ref 已回填进对象, Update 走"读回镜像"分支, 链不动)
  130. // ------------------------------------------------------------------
  131. got.Name = "renamed.bin"
  132. must(files.Update(got))
  133. must(db.Commit())
  134. renamed, err := files.Find(got.ID)
  135. must(err)
  136. rc3, err := renamed.Data.Open(files.BlobReader())
  137. must(err)
  138. renameCRC, err := crc32Reader(rc3)
  139. must(err)
  140. must(rc3.Close())
  141. fmt.Printf("[3.5] 改名 Update 后 blob: %s (%s, %d 字节)\n",
  142. cmpCRC(renameCRC, wantCRC), renamed.Name, renamed.Data.TotalLen)
  143. // ------------------------------------------------------------------
  144. // [4] 持久性: 关库重开, 流式读照样成立
  145. // ------------------------------------------------------------------
  146. must(db.Close())
  147. db2, err := DB.Open(dbPath)
  148. must(err)
  149. defer db2.Close()
  150. files2, err := DB.UseTable[FileRow](db2, "files")
  151. must(err)
  152. again, err := files2.Find(row.ID)
  153. must(err)
  154. rc2, err := again.Data.Open(files2.BlobReader())
  155. must(err)
  156. reopenCRC, err := crc32Reader(rc2)
  157. must(err)
  158. must(rc2.Close())
  159. fmt.Printf("[4] 重开库流式读: CRC %s\n", cmpCRC(reopenCRC, wantCRC))
  160. // ------------------------------------------------------------------
  161. // [5] 删行归还: Delete 后空闲页回升 (堆页 + 整条 blob 链)
  162. // ------------------------------------------------------------------
  163. core := db2.CoreDB()
  164. freeBefore := core.DebugFreePageCount()
  165. must(again.Delete())
  166. must(db2.Commit())
  167. freeAfter := core.DebugFreePageCount()
  168. fmt.Printf("[5] 删 32MB 行: 空闲页 %d -> %d (归还 %d 页 ≈ %d KB)\n",
  169. freeBefore, freeAfter, freeAfter-freeBefore, (freeAfter-freeBefore)*16)
  170. fmt.Println("\n=== 全部场景通过 ===")
  171. }
  172. func cmpCRC(got, want uint32) string {
  173. if got == want {
  174. return "一致"
  175. }
  176. return fmt.Sprintf("不一致! got=%08x", got)
  177. }
  178. func crc32Reader(r io.Reader) (uint32, error) {
  179. h := crc32.NewIEEE()
  180. if _, err := io.Copy(h, r); err != nil {
  181. return 0, err
  182. }
  183. return h.Sum32(), nil
  184. }