package core import ( "errors" "io" "git.ycl.pub/yl32940/TsDB2/DB/core/page" ) // blobChainReader 把"遍历 blob 链 (PageHdr.NextPage)"包装成 io.Reader. // // 与 blobChainWriter 一一对应: // - 一页 = 一个 blob chunk, 整页 Data 区是 raw 字节; // - 链由 PageHdr.NextPage 串, Read 读到 NextPage = 0 时返回 io.EOF. // // 调用方拿到 *blobChainReader 后可直接包成 snappy.Reader 或 io.ReadAll 等. type blobChainReader struct { db *DB cur uint32 // 当前页 PageID (0 表示已走完链) off uint16 // 当前页 Data 区已读偏移 pageBuf []byte // 当前页缓存 (复用, 避免每页 make) done bool // 已读到 EOF } // newBlobChainReader 从首页开始构造 reader. func newBlobChainReader(db *DB, firstPageID uint32) (*blobChainReader, error) { if firstPageID == 0 { return &blobChainReader{db: db, done: true}, nil } r := &blobChainReader{ db: db, cur: firstPageID, off: 0, pageBuf: make([]byte, PageSize), } if err := db.file.ReadPage(firstPageID, r.pageBuf); err != nil { return nil, err } return r, nil } // Read 实现 io.Reader. 一次 Read 可能跨页 (内部按 PageHdr.NextPage 衔接). // // 返回 (n, nil) 表示读了 n 字节但未到 EOF, (n, io.EOF) 表示链读完且 n>0, // (0, io.EOF) 表示调用前已在链尾. 错误一律返回 err (含 io.ErrUnexpectedEOF). func (r *blobChainReader) Read(p []byte) (int, error) { if r.done { return 0, io.EOF } const pageData = uint32(PageSize - page.PageHdrSize) total := 0 for total < len(p) { used := page.HdrOf(r.pageBuf).UseSize - page.PageHdrSize // 注意用 int 计算: 调用方 (如 snappy 内部 io.ReadFull) 的 p 可达 64KB+, // 若用 uint16(len(p)-total) 会在 >65535 时截断为 0, 导致跳页丢数据. avail := int(used) - int(r.off) if avail > 0 { n := len(p) - total if n > avail { n = avail } copy(p[total:total+n], r.pageBuf[int(page.PageHdrSize)+int(r.off):int(page.PageHdrSize)+int(r.off)+n]) r.off += uint16(n) total += n if total == len(p) { return total, nil } } // 当前页已读完, 跳下一页 next := page.HdrOf(r.pageBuf).NextPage if next == 0 { r.done = true r.cur = 0 if total > 0 { return total, io.EOF } return 0, io.EOF } if err := r.db.file.ReadPage(next, r.pageBuf); err != nil { return total, err } r.cur = next r.off = 0 } return total, nil } // ErrBlobCorrupted blob 解压后字节数与 BlobRef.TotalLen 不一致 —— // // 意味着落盘链被外部破坏 (页被复用、链指针写错、压缩标志被翻等), // 不应继续把可疑字节返回给调用方. var ErrBlobCorrupted = errors.New("blob corrupted: decoded length mismatches TotalLen")