| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157 |
- package DB
- import (
- "bytes"
- "path/filepath"
- "testing"
- "git.ycl.pub/yl32940/TsDB2/DB/core"
- )
- // TestBlobColumnORMTxRollback 显式事务内 ORM Insert 写 blob, 回滚后:
- // 1. 行确实没了 (FindByPK 报 not found);
- // 2. blob 链页整链归还空闲链 (Insert 注释承诺: 用户已开事务时链页经 NewPage
- // 登记进该事务, Rollback 整链回收 —— blob_stream demo 在 core 层验过,
- // ORM 路径必须同样成立, 否则回滚一次就泄漏一批链页)。
- func TestBlobColumnORMTxRollback(t *testing.T) {
- dbPath := filepath.Join(t.TempDir(), "blob_tx_rb.db")
- db, err := Open(dbPath)
- if err != nil {
- t.Fatalf("打开: %v", err)
- }
- defer func() { _ = db.Close() }()
- db.SetAutoCommitDelay(0)
- tbl, err := UseTable[blobOrmRow](db, "blobtx")
- if err != nil {
- t.Fatalf("建表: %v", err)
- }
- freeBefore := db.CoreDB().DebugFreePageCount()
- tx := db.BeginTransaction()
- big := make([]byte, core.MaxBlobSize*3+9) // 跨 3+ 个链页, 回滚归还才有可观测量
- for i := range big {
- big[i] = byte(i % 199)
- }
- obj := &blobOrmRow{Name: "tx-rb", Body: big}
- if err := tbl.Insert(obj); err != nil {
- t.Fatalf("事务内 Insert: %v", err)
- }
- if err := tx.Rollback(); err != nil {
- t.Fatalf("Rollback: %v", err)
- }
- // 行已不存在。
- if _, err := tbl.Find(obj.ID); err == nil {
- t.Fatal("回滚后行仍可读出")
- }
- // 链页归还: 空闲页数不少于回滚前 (归还的链页挂回空闲链)。
- freeAfter := db.CoreDB().DebugFreePageCount()
- if freeAfter < freeBefore {
- t.Fatalf("回滚后空闲页 %d < 回滚前 %d: blob 链页未归还", freeAfter, freeBefore)
- }
- t.Logf("空闲页: 回滚前=%d 回滚后=%d (归还 %d 页)", freeBefore, freeAfter, freeAfter-freeBefore)
- }
- // TestBlobColumnORMNullAndDelete
- // - NULL/nil/空 blob: nil 切片按"空 blob"语义 (见 convert.go: 只认指针/接口,
- // nil 切片是空值不是 NULL) 存空 ref, 读回必须是空内容而不是报错;
- // - Delete 行后, 该行 blob 链页必须归还空闲链 —— 只删行不还链是慢性泄漏,
- // 大对象场景下几轮删插就把文件撑爆。
- func TestBlobColumnORMNullAndDelete(t *testing.T) {
- dbPath := filepath.Join(t.TempDir(), "blob_null_del.db")
- db, err := Open(dbPath)
- if err != nil {
- t.Fatalf("打开: %v", err)
- }
- defer func() { _ = db.Close() }()
- db.SetAutoCommitDelay(0)
- tbl, err := UseTable[blobOrmRow](db, "blobnd")
- if err != nil {
- t.Fatalf("建表: %v", err)
- }
- // nil / 空 blob 正常往返。
- nul := &blobOrmRow{Name: "nil-body", Body: nil}
- empty := &blobOrmRow{Name: "empty-body", Body: []byte{}}
- for _, o := range []*blobOrmRow{nul, empty} {
- if err := tbl.Insert(o); err != nil {
- t.Fatalf("Insert(%s): %v", o.Name, err)
- }
- got, err := tbl.Find(o.ID)
- if err != nil {
- t.Fatalf("Find(%s): %v", o.Name, err)
- }
- if len(got.Body) != 0 {
- t.Fatalf("%s: 读回 %d 字节, 期望空", o.Name, len(got.Body))
- }
- }
- if err := db.Commit(); err != nil {
- t.Fatalf("提交: %v", err)
- }
- // Delete 后链页归还: 先插一条大 blob, 记空闲页数, 删行提交, 空闲页应回升。
- big := make([]byte, core.MaxBlobSize*2+11)
- for i := range big {
- big[i] = byte(i % 253)
- }
- obj := &blobOrmRow{Name: "to-delete", Body: big}
- if err := tbl.Insert(obj); err != nil {
- t.Fatalf("Insert: %v", err)
- }
- if err := db.Commit(); err != nil {
- t.Fatalf("提交: %v", err)
- }
- freeBefore := db.CoreDB().DebugFreePageCount()
- if err := tbl.Delete(obj.ID); err != nil {
- t.Fatalf("Delete: %v", err)
- }
- if err := db.Commit(); err != nil {
- t.Fatalf("提交: %v", err)
- }
- if _, err := tbl.Find(obj.ID); err == nil {
- t.Fatal("删除后行仍可读出")
- }
- freeAfter := db.CoreDB().DebugFreePageCount()
- if freeAfter <= freeBefore {
- t.Fatalf("删除带大 blob 的行后空闲页 %d 未回升 (删除前 %d): blob 链页泄漏",
- freeAfter, freeBefore)
- }
- t.Logf("空闲页: 删前=%d 删后=%d (归还 %d 页)", freeBefore, freeAfter, freeAfter-freeBefore)
- // 其余行不受牵连。
- got, err := tbl.Find(nul.ID)
- if err != nil {
- t.Fatalf("删除牵连 Find(nil-body): %v", err)
- }
- if len(got.Body) != 0 || got.Name != "nil-body" {
- t.Fatalf("删除牵连邻行: %+v", got)
- }
- // 重开后内容依旧 (链上字节没被误归还污染)。
- 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, "blobnd")
- if err != nil {
- t.Fatalf("重开建表: %v", err)
- }
- got2, err := tbl2.Find(empty.ID)
- if err != nil {
- t.Fatalf("重开 Find: %v", err)
- }
- if !bytes.Equal(got2.Body, []byte{}) || got2.Name != "empty-body" {
- t.Fatalf("重开后数据异常: %+v", got2)
- }
- }
|