|
|
@@ -0,0 +1,141 @@
|
|
|
+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 后行应已删除")
|
|
|
+ }
|
|
|
+}
|