audit_round3_test.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. package core
  2. import (
  3. "testing"
  4. "git.ycl.pub/yl32940/TsDB2/DB/core/page"
  5. "git.ycl.pub/yl32940/TsDB2/DB/core/types"
  6. )
  7. // TestTxnRollbackAfterEmptyingPage 事务内把一页删空再回滚:
  8. // 旧实现删空即直写盘还链, 回滚后树仍引用该页 —— 后续分配把它复用后,
  9. // 回滚"恢复"的行读到别人的页 (数据损坏)。延迟回收下回滚必须完整复活数据页。
  10. func TestTxnRollbackAfterEmptyingPage(t *testing.T) {
  11. db := fixOpenDB(t)
  12. tbl, err := db.CreateTable("t", []types.FieldInfo{
  13. {FieldType: types.FieldType_Uint64, FieldName: []byte("id")},
  14. {FieldType: types.FieldType_Int64, FieldName: []byte("v")},
  15. })
  16. if err != nil {
  17. t.Fatalf("建表: %v", err)
  18. }
  19. if _, err := tbl.CreateIndex("pk", page.IndexTypePrimary, []byte{0}); err != nil {
  20. t.Fatalf("建主键索引: %v", err)
  21. }
  22. // 100 行, 单页装得下
  23. for i := 1; i <= 100; i++ {
  24. if _, err := tbl.InsertRowWithID(uint64(i), []types.FieldValue{
  25. {FieldName: []byte("id"), Value: uint64(i)},
  26. {FieldName: []byte("v"), Value: int64(i)},
  27. }); err != nil {
  28. t.Fatalf("插入 %d: %v", i, err)
  29. }
  30. }
  31. if err := db.Commit(); err != nil {
  32. t.Fatalf("commit: %v", err)
  33. }
  34. // 事务内删空
  35. tx := db.BeginTransaction()
  36. for i := 1; i <= 100; i++ {
  37. if err := tbl.DeleteRowByPK(uint64(i)); err != nil {
  38. t.Fatalf("事务内删除 %d: %v", i, err)
  39. }
  40. }
  41. if err := tx.Rollback(); err != nil {
  42. t.Fatalf("rollback: %v", err)
  43. }
  44. // 回滚后 100 行必须完整回来
  45. if c := vacCountOf(t, tbl); c != 100 {
  46. t.Fatalf("回滚后行数 = %d, 想要 100 (删空页的直写盘还链会让回滚丢数据)", c)
  47. }
  48. row, found := tbl.IndexMeta().ResolveByPK(1)
  49. if !found || len(row) < 8 {
  50. t.Fatalf("回滚后 FindByPK(1) 失败 (树引用了被复用的页)")
  51. }
  52. // 继续写入: 若页被误还链, 这里会复用它并踩坏回滚的数据
  53. for i := 101; i <= 150; i++ {
  54. if _, err := tbl.InsertRowWithID(uint64(i), []types.FieldValue{
  55. {FieldName: []byte("id"), Value: uint64(i)},
  56. {FieldName: []byte("v"), Value: int64(i)},
  57. }); err != nil {
  58. t.Fatalf("插入 %d: %v", i, err)
  59. }
  60. }
  61. if err := db.Commit(); err != nil {
  62. t.Fatalf("commit: %v", err)
  63. }
  64. if c := vacCountOf(t, tbl); c != 150 {
  65. t.Fatalf("继续写入后行数 = %d, 想要 150", c)
  66. }
  67. if _, found := tbl.IndexMeta().ResolveByPK(1); !found {
  68. t.Fatalf("复用后 FindByPK(1) 丢失 (空闲页与活页双重分配)")
  69. }
  70. if _, found := tbl.IndexMeta().ResolveByPK(150); !found {
  71. t.Fatalf("FindByPK(150) 丢失")
  72. }
  73. }
  74. // TestTxnWorksAfterVacuum 回归: VACUUM 换引擎状态后, 事务的 Rollback 必须
  75. // 真正生效 (markTouched/allocated/pendingFree 闭包重绑到当前句柄)。
  76. func TestTxnWorksAfterVacuum(t *testing.T) {
  77. db := fixOpenDB(t)
  78. tbl, err := db.CreateTable("t", []types.FieldInfo{
  79. {FieldType: types.FieldType_Uint64, FieldName: []byte("id")},
  80. {FieldType: types.FieldType_Int64, FieldName: []byte("v")},
  81. })
  82. if err != nil {
  83. t.Fatalf("建表: %v", err)
  84. }
  85. if _, err := tbl.CreateIndex("pk", page.IndexTypePrimary, []byte{0}); err != nil {
  86. t.Fatalf("建主键索引: %v", err)
  87. }
  88. for i := 1; i <= 10; i++ {
  89. if _, err := tbl.InsertRowWithID(uint64(i), []types.FieldValue{
  90. {FieldName: []byte("id"), Value: uint64(i)},
  91. {FieldName: []byte("v"), Value: int64(i)},
  92. }); err != nil {
  93. t.Fatalf("插入 %d: %v", i, err)
  94. }
  95. }
  96. if err := db.Commit(); err != nil {
  97. t.Fatalf("commit: %v", err)
  98. }
  99. if _, err := db.VACUUM(); err != nil {
  100. t.Fatalf("VACUUM: %v", err)
  101. }
  102. // VACUUM 换了引擎状态, 须按当前句柄重新取表 (旧表对象的句柄/闭包属于旧状态)
  103. var ok bool
  104. tbl, ok = db.FindTable("t")
  105. if !ok {
  106. t.Fatalf("VACUUM 后表丢失")
  107. }
  108. // VACUUM 后: 事务插入再回滚 → 行必须不存在
  109. tx := db.BeginTransaction()
  110. if _, err := tbl.InsertRowWithID(999, []types.FieldValue{
  111. {FieldName: []byte("id"), Value: uint64(999)},
  112. {FieldName: []byte("v"), Value: int64(999)},
  113. }); err != nil {
  114. t.Fatalf("事务内插入: %v", err)
  115. }
  116. if err := tx.Rollback(); err != nil {
  117. t.Fatalf("rollback: %v", err)
  118. }
  119. if _, found := tbl.IndexMeta().ResolveByPK(999); found {
  120. t.Fatalf("VACUUM 后 Rollback 失效: 已回滚的行仍在 (闭包未重绑, markTouched 落空)")
  121. }
  122. if c := vacCountOf(t, tbl); c != 10 {
  123. t.Fatalf("回滚后行数 = %d, 想要 10", c)
  124. }
  125. // VACUUM 后: 事务插入再提交 → 行必须存在
  126. tx2 := db.BeginTransaction()
  127. if _, err := tbl.InsertRowWithID(1000, []types.FieldValue{
  128. {FieldName: []byte("id"), Value: uint64(1000)},
  129. {FieldName: []byte("v"), Value: int64(1000)},
  130. }); err != nil {
  131. t.Fatalf("事务内插入: %v", err)
  132. }
  133. if err := tx2.Commit(); err != nil {
  134. t.Fatalf("commit: %v", err)
  135. }
  136. if _, found := tbl.IndexMeta().ResolveByPK(1000); !found {
  137. t.Fatalf("VACUUM 后事务提交的行丢失")
  138. }
  139. }
  140. // TestInDuplicateValueAggCount IN 重复值不得让聚合重复计数:
  141. // 行交付路径有去重, raw 聚合路径 (Count/Sum) 依赖规划期区间去重。
  142. func TestInDuplicateValueAggCount(t *testing.T) {
  143. db := fixOpenDB(t)
  144. tbl, err := db.CreateTable("t", []types.FieldInfo{
  145. {FieldType: types.FieldType_Uint64, FieldName: []byte("id")},
  146. {FieldType: types.FieldType_Int64, FieldName: []byte("age")},
  147. })
  148. if err != nil {
  149. t.Fatalf("建表: %v", err)
  150. }
  151. if _, err := tbl.CreateIndex("pk", page.IndexTypePrimary, []byte{0}); err != nil {
  152. t.Fatalf("建主键索引: %v", err)
  153. }
  154. if _, err := tbl.CreateIndex("idx_age", page.IndexTypeBTree, []byte{1}); err != nil {
  155. t.Fatalf("建二级索引: %v", err)
  156. }
  157. for i := 1; i <= 100; i++ {
  158. if _, err := tbl.InsertRowWithID(uint64(i), []types.FieldValue{
  159. {FieldName: []byte("id"), Value: uint64(i)},
  160. {FieldName: []byte("age"), Value: int64(i % 50)},
  161. }); err != nil {
  162. t.Fatalf("插入 %d: %v", i, err)
  163. }
  164. }
  165. if err := db.Commit(); err != nil {
  166. t.Fatalf("commit: %v", err)
  167. }
  168. // age=7 → id ∈ {7,57} 两行; 重复写三遍仍是 2
  169. n, err := tbl.Query().Where(In("age", []interface{}{int64(7), int64(7), int64(7)}, 0)).Count()
  170. if err != nil || n != 2 {
  171. t.Fatalf("重复 IN 的 Count = %d (err=%v), 想要 2", n, err)
  172. }
  173. // 行交付路径同样不得重复
  174. got := collectAges(t, tbl.Query().Where(In("age", []interface{}{int64(7), int64(7)}, 0)))
  175. if len(got) != 2 {
  176. t.Fatalf("重复 IN 的行交付 = %d 行, 想要 2", len(got))
  177. }
  178. }
  179. // TestDropRowStoreComponentRejected 行存储索引的分量列不得删除:
  180. // 删了之后新行提不出键、不进任何索引, 插入"成功"但行静默丢失。
  181. func TestDropRowStoreComponentRejected(t *testing.T) {
  182. db := fixOpenDB(t)
  183. tbl, err := db.CreateTable("t", []types.FieldInfo{
  184. {FieldType: types.FieldType_Uint64, FieldName: []byte("id")},
  185. {FieldType: types.FieldType_Int64, FieldName: []byte("v")},
  186. })
  187. if err != nil {
  188. t.Fatalf("建表: %v", err)
  189. }
  190. if _, err := tbl.CreateIndex("pk", page.IndexTypePrimary, []byte{0}); err != nil {
  191. t.Fatalf("建主键索引: %v", err)
  192. }
  193. if err := tbl.DropField("id"); err == nil {
  194. t.Fatalf("删除行存储索引分量列必须报错, 却成功了")
  195. }
  196. // 普通列仍可删除
  197. if err := tbl.DropField("v"); err != nil {
  198. t.Fatalf("删除普通列不应报错: %v", err)
  199. }
  200. }