| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152 |
- package core
- import (
- "errors"
- "git.ycl.pub/yl32940/TsDB2/DB/core/page"
- "git.ycl.pub/yl32940/TsDB2/DB/core/types"
- )
- // blobChainWriter 把 io.Writer.Write 调用直接转为"写 blob 链尾", 自动跨页 NewPage。
- //
- // 约束:
- // - 一页 = 一个 blob chunk (与 HeapPage 那种"一页多块"完全不同);
- // - 链由 PageHdr.NextPage 串, 最后一页 NextPage = 0;
- // - 不维护 RefCount (单 owner, 删除时整链 FreeChain 一次 O(1) 还链)。
- //
- // 所有页头统一用 PageHdr (共 20 字节, 与 heap/index/schema-ext 一致):
- // PageHdr 占每页前 20 字节, 后续 Data 区就是 raw blob 字节, 无 chunk 头、无长度前缀。
- //
- // 错误:
- //
- // - ErrBlobTooLarge: 单 blob 链长超过 BlobChainMax (uint32 上限);
- //
- // 调用方应中止 io.Copy 并 tx.Rollback(), 由事务模型自动还链。
- type blobChainWriter struct {
- db *DB
- firstPage uint32 // 链首
- tailPage uint32 // 链尾 (Write 时优先填这一页, 满了再 NewPage)
- tailUsed uint16 // tailPage.Data 区已用字节
- chainLen uint32 // 已分配的页数 (含第一页)
- totalBytes uint64 // 已写入字节数 (= TotalLen)
- }
- // newBlobChainWriter 预分配第一页并把 writer 初始化到该页。
- //
- // 之所以必须"先 NewPage 再 Write": snappy.Writer 在第一次 Write 之前会先写一个
- // stream header (几字节), 如果第一页还没 NewPage, snappy 的 Write 会落到 nil tailPage
- // 上 panic。预开第一页后, 写入路径与 snappy 不耦合。
- //
- // 参数是 *DB 而不是 *Transaction: 链页的分配 (NewPage) 与写盘 (file.WritePage) 都不需要
- // 事务参与 —— 事务只是在有 currentTxn 时顺带把新页登记进 allocated (回滚时归还)。
- // 于是"没有显式事务"的写入路径 (core 层 InsertRow 直接塞 []byte, 见 Table.blobFieldRef)
- // 也能复用同一个 writer。
- func newBlobChainWriter(db *DB) (*blobChainWriter, error) {
- pid := db.NewPage()
- buf := make([]byte, PageSize)
- hdr := page.HdrOf(buf)
- hdr.Init(page.BlobIndex, pid)
- hdr.NextPage = 0
- hdr.UseSize = page.PageHdrSize
- if err := db.file.WritePage(pid, buf); err != nil {
- return nil, err
- }
- return &blobChainWriter{
- db: db,
- firstPage: pid,
- tailPage: pid,
- tailUsed: 0,
- chainLen: 1,
- totalBytes: 0,
- }, nil
- }
- // Write 实现 io.Writer. 一次 Write 可能跨多页, 由内部 NewPage 衔接.
- //
- // 返回值语义: 总是返回 (len(p), nil) 或 (已写入字节, err).
- // 错误只可能是 ErrBlobTooLarge, 一旦发生, 链已被 NewPage 占用,
- // 调用方应中止 io.Copy 并 tx.Rollback() 走事务模型回收整链.
- func (w *blobChainWriter) Write(p []byte) (int, error) {
- const pageData = uint32(PageSize - page.PageHdrSize) // 16364
- written := 0
- for written < len(p) {
- // 1. 当前尾页已满? 开新页并挂链
- if uint32(w.tailUsed) >= pageData {
- if w.chainLen >= BlobChainMax {
- return written, ErrBlobTooLarge
- }
- newPID := w.db.NewPage()
- // 挂到老尾页的 NextPage
- tailBuf := make([]byte, PageSize)
- if err := w.db.file.ReadPage(w.tailPage, tailBuf); err != nil {
- return written, err
- }
- page.HdrOf(tailBuf).NextPage = newPID
- if err := w.db.file.WritePage(w.tailPage, tailBuf); err != nil {
- return written, err
- }
- // 初始化新页
- newBuf := make([]byte, PageSize)
- hdr := page.HdrOf(newBuf)
- hdr.Init(page.BlobIndex, newPID)
- hdr.NextPage = 0
- hdr.UseSize = page.PageHdrSize
- if err := w.db.file.WritePage(newPID, newBuf); err != nil {
- return written, err
- }
- // 推进尾指针
- w.tailPage = newPID
- w.tailUsed = 0
- w.chainLen++
- }
- // 2. 计算本批可写字节数 (min(本页剩余空间, p 剩余字节))
- space := uint32(pageData - uint32(w.tailUsed))
- remaining := uint32(len(p) - written)
- n := space
- if remaining < n {
- n = remaining
- }
- // 3. 读尾页 + 写本批
- tailBuf := make([]byte, PageSize)
- if err := w.db.file.ReadPage(w.tailPage, tailBuf); err != nil {
- return written, err
- }
- copy(tailBuf[page.PageHdrSize+uint32(w.tailUsed):], p[written:written+int(n)])
- page.HdrOf(tailBuf).UseSize = page.PageHdrSize + uint32(w.tailUsed) + n
- if err := w.db.file.WritePage(w.tailPage, tailBuf); err != nil {
- return written, err
- }
- w.tailUsed += uint16(n)
- written += int(n)
- w.totalBytes += uint64(n)
- }
- return written, nil
- }
- // ErrBlobTooLarge 单 blob 链长超过 BlobChainMax (256K 页 = ~4GB).
- var ErrBlobTooLarge = errors.New("blob chain exceeds maximum length")
- // BlobChainMax 单 blob 链页数上限, 值 256 * 1024.
- // 定义在 const.go (库级常量统一管理), 本文件直接引用.
- //
- // 上限由来: 链长用 uint32 计数 (避免 RowFieldRef 里的 2 字节截断隐患),
- // 单页 16KB, 256K 页 ≈ 4GB. 这个量级对绝大多数 binary 大对象够用;
- // 超大文件 (>4GB) 应改用对象存储而非单字段.
- // BlobRef 行内 8 字节引用. 类型权威定义见 types.BlobRef (本类型为 alias, 历史代码兼容)。
- //
- // [TotalLen 4B BE][FirstPageID 4B BE]
- //
- // TotalLen = 原始 (解压后) 字节数;
- // FirstPageID = 0 表示空 blob (与 TotalLen = 0 一同出现).
- //
- // 【为什么要 alias】types 包持有 BlobRef 是为了让 EncodeValue / DecodeValue 不必反向依赖
- // core 包 (core 已经依赖 types, 反过来会循环)。core 内的历史 API (BlobCommit / BlobRead
- // / BlobFree 等) 全部继续用这个名字, 直接 type 别名过去, 调用方代码零改动。
- //
- // types.BlobRef 已定义 Empty / SizeBytes 方法, alias 自动继承, 不在这里再写一次
- // (否则是 duplicate method)。
- type BlobRef = types.BlobRef
|