vacuum_identity_test.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  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. // TestVACUUMPreservesRowStoreIdentity 行存储索引必须按原始名字/类型重建:
  8. // 旧实现无条件建成 ("pk", Primary) —— ORM 按 pk_<表名> 寻址, VACUUM 后
  9. // Find/Delete/Update 全部 ErrFieldNotFound; 时序表被静默改成主键表。
  10. func TestVACUUMPreservesRowStoreIdentity(t *testing.T) {
  11. db := fixOpenDB(t)
  12. // 主键表: 行存储索引用 ORM 风格命名 pk_people
  13. tbl, err := db.CreateTable("people", []types.FieldInfo{
  14. {FieldType: types.FieldType_Uint64, FieldName: []byte("id")},
  15. {FieldType: types.FieldType_Int64, FieldName: []byte("age")},
  16. })
  17. if err != nil {
  18. t.Fatalf("建表: %v", err)
  19. }
  20. if _, err := tbl.CreateIndex("pk_people", page.IndexTypePrimary, []byte{0}); err != nil {
  21. t.Fatalf("建主键索引: %v", err)
  22. }
  23. for i := 1; i <= 50; i++ {
  24. if _, err := tbl.InsertRowWithID(uint64(i), []types.FieldValue{
  25. {FieldName: []byte("id"), Value: uint64(i)},
  26. {FieldName: []byte("age"), Value: int64(i * 2)},
  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. if _, err := db.VACUUM(); err != nil {
  35. t.Fatalf("VACUUM: %v", err)
  36. }
  37. // 行存储索引名字/类型必须原样保留
  38. ntbl, ok := db.FindTable("people")
  39. if !ok {
  40. t.Fatalf("VACUUM 后表丢失")
  41. }
  42. m, h, ok := ntbl.IndexMeta().rowStoreMeta()
  43. if !ok {
  44. t.Fatalf("VACUUM 后行存储索引丢失")
  45. }
  46. if m.Name() != "pk_people" {
  47. t.Fatalf("行存储索引名 = %q, 想要 pk_people", m.Name())
  48. }
  49. if m.IndexType != page.IndexTypePrimary {
  50. t.Fatalf("行存储索引类型 = %v, 想要 Primary", m.IndexType)
  51. }
  52. _ = h
  53. // 按原索引名寻址 (ORM 的路径) 必须可用
  54. row, found := ntbl.IndexMeta().SearchByIndex("pk_people", func() []byte {
  55. k := make([]byte, 8)
  56. types.EncodeValue(types.FieldType_Uint64, 0, uint64(7), k)
  57. return k
  58. }())
  59. if !found || len(row) < 8 {
  60. t.Fatalf("VACUUM 后按 pk_people 查不到 id=7")
  61. }
  62. if got := row[8:][1:][0]; got != 0 { // rowData[0]=schemaVer
  63. _ = got
  64. }
  65. // 数据完整性
  66. if c := vacCountOf(t, ntbl); c != 50 {
  67. t.Fatalf("VACUUM 后行数 = %d, 想要 50", c)
  68. }
  69. }
  70. // TestVACUUMPreservesNULL VACUUM 不得把 NULL 物化成零值: 快照保 NULL,
  71. // 重建后 NULL 位图仍在, IS-NULL 语义不变。
  72. func TestVACUUMPreservesNULL(t *testing.T) {
  73. db := fixOpenDB(t)
  74. tbl, err := db.CreateTable("t", []types.FieldInfo{
  75. {FieldType: types.FieldType_Uint64, FieldName: []byte("id")},
  76. {FieldType: types.FieldType_Int64, FieldName: []byte("v"), Flag: types.FlagNullable},
  77. })
  78. if err != nil {
  79. t.Fatalf("建表: %v", err)
  80. }
  81. if _, err := tbl.CreateIndex("pk", page.IndexTypePrimary, []byte{0}); err != nil {
  82. t.Fatalf("建主键索引: %v", err)
  83. }
  84. // 3 行: NULL / 有值 / NULL
  85. for i := 1; i <= 3; i++ {
  86. vals := []types.FieldValue{
  87. {FieldName: []byte("id"), Value: uint64(i)},
  88. }
  89. if i == 2 {
  90. vals = append(vals, types.FieldValue{FieldName: []byte("v"), Value: int64(99)})
  91. } else {
  92. vals = append(vals, types.FieldValue{FieldName: []byte("v"), Value: nil})
  93. }
  94. if _, err := tbl.InsertRowWithID(uint64(i), vals); err != nil {
  95. t.Fatalf("插入 %d: %v", i, err)
  96. }
  97. }
  98. if err := db.Commit(); err != nil {
  99. t.Fatalf("commit: %v", err)
  100. }
  101. if _, err := db.VACUUM(); err != nil {
  102. t.Fatalf("VACUUM: %v", err)
  103. }
  104. ntbl, _ := db.FindTable("t")
  105. // id=1 的 v 必须仍是 NULL (查不到值), id=2 是 99
  106. r1, _ := ntbl.FindByPK(1)
  107. r2, _ := ntbl.FindByPK(2)
  108. if hasColVal(r1, "v") {
  109. t.Fatalf("id=1 的 v 应为 NULL, 得到 %+v", r1)
  110. }
  111. if !hasColVal(r2, "v") {
  112. t.Fatalf("id=2 的 v 应为 99, 得到 %+v", r2)
  113. }
  114. if c := vacCountOf(t, ntbl); c != 3 {
  115. t.Fatalf("行数 = %d, 想要 3", c)
  116. }
  117. }
  118. // TestVACUUMDroppedColumnTable "删过列、且被索引列在被删列之后"的表,
  119. // VACUUM 必须成功 (FieldIDs 重映射), 且索引仍可用。
  120. func TestVACUUMDroppedColumnTable(t *testing.T) {
  121. db := fixOpenDB(t)
  122. tbl, err := db.CreateTable("t", []types.FieldInfo{
  123. {FieldType: types.FieldType_Uint64, FieldName: []byte("id")},
  124. {FieldType: types.FieldType_Int64, FieldName: []byte("dead")},
  125. {FieldType: types.FieldType_Int64, FieldName: []byte("age")},
  126. })
  127. if err != nil {
  128. t.Fatalf("建表: %v", err)
  129. }
  130. if _, err := tbl.CreateIndex("pk", page.IndexTypePrimary, []byte{0}); err != nil {
  131. t.Fatalf("建主键索引: %v", err)
  132. }
  133. if _, err := tbl.CreateIndex("idx_age", page.IndexTypeBTree, []byte{2}); err != nil {
  134. t.Fatalf("建 idx_age: %v", err)
  135. }
  136. for i := 1; i <= 30; i++ {
  137. if _, err := tbl.InsertRowWithID(uint64(i), []types.FieldValue{
  138. {FieldName: []byte("id"), Value: uint64(i)},
  139. {FieldName: []byte("dead"), Value: int64(i)},
  140. {FieldName: []byte("age"), Value: int64(i % 40)},
  141. }); err != nil {
  142. t.Fatalf("插入 %d: %v", i, err)
  143. }
  144. }
  145. if err := tbl.DropField("dead"); err != nil {
  146. t.Fatalf("DropField: %v", err)
  147. }
  148. if err := db.Commit(); err != nil {
  149. t.Fatalf("commit: %v", err)
  150. }
  151. if _, err := db.VACUUM(); err != nil {
  152. t.Fatalf("删过列的表 VACUUM 失败: %v", err)
  153. }
  154. ntbl, _ := db.FindTable("t")
  155. if c := vacCountOf(t, ntbl); c != 30 {
  156. t.Fatalf("VACUUM 后行数 = %d, 想要 30", c)
  157. }
  158. // 重建的 idx_age 必须仍按新下标工作: age=5 → id ∈ {5, 45} 中 45>30 即仅 id=5
  159. n, err := ntbl.Query().Where(Eq("age", int64(5), 0)).Count()
  160. if err != nil || n != 1 {
  161. t.Fatalf("VACUUM 后 idx_age 等值 count=%d err=%v, 想要 1", n, err)
  162. }
  163. }
  164. // TestVACUUMTimeSeriesKeepsTimeIndex 时序表 VACUUM 后仍是时序索引,
  165. // 时间范围查询正常。
  166. func TestVACUUMTimeSeriesKeepsTimeIndex(t *testing.T) {
  167. db := fixOpenDB(t)
  168. tbl, err := db.CreateTable("tick", []types.FieldInfo{
  169. {FieldType: types.FieldType_Uint64, FieldName: []byte("ts")},
  170. {FieldType: types.FieldType_Float64, FieldName: []byte("price")},
  171. })
  172. if err != nil {
  173. t.Fatalf("建表: %v", err)
  174. }
  175. if _, err := tbl.CreateIndex("ts_tick", page.IndexTypeTime, []byte{0}); err != nil {
  176. t.Fatalf("建时序索引: %v", err)
  177. }
  178. for i := 1; i <= 100; i++ {
  179. if _, err := tbl.InsertRowWithID(uint64(i), []types.FieldValue{
  180. {FieldName: []byte("ts"), Value: uint64(i * 10)},
  181. {FieldName: []byte("price"), Value: float64(i) + 0.5},
  182. }); err != nil {
  183. t.Fatalf("插入 %d: %v", i, err)
  184. }
  185. }
  186. if err := db.Commit(); err != nil {
  187. t.Fatalf("commit: %v", err)
  188. }
  189. if _, err := db.VACUUM(); err != nil {
  190. t.Fatalf("VACUUM: %v", err)
  191. }
  192. ntbl, _ := db.FindTable("tick")
  193. m, _, ok := ntbl.IndexMeta().rowStoreMeta()
  194. if !ok || m.IndexType != page.IndexTypeTime {
  195. t.Fatalf("VACUUM 后行存储索引类型 = %+v, 想要 Time", m)
  196. }
  197. if m.Name() != "ts_tick" {
  198. t.Fatalf("VACUUM 后时序索引名 = %q, 想要 ts_tick", m.Name())
  199. }
  200. // 时间范围查询
  201. n, err := ntbl.Query().Where(And(
  202. Ge("ts", uint64(300), 0), Le("ts", uint64(500), 0))).Count()
  203. if err != nil || n != 21 {
  204. t.Fatalf("时序范围 count=%d err=%v, 想要 21 (300..500 步进 10)", n, err)
  205. }
  206. }
  207. // vacCountOf 拿表行数 (测试辅助)。
  208. func vacCountOf(t *testing.T, tbl *Table) int64 {
  209. t.Helper()
  210. c, err := tbl.Query().Count()
  211. if err != nil {
  212. t.Fatalf("count: %v", err)
  213. }
  214. return c
  215. }
  216. // hasColVal 行字段值里是否有指定列 (NULL 列不在其中)。
  217. func hasColVal(vals []types.FieldValue, name string) bool {
  218. for _, fv := range vals {
  219. if string(fv.FieldName) == name {
  220. return true
  221. }
  222. }
  223. return false
  224. }