main.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. // mrepro — 迁移孤儿页复现工具。
  2. //
  3. // mrepro -src <sqlite 源库> [-workdir 目录] [-vacuum] [-keep]
  4. //
  5. // 流程: 把源库 cp 进工作目录 (绝不碰原件) → 官方 migrate.Run(sqlite→VRTX) →
  6. // (可选) VACUUM → orphancheck 取证 → 打印两套口径孤儿数。
  7. // 退出码: 真实孤儿页 > 0 时 exit 1 (供 CI/脚本判定复现)。
  8. package main
  9. import (
  10. "flag"
  11. "fmt"
  12. "io"
  13. "os"
  14. "path/filepath"
  15. "git.ycl.pub/yl32940/TsDB2/DB/core"
  16. "git.ycl.pub/yl32940/TsDB2/internal/orphancheck"
  17. "git.ycl.pub/yl32940/TsDB2/migrate"
  18. )
  19. func main() {
  20. src := flag.String("src", "", "sqlite 源库路径 (会先复制再迁, 不直接打开原件)")
  21. workdir := flag.String("workdir", "", "工作目录 (默认自动创建临时目录)")
  22. vac := flag.Bool("vacuum", false, "迁移后跑一次 VACUUM")
  23. keep := flag.Bool("keep", false, "保留工作目录 (默认结束后删除)")
  24. flag.Parse()
  25. if *src == "" {
  26. fmt.Fprintln(os.Stderr, "用法: mrepro -src <sqlite 源库> [-workdir 目录] [-vacuum] [-keep]")
  27. os.Exit(2)
  28. }
  29. wd := *workdir
  30. var err error
  31. if wd == "" {
  32. wd, err = os.MkdirTemp("", "mrepro-")
  33. if err != nil {
  34. fatal(err)
  35. }
  36. } else if err := os.MkdirAll(wd, 0o755); err != nil {
  37. fatal(err)
  38. }
  39. if !*keep {
  40. defer os.RemoveAll(wd)
  41. }
  42. fmt.Println("工作目录:", wd)
  43. // 1. 复制源库 —— 只迁副本
  44. srcCopy := filepath.Join(wd, "src.db")
  45. if err := copyFile(srcCopy, *src); err != nil {
  46. fatal(err)
  47. }
  48. // 2. 官方 migrate: sqlite → VRTX
  49. sqliteSrc, err := migrate.OpenSQLite(srcCopy, nil)
  50. if err != nil {
  51. fatal(fmt.Errorf("打开 sqlite 副本: %w", err))
  52. }
  53. outDB := filepath.Join(wd, "out.db")
  54. vrtxDst, err := migrate.OpenVRTX(outDB, nil)
  55. if err != nil {
  56. fatal(fmt.Errorf("打开 VRTX 目标: %w", err))
  57. }
  58. rep, runErr := migrate.Run(sqliteSrc, vrtxDst, migrate.DefaultOptions())
  59. _ = vrtxDst.Close()
  60. _ = sqliteSrc.Close()
  61. if runErr != nil {
  62. fmt.Fprintln(os.Stderr, "迁移报错 (继续取证):", runErr)
  63. }
  64. for _, tr := range rep.Tables {
  65. note := ""
  66. if tr.Error != "" {
  67. note = " ERROR: " + tr.Error
  68. }
  69. fmt.Printf(" 表 %-32s %8d 行 %s\n", tr.Table, tr.Rows, note)
  70. }
  71. // 3. (可选) VACUUM
  72. if *vac {
  73. db, err := core.Open(outDB)
  74. if err != nil {
  75. fatal(fmt.Errorf("打开迁移产物: %w", err))
  76. }
  77. vres, err := db.VACUUM()
  78. _ = db.Close()
  79. if err != nil {
  80. fatal(fmt.Errorf("VACUUM: %w", err))
  81. }
  82. fmt.Printf("VACUUM: %d → %d 字节 (省 %d), 复制 %d 行\n",
  83. vres.OldFileSize, vres.NewFileSize, vres.SavedBytes, vres.RowsCopied)
  84. }
  85. // 4. 取证
  86. R, err := orphancheck.Run(outDB, orphancheck.Options{EngineBlobScan: true, SamplesPerType: 3})
  87. if err != nil {
  88. fatal(err)
  89. }
  90. fmt.Printf("\n== 取证: %s\n", outDB)
  91. fmt.Printf("总页数=%d 空闲链=%d 结构可达=%d blob 链页=%d\n",
  92. R.TotalPages, R.FreeCount, R.ReachableNoBlob, R.BlobPages)
  93. fmt.Printf("analyzer 口径孤儿 = %d (按类型 %v)\n", len(R.AnalyzerOrphans), R.AnalyzerByType)
  94. fmt.Printf("blob 感知真实孤儿 = %d (按类型 %v)\n", len(R.Orphans), R.ByType)
  95. for _, n := range R.Notes {
  96. fmt.Println("备注:", n)
  97. }
  98. for _, tn := range orphancheck.SortedTypes(R.ByType) {
  99. for _, p := range R.Samples[tn] {
  100. fmt.Printf("-- 样例 页 %d 类型 %s UseSize=%d NextPage=%d\n", p.ID, tn, p.UseSize, p.Next)
  101. fmt.Print(orphancheck.SampleHex(p))
  102. }
  103. }
  104. if len(R.Orphans) > 0 {
  105. fmt.Printf("\n复现成功: %d 页真实孤儿。\n", len(R.Orphans))
  106. os.Exit(1)
  107. }
  108. extra := ""
  109. if d := len(R.AnalyzerOrphans) - len(R.Orphans); d > 0 {
  110. extra = fmt.Sprintf(" (analyzer 口径的 %d 页全部是活 blob 链页 — analyzer 误报)", d)
  111. }
  112. fmt.Printf("\n纯净: 真实孤儿页 = 0%s\n", extra)
  113. }
  114. func copyFile(dst, src string) error { //nolint:revive // 参数序与 io.Copy 对齐习惯
  115. in, err := os.Open(src)
  116. if err != nil {
  117. return err
  118. }
  119. defer in.Close()
  120. out, err := os.Create(dst)
  121. if err != nil {
  122. return err
  123. }
  124. if _, err := io.Copy(out, in); err != nil {
  125. _ = out.Close()
  126. return err
  127. }
  128. return out.Close()
  129. }
  130. func fatal(err error) {
  131. fmt.Fprintln(os.Stderr, "错误:", err)
  132. os.Exit(2)
  133. }