| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 |
- package DB
- import (
- "bytes"
- "path/filepath"
- "testing"
- "git.ycl.pub/yl32940/TsDB2/DB/core"
- "git.ycl.pub/yl32940/TsDB2/DB/core/types"
- )
- // blobOrmRow blob 列在 ORM 层的载体: []byte + `type:blob` 标签。
- //
- // 载体只能是 []byte (见 types.go 的 blob 载体校验: string 载体按定长串语义会被第一个
- // 0x00 截断), 所以这里也不拿 string 试。
- type blobOrmRow struct {
- BaseModel
- Name string `vrtx:"maxlen:32"`
- Body []byte `vrtx:"type:blob"`
- }
- // TestBlobColumnORMRoundTrip blob 列在 ORM 层的完整往返。
- //
- // 钉三件事:
- // 1. 写: []byte 直接 Insert 即可, 调用方不用手动开事务 / 调 BlobCommit —— ORM 自动接管
- // 这个边界 (Table.Insert 里那个 tx), 内容落链页;
- // 2. 读: []byte 载体拿到的是【内容】而不是 8 字节引用 —— 引用只活在行内, 读回侧不把链
- // 解开的话字段永远是 nil (写进去什么也读不回来);
- // 3. 持久: 关库重开之后照样读得回来 (表是从盘上加载的, 读链要靠 db 反向注入)。
- func TestBlobColumnORMRoundTrip(t *testing.T) {
- dbPath := filepath.Join(t.TempDir(), "blob_orm.db")
- db, err := Open(dbPath)
- if err != nil {
- t.Fatalf("打开: %v", err)
- }
- // 不 defer 关这个句柄: 本用例中途要显式关掉再重开 (见下), 重复 Close 会走一遍
- // 已经关掉的 file。fail-fast 时留下的句柄由进程退出回收。
- db.SetAutoCommitDelay(0)
- tbl, err := UseTable[blobOrmRow](db, "bloborm")
- if err != nil {
- t.Fatalf("建表: %v", err)
- }
- // big: 特意超过 Dynamicbyte 的单值上限 (内容整块落一个堆页), 只有 blob 链装得下。
- big := make([]byte, core.MaxBlobSize*2+5)
- for i := range big {
- big[i] = byte(i * 13 % 251)
- }
- // zeros: 内容含 0x00 (二进制里合法, 按字符串语义读会被静默截断)。
- zeros := []byte{0x00, 0x01, 0x00, 0xFF, 0x00, 0x7F}
- rows := []*blobOrmRow{
- {Name: "small", Body: []byte("hello blob")},
- {Name: "big", Body: big},
- {Name: "zeros", Body: zeros},
- {Name: "empty", Body: []byte{}},
- }
- for _, r := range rows {
- if err := tbl.Insert(r); err != nil {
- t.Fatalf("Insert(%s): %v", r.Name, err)
- }
- }
- if err := db.Commit(); err != nil {
- t.Fatalf("提交: %v", err)
- }
- check := func(stage string) {
- for _, want := range rows {
- got, err := tbl.Find(want.ID)
- if err != nil {
- t.Fatalf("%s: Find(%d, %s): %v", stage, want.ID, want.Name, err)
- }
- if !bytes.Equal(got.Body, want.Body) {
- t.Fatalf("%s: %s 行内容不一致: 读回 %d 字节, 写入 %d 字节",
- stage, want.Name, len(got.Body), len(want.Body))
- }
- }
- }
- check("提交后")
- // 行内只放 8 字节引用: 内容不跟着行走 —— 这是"内容可以跨页"的前提。
- coreTbl, ok := db.CoreDB().FindTable("bloborm")
- if !ok {
- t.Fatal("core 找不到表 bloborm")
- }
- fvs, err := coreTbl.FindByPK(rows[1].ID)
- if err != nil {
- t.Fatalf("FindByPK: %v", err)
- }
- var ref types.BlobRef
- var seen bool
- for _, fv := range fvs {
- if r, isRef := fv.Value.(types.BlobRef); isRef {
- ref, seen = r, true
- }
- }
- if !seen {
- t.Fatalf("行内 blob 列的值不是 types.BlobRef: %+v (内容被内联进行里了?)", fvs)
- }
- if int(ref.TotalLen) != len(big) {
- t.Errorf("ref.TotalLen = %d, 期望 %d", ref.TotalLen, len(big))
- }
- // 关库重开: 盘上加载出来的表读 blob 依赖 db 反向注入 (bindDB)。
- if err := db.Close(); err != nil {
- t.Fatalf("关库: %v", err)
- }
- db2, err := Open(dbPath)
- if err != nil {
- t.Fatalf("重开: %v", err)
- }
- defer func() { _ = db2.Close() }()
- db2.SetAutoCommitDelay(0)
- tbl2, err := UseTable[blobOrmRow](db2, "bloborm")
- if err != nil {
- t.Fatalf("重开建表: %v", err)
- }
- for _, want := range rows {
- got, err := tbl2.Find(want.ID)
- if err != nil {
- t.Fatalf("重开: Find(%d, %s): %v", want.ID, want.Name, err)
- }
- if !bytes.Equal(got.Body, want.Body) {
- t.Fatalf("重开: %s 行内容不一致: 读回 %d 字节, 写入 %d 字节",
- want.Name, len(got.Body), len(want.Body))
- }
- }
- }
- // TestBlobColumnORMUpdateReplacesChain blob 列更新到新的内容后, 读回的是新内容。
- //
- // 更新走的是另一条路径 (serializeRowInternal 的 oldBlobRefs 释放 + 新链落地), 与插入
- // 不是同一段代码 —— 只在插入上验一遍, 更新时"旧链没释放 / 新值没落链"都不会被发现。
- func TestBlobColumnORMUpdateReplacesChain(t *testing.T) {
- db, err := Open(filepath.Join(t.TempDir(), "blob_update.db"))
- if err != nil {
- t.Fatalf("打开: %v", err)
- }
- defer func() { _ = db.Close() }()
- db.SetAutoCommitDelay(0)
- tbl, err := UseTable[blobOrmRow](db, "blobupd")
- if err != nil {
- t.Fatalf("建表: %v", err)
- }
- obj := &blobOrmRow{Name: "n", Body: []byte("first")}
- if err := tbl.Insert(obj); err != nil {
- t.Fatalf("Insert: %v", err)
- }
- next := make([]byte, core.MaxBlobSize+3)
- for i := range next {
- next[i] = byte(i % 97)
- }
- obj.Body = next
- if err := tbl.Update(obj); err != nil {
- t.Fatalf("Update: %v", err)
- }
- if err := db.Commit(); err != nil {
- t.Fatalf("提交: %v", err)
- }
- got, err := tbl.Find(obj.ID)
- if err != nil {
- t.Fatalf("Find: %v", err)
- }
- if !bytes.Equal(got.Body, next) {
- t.Fatalf("更新后内容不一致: 读回 %d 字节, 写入 %d 字节", len(got.Body), len(next))
- }
- // 再读一次仍是同一份内容 (旧链若被误释放, 这里会读出损坏或短读)。
- again, err := tbl.Find(obj.ID)
- if err != nil {
- t.Fatalf("二次 Find: %v", err)
- }
- if !bytes.Equal(again.Body, next) {
- t.Fatalf("二次读内容不一致: %d 字节", len(again.Body))
- }
- }
|