| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141 |
- package DB
- import (
- "path/filepath"
- "testing"
- )
- // TestExplicitTxnSurvivesObjDeleteAndBatchInsert 回归测试: 用户持有显式事务时,
- // 对象头部 Delete() (行号已知 → deleteByRowID) 与 Table.BatchInsert() 必须复用
- // 该事务, 而不是顶掉它、在操作中途替用户全局提交 —— 否则用户随后的 Rollback
- // 失去原子性:
- // - 事务内 Delete 的行在 Rollback 后必须"复活";
- // - 事务内 BatchInsert 的行在 Rollback 后必须全部消失。
- //
- // 旧实现 (修复前) 在这两处无条件 db.BeginTransaction() + Commit: 用户事务被
- // 顶掉且已全局提交, Rollback 回滚不到任何东西, 下面两个断言都会失败。
- func TestExplicitTxnSurvivesObjDeleteAndBatchInsert(t *testing.T) {
- db, err := Open(filepath.Join(t.TempDir(), "txn_hijack.db"))
- if err != nil {
- t.Fatalf("打开: %v", err)
- }
- defer func() { _ = db.Close() }()
- db.SetAutoCommitDelay(0)
- tbl, err := UseTable[biRow](db, "txnhi")
- if err != nil {
- t.Fatalf("建表: %v", err)
- }
- // 预置 4 行并落盘
- for i := 0; i < 4; i++ {
- if err := tbl.Insert(biMake(i)); err != nil {
- t.Fatalf("预置 %d: %v", i, err)
- }
- }
- if err := db.Commit(); err != nil {
- t.Fatalf("提交: %v", err)
- }
- // 用户开显式事务, 在事务内做 Delete + BatchInsert
- tx := db.BeginTransaction()
- // victim 经 Find 读出, 行号已知 → obj.Delete() 走 deleteByRowID 自动事务路径
- victim, err := tbl.Find(2)
- if err != nil {
- t.Fatalf("Find(2): %v", err)
- }
- if err := victim.Delete(); err != nil {
- t.Fatalf("事务内 Delete: %v", err)
- }
- added := make([]*biRow, 10)
- for i := range added {
- added[i] = biMake(100 + i)
- }
- if err := tbl.BatchInsert(added); err != nil {
- t.Fatalf("事务内 BatchInsert: %v", err)
- }
- // 中途数据必须在事务内可见 (证明确实写进去了, 而不是被悄悄吞掉)
- if _, err := tbl.Find(2); err == nil {
- t.Fatal("事务内行 2 应已被删除")
- }
- // 回滚: 删除的行复活, 批插的行全部消失
- if err := tx.Rollback(); err != nil {
- t.Fatalf("Rollback: %v", err)
- }
- if _, err := tbl.Find(2); err != nil {
- t.Fatalf("Rollback 后行 2 应复活: %v", err)
- }
- for _, o := range added {
- if _, err := tbl.Find(o.ID); err == nil {
- t.Fatalf("Rollback 后批插行 %d 不应存在", o.ID)
- }
- }
- coreTbl, ok := db.CoreDB().FindTable("txnhi")
- if !ok {
- t.Fatal("core 找不到表 txnhi")
- }
- if got := coreTbl.TotalRows(); got != 4 {
- t.Fatalf("Rollback 后行数: 期望 4, 实际 %d", got)
- }
- // 反向对照: 同样操作走 Commit 必须生效 (证明复用用户事务时提交链没断)
- tx2 := db.BeginTransaction()
- victim2, err := tbl.Find(1)
- if err != nil {
- t.Fatalf("对照 Find(1): %v", err)
- }
- if err := victim2.Delete(); err != nil {
- t.Fatalf("对照 Delete: %v", err)
- }
- kept := make([]*biRow, 5)
- for i := range kept {
- kept[i] = biMake(200 + i)
- }
- if err := tbl.BatchInsert(kept); err != nil {
- t.Fatalf("对照 BatchInsert: %v", err)
- }
- if err := tx2.Commit(); err != nil {
- t.Fatalf("对照 Commit: %v", err)
- }
- if _, err := tbl.Find(1); err == nil {
- t.Fatal("Commit 后行 1 应已删除")
- }
- for _, o := range kept {
- if _, err := tbl.Find(o.ID); err != nil {
- t.Fatalf("Commit 后批插行 %d 应存在: %v", o.ID, err)
- }
- }
- if got := coreTbl.TotalRows(); got != 8 {
- t.Fatalf("Commit 后行数: 期望 8, 实际 %d", got)
- }
- }
- // TestAutoTxnDeleteStillCommits 回归对照: 没有用户事务时, deleteByRowID 的自开自提
- // 必须保持原语义 —— Delete 立即生效 (不依赖后续的 db.Commit)。
- func TestAutoTxnDeleteStillCommits(t *testing.T) {
- db, err := Open(filepath.Join(t.TempDir(), "txn_auto_del.db"))
- if err != nil {
- t.Fatalf("打开: %v", err)
- }
- defer func() { _ = db.Close() }()
- db.SetAutoCommitDelay(0)
- tbl, err := UseTable[biRow](db, "txnad")
- if err != nil {
- t.Fatalf("建表: %v", err)
- }
- obj := &biRow{Name: "auto", Age: 30, Dept: "d", Score: 7}
- if err := tbl.Insert(obj); err != nil {
- t.Fatalf("Insert: %v", err)
- }
- if err := obj.Delete(); err != nil { // 无活跃事务 → 自开自提
- t.Fatalf("Delete: %v", err)
- }
- if _, err := tbl.Find(obj.ID); err == nil {
- t.Fatal("Delete 后行应已删除")
- }
- }
|