main.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. package main
  2. import (
  3. "bytes"
  4. "crypto/rand"
  5. "fmt"
  6. "io"
  7. "os"
  8. "time"
  9. "TsDB2/DB"
  10. "TsDB2/DB/core"
  11. "TsDB2/DB/core/page"
  12. "TsDB2/DB/core/types"
  13. )
  14. const dbPath = "blob_demo.db"
  15. // brokenStream 模拟"前 N 字节正常, 之后 io.ErrUnexpectedEOF"(网络中断 / 文件被截断).
  16. type brokenStream struct {
  17. consumed int
  18. failAt int
  19. }
  20. func (b *brokenStream) Read(p []byte) (int, error) {
  21. if b.consumed >= b.failAt {
  22. return 0, io.ErrUnexpectedEOF
  23. }
  24. n := b.failAt - b.consumed
  25. if n > len(p) {
  26. n = len(p)
  27. }
  28. got, _ := rand.Read(p[:n])
  29. b.consumed += got
  30. return got, nil
  31. }
  32. func must(err error) {
  33. if err != nil {
  34. fmt.Println("ERR:", err)
  35. os.Exit(1)
  36. }
  37. }
  38. // fieldValue 从 FindByPK / 读回的行里按列名取值 (找不到返 nil).
  39. func fieldValue(fvs []types.FieldValue, name string) interface{} {
  40. for _, fv := range fvs {
  41. if string(fv.FieldName) == name {
  42. return fv.Value
  43. }
  44. }
  45. return nil
  46. }
  47. // blobRef 取出一行的 blob 列引用, 并按 compress 把链页内容读回来。
  48. func blobRef(t *core.Table, fvs []types.FieldValue, name string, compress types.FieldCompress) (types.BlobRef, []byte, error) {
  49. ref, ok := fieldValue(fvs, name).(types.BlobRef)
  50. if !ok {
  51. return types.BlobRef{}, nil, fmt.Errorf("列 %s 读出的不是 types.BlobRef (内容被内联进行里了?)", name)
  52. }
  53. got, err := t.BlobRead(ref, compress)
  54. return ref, got, err
  55. }
  56. func main() {
  57. os.Remove(dbPath)
  58. fmt.Println("=== TsDB2 Blob 流式存储 demo ===")
  59. fmt.Println("blob 是【普通的一列】: 与 id/name/size 同在一行, 内容单独走链页。")
  60. fmt.Println("写入时给 []byte / io.Reader / types.Blob 都行, 引擎自动流式落链, 不需要手动开事务。")
  61. fmt.Println()
  62. db, err := DB.Open(dbPath)
  63. must(err)
  64. db.SetAutoCommitDelay(0) // 关掉防抖自动提交: 每步手动 Commit, 便于对照
  65. // 四列: 三列普通列 + 一列 blob。blob 列在行内只占 8 字节引用, 与其它列平级。
  66. tbl, err := db.CoreDB().CreateTable("files", []types.FieldInfo{
  67. {FieldType: types.FieldType_Uint64, FieldName: []byte("id")},
  68. {FieldType: types.FieldType_String, FieldName: []byte("name"), Flag: types.WidthCodeFor(32)},
  69. {FieldType: types.FieldType_Int64, FieldName: []byte("size")},
  70. {FieldType: types.FieldType_Blob, FieldName: []byte("data"), Flag: types.FlagNullable},
  71. })
  72. must(err)
  73. _, err = tbl.CreateIndex("pk", page.IndexTypePrimary, []byte{0})
  74. must(err)
  75. // ------------------------------------------------------------------
  76. fmt.Println("[1] 插一行: id + name + size + data([]byte) —— 多列共存")
  77. payload := bytes.Repeat([]byte("hello blob "), 512) // ~5.5 KB
  78. _, err = tbl.InsertRowWithID(1, []types.FieldValue{
  79. {FieldName: []byte("id"), Value: uint64(1)},
  80. {FieldName: []byte("name"), Value: "readme.txt"},
  81. {FieldName: []byte("size"), Value: int64(len(payload))},
  82. {FieldName: []byte("data"), Value: payload},
  83. })
  84. must(err)
  85. must(db.Commit())
  86. fvs, err := tbl.FindByPK(1)
  87. must(err)
  88. ref1, got1, err := blobRef(tbl, fvs, "data", types.CompressNone)
  89. must(err)
  90. fmt.Printf(" 读回整行: name=%q size=%d data=%d 字节\n",
  91. string(fieldValue(fvs, "name").([]byte)), fieldValue(fvs, "size").(int64), len(got1))
  92. fmt.Printf(" 行内只放 8B 引用: ref=%+v; 内容逐字节一致: %v\n", ref1, bytes.Equal(got1, payload))
  93. fmt.Println()
  94. // ------------------------------------------------------------------
  95. fmt.Println("[2] 流式插入: 给 io.Reader (超过 Dynamicbyte 的单块上限, 链能跨页)")
  96. streamLen := int(core.MaxBlobSize)*2 + 7
  97. _, err = tbl.InsertRowWithID(2, []types.FieldValue{
  98. {FieldName: []byte("id"), Value: uint64(2)},
  99. {FieldName: []byte("name"), Value: "random.bin"},
  100. {FieldName: []byte("size"), Value: int64(streamLen)},
  101. // 流在这里被引擎 io.Copy (32KB buf) 消费完, 内存峰值与 blob 大小无关。
  102. {FieldName: []byte("data"), Value: io.LimitReader(rand.Reader, int64(streamLen))},
  103. })
  104. must(err)
  105. must(db.Commit())
  106. fvs, err = tbl.FindByPK(2)
  107. must(err)
  108. ref2, got2, err := blobRef(tbl, fvs, "data", types.CompressNone)
  109. must(err)
  110. fmt.Printf(" 写入 %d 字节 (单块堆页上限 %d) -> ref.TotalLen=%d 首页=%d, 读回 %d 字节\n",
  111. streamLen, core.MaxBlobSize, ref2.TotalLen, ref2.FirstPageID, len(got2))
  112. fmt.Println()
  113. // ------------------------------------------------------------------
  114. fmt.Println("[3] 更新同一行的 blob 列: 换内容时旧链自动归还空闲链")
  115. next := []byte("second version, 换了内容")
  116. err = tbl.UpdateRowByPK(2, []types.FieldValue{
  117. // 只传 data: 其它列未提供 = 沿用旧行原值 (不会清空)
  118. {FieldName: []byte("data"), Value: next},
  119. })
  120. must(err)
  121. must(db.Commit())
  122. fvs, err = tbl.FindByPK(2)
  123. must(err)
  124. ref3, got3, err := blobRef(tbl, fvs, "data", types.CompressNone)
  125. must(err)
  126. fmt.Printf(" 链换了: 更新前 ref=%+v / 更新后 ref=%+v\n", ref2, ref3)
  127. fmt.Printf(" 内容: %q; 其它列仍在: name=%q size=%d\n",
  128. string(got3), string(fieldValue(fvs, "name").([]byte)), fieldValue(fvs, "size").(int64))
  129. fmt.Println()
  130. // ------------------------------------------------------------------
  131. fmt.Println("[4] 流中断: 100KB 流读到 50KB 断 —— 插入失败 + Rollback, 链页归还")
  132. tx := db.BeginTransaction()
  133. _, err = tbl.InsertRowWithID(3, []types.FieldValue{
  134. {FieldName: []byte("id"), Value: uint64(3)},
  135. {FieldName: []byte("name"), Value: "broken.bin"},
  136. {FieldName: []byte("size"), Value: int64(100_000)},
  137. {FieldName: []byte("data"), Value: &brokenStream{failAt: 50_000}},
  138. })
  139. if err == nil {
  140. fmt.Println(" ERR: 期望流错误, 实际成功")
  141. os.Exit(1)
  142. }
  143. fmt.Printf(" 插入失败: %v\n", err)
  144. must(tx.Rollback())
  145. if _, err := tbl.FindByPK(3); err != nil {
  146. fmt.Println(" 回滚后 pk=3 查不到 (整行未生效), 已分配的链页随事务归还")
  147. } else {
  148. fmt.Println(" ERR: 回滚后 pk=3 却存在")
  149. os.Exit(1)
  150. }
  151. fmt.Println()
  152. // ------------------------------------------------------------------
  153. fmt.Println("[5] 100 MB 随机流 + Snappy: 内存峰值固定 (~128KB, 与 blob 大小无关)")
  154. const MB = 1024 * 1024
  155. t0 := time.Now()
  156. _, err = tbl.InsertRowWithID(4, []types.FieldValue{
  157. {FieldName: []byte("id"), Value: uint64(4)},
  158. {FieldName: []byte("name"), Value: "big.snappy"},
  159. {FieldName: []byte("size"), Value: int64(100 * MB)},
  160. // 压缩策略是运行时参数 (不落盘), 读侧必须给同一个 compress。
  161. {FieldName: []byte("data"), Value: types.Blob{
  162. Source: io.LimitReader(rand.Reader, 100*MB),
  163. Compress: types.CompressSnappy,
  164. }},
  165. })
  166. must(err)
  167. must(db.Commit())
  168. fvs, err = tbl.FindByPK(4)
  169. must(err)
  170. ref4, got4, err := blobRef(tbl, fvs, "data", types.CompressSnappy)
  171. must(err)
  172. fmt.Printf(" 写入耗时 %.1fs; ref=%+v; 读回 %d 字节 (链上 %d 字节)\n",
  173. time.Since(t0).Seconds(), ref4, len(got4), ref4.TotalLen)
  174. fmt.Println()
  175. // ------------------------------------------------------------------
  176. fmt.Println("[6] 关库重开再读: 表是从盘上加载的, blob 仍读得回来")
  177. must(db.Close())
  178. db2, err := DB.Open(dbPath)
  179. must(err)
  180. db2.SetAutoCommitDelay(0)
  181. tbl2, ok := db2.CoreDB().FindTable("files")
  182. if !ok {
  183. fmt.Println(" ERR: 重开后找不到表 files")
  184. os.Exit(1)
  185. }
  186. fvs, err = tbl2.FindByPK(1)
  187. must(err)
  188. _, got6, err := blobRef(tbl2, fvs, "data", types.CompressNone)
  189. must(err)
  190. fmt.Printf(" pk=1 内容与写入一致: %v (前 16 字节 %q)\n",
  191. bytes.Equal(got6, payload), string(got6[:min(16, len(got6))]))
  192. must(db2.Close())
  193. fmt.Println()
  194. fmt.Println("=== 全部场景通过 ===")
  195. }