txn_no_hijack_test.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. package DB
  2. import (
  3. "path/filepath"
  4. "testing"
  5. )
  6. // TestExplicitTxnSurvivesObjDeleteAndBatchInsert 回归测试: 用户持有显式事务时,
  7. // 对象头部 Delete() (行号已知 → deleteByRowID) 与 Table.BatchInsert() 必须复用
  8. // 该事务, 而不是顶掉它、在操作中途替用户全局提交 —— 否则用户随后的 Rollback
  9. // 失去原子性:
  10. // - 事务内 Delete 的行在 Rollback 后必须"复活";
  11. // - 事务内 BatchInsert 的行在 Rollback 后必须全部消失。
  12. //
  13. // 旧实现 (修复前) 在这两处无条件 db.BeginTransaction() + Commit: 用户事务被
  14. // 顶掉且已全局提交, Rollback 回滚不到任何东西, 下面两个断言都会失败。
  15. func TestExplicitTxnSurvivesObjDeleteAndBatchInsert(t *testing.T) {
  16. db, err := Open(filepath.Join(t.TempDir(), "txn_hijack.db"))
  17. if err != nil {
  18. t.Fatalf("打开: %v", err)
  19. }
  20. defer func() { _ = db.Close() }()
  21. db.SetAutoCommitDelay(0)
  22. tbl, err := UseTable[biRow](db, "txnhi")
  23. if err != nil {
  24. t.Fatalf("建表: %v", err)
  25. }
  26. // 预置 4 行并落盘
  27. for i := 0; i < 4; i++ {
  28. if err := tbl.Insert(biMake(i)); err != nil {
  29. t.Fatalf("预置 %d: %v", i, err)
  30. }
  31. }
  32. if err := db.Commit(); err != nil {
  33. t.Fatalf("提交: %v", err)
  34. }
  35. // 用户开显式事务, 在事务内做 Delete + BatchInsert
  36. tx := db.BeginTransaction()
  37. // victim 经 Find 读出, 行号已知 → obj.Delete() 走 deleteByRowID 自动事务路径
  38. victim, err := tbl.Find(2)
  39. if err != nil {
  40. t.Fatalf("Find(2): %v", err)
  41. }
  42. if err := victim.Delete(); err != nil {
  43. t.Fatalf("事务内 Delete: %v", err)
  44. }
  45. added := make([]*biRow, 10)
  46. for i := range added {
  47. added[i] = biMake(100 + i)
  48. }
  49. if err := tbl.BatchInsert(added); err != nil {
  50. t.Fatalf("事务内 BatchInsert: %v", err)
  51. }
  52. // 中途数据必须在事务内可见 (证明确实写进去了, 而不是被悄悄吞掉)
  53. if _, err := tbl.Find(2); err == nil {
  54. t.Fatal("事务内行 2 应已被删除")
  55. }
  56. // 回滚: 删除的行复活, 批插的行全部消失
  57. if err := tx.Rollback(); err != nil {
  58. t.Fatalf("Rollback: %v", err)
  59. }
  60. if _, err := tbl.Find(2); err != nil {
  61. t.Fatalf("Rollback 后行 2 应复活: %v", err)
  62. }
  63. for _, o := range added {
  64. if _, err := tbl.Find(o.ID); err == nil {
  65. t.Fatalf("Rollback 后批插行 %d 不应存在", o.ID)
  66. }
  67. }
  68. coreTbl, ok := db.CoreDB().FindTable("txnhi")
  69. if !ok {
  70. t.Fatal("core 找不到表 txnhi")
  71. }
  72. if got := coreTbl.TotalRows(); got != 4 {
  73. t.Fatalf("Rollback 后行数: 期望 4, 实际 %d", got)
  74. }
  75. // 反向对照: 同样操作走 Commit 必须生效 (证明复用用户事务时提交链没断)
  76. tx2 := db.BeginTransaction()
  77. victim2, err := tbl.Find(1)
  78. if err != nil {
  79. t.Fatalf("对照 Find(1): %v", err)
  80. }
  81. if err := victim2.Delete(); err != nil {
  82. t.Fatalf("对照 Delete: %v", err)
  83. }
  84. kept := make([]*biRow, 5)
  85. for i := range kept {
  86. kept[i] = biMake(200 + i)
  87. }
  88. if err := tbl.BatchInsert(kept); err != nil {
  89. t.Fatalf("对照 BatchInsert: %v", err)
  90. }
  91. if err := tx2.Commit(); err != nil {
  92. t.Fatalf("对照 Commit: %v", err)
  93. }
  94. if _, err := tbl.Find(1); err == nil {
  95. t.Fatal("Commit 后行 1 应已删除")
  96. }
  97. for _, o := range kept {
  98. if _, err := tbl.Find(o.ID); err != nil {
  99. t.Fatalf("Commit 后批插行 %d 应存在: %v", o.ID, err)
  100. }
  101. }
  102. if got := coreTbl.TotalRows(); got != 8 {
  103. t.Fatalf("Commit 后行数: 期望 8, 实际 %d", got)
  104. }
  105. }
  106. // TestAutoTxnDeleteStillCommits 回归对照: 没有用户事务时, deleteByRowID 的自开自提
  107. // 必须保持原语义 —— Delete 立即生效 (不依赖后续的 db.Commit)。
  108. func TestAutoTxnDeleteStillCommits(t *testing.T) {
  109. db, err := Open(filepath.Join(t.TempDir(), "txn_auto_del.db"))
  110. if err != nil {
  111. t.Fatalf("打开: %v", err)
  112. }
  113. defer func() { _ = db.Close() }()
  114. db.SetAutoCommitDelay(0)
  115. tbl, err := UseTable[biRow](db, "txnad")
  116. if err != nil {
  117. t.Fatalf("建表: %v", err)
  118. }
  119. obj := &biRow{Name: "auto", Age: 30, Dept: "d", Score: 7}
  120. if err := tbl.Insert(obj); err != nil {
  121. t.Fatalf("Insert: %v", err)
  122. }
  123. if err := obj.Delete(); err != nil { // 无活跃事务 → 自开自提
  124. t.Fatalf("Delete: %v", err)
  125. }
  126. if _, err := tbl.Find(obj.ID); err == nil {
  127. t.Fatal("Delete 后行应已删除")
  128. }
  129. }