abort_test.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. package migrate
  2. import (
  3. "git.ycl.pub/yl32940/TsDB2/DB/core/page"
  4. "git.ycl.pub/yl32940/TsDB2/DB/core/types"
  5. "database/sql"
  6. "errors"
  7. "path/filepath"
  8. "testing"
  9. )
  10. // failSource 一个"扫描到一半就报错"的源。
  11. //
  12. // 前 failAt 行正常吐出来, 第 failAt+1 行直接返回错误 —— 用来把"扫描失败时目标端
  13. // 到底提交了什么"钉死。failAt < 0 表示永不失败 (当对照组: 正常路径必须照常提交)。
  14. type failSource struct {
  15. table string
  16. sc *TableSchema
  17. rows int
  18. failAt int
  19. }
  20. func (s *failSource) Kind() Kind { return KindSQLite } // 借 SQL 的字面量即可, 只用于日志/报告
  21. func (s *failSource) Identity() string { return "fail-source" }
  22. func (s *failSource) Close() error { return nil }
  23. func (s *failSource) Tables() ([]string, error) { return []string{s.table}, nil }
  24. func (s *failSource) Schema(string) (*TableSchema, error) { return s.sc.Clone(), nil }
  25. func (s *failSource) Scan(_ string, _ *TableSchema, fn func(Row) error) error {
  26. for i := 0; i < s.rows; i++ {
  27. if i == s.failAt {
  28. return errors.New("模拟扫描中途失败")
  29. }
  30. if err := fn(Row{
  31. PK: uint64(i + 1),
  32. HasPK: true,
  33. Values: []any{int64(i + 1), int64(i * 10)},
  34. }); err != nil {
  35. return err
  36. }
  37. }
  38. return nil
  39. }
  40. // TestScanFailureDoesNotCommitPartialRows 扫描失败时, 缓冲区里那批行必须被丢弃。
  41. //
  42. // 修复前 runTable 在出错路径上调的是 writer.Close() —— 而 SQL 写端的 Close 是
  43. // 【成功语义】: flush 剩余缓冲 + Commit。于是攒在 RowWriter 缓冲里、还没写出去的行被
  44. // 当成成功提交上去, 调用方却拿到一个 error、以为这张表没进去 —— 库里躺了半张表,
  45. // 而且这份"半截数据"下一次续迁还会撞主键。
  46. //
  47. // 修复后出错路径走 Discarder.Discard (Rollback), 只有真正提交过的批次留在库里。
  48. func TestScanFailureDoesNotCommitPartialRows(t *testing.T) {
  49. cases := []struct {
  50. name string
  51. rows int
  52. failAt int
  53. wantRows int
  54. }{
  55. {"扫到第 3 行失败", 5, 2, 0},
  56. {"扫到第 5 行失败", 5, 4, 0},
  57. {"对照组: 正常迁完", 5, -1, 5},
  58. }
  59. for _, c := range cases {
  60. t.Run(c.name, func(t *testing.T) {
  61. dir := t.TempDir()
  62. dstPath := filepath.Join(dir, "dst.sqlite")
  63. dst, err := OpenSQLite(dstPath, quiet())
  64. if err != nil {
  65. t.Fatal(err)
  66. }
  67. // 只留两列整数: 避开字符串宽度档位这些与本次验证无关的东西。
  68. sc := &TableSchema{
  69. Name: "t1",
  70. Columns: []Column{
  71. {Name: "id", Type: types.FieldType_Int64},
  72. {Name: "n", Type: types.FieldType_Int64},
  73. },
  74. PKColumn: "id",
  75. PKIndexType: page.IndexTypePrimary,
  76. }
  77. src := &failSource{table: "t1", sc: sc, rows: c.rows, failAt: c.failAt}
  78. // 批大小比行数大得多, 于是所有行都只会留在写入器的缓冲里 ——
  79. // 这正是"出错时 Close 会把它们提交上去"的那一批。
  80. o := quiet()
  81. _, runErr := Run(src, dst, o)
  82. if err := dst.Close(); err != nil {
  83. t.Fatal(err)
  84. }
  85. if c.failAt >= 0 && runErr == nil {
  86. t.Fatal("扫描失败必须把错误抛给调用方")
  87. }
  88. if c.failAt < 0 && runErr != nil {
  89. t.Fatalf("对照组不该失败: %v", runErr)
  90. }
  91. // 关键断言: 库里到底留了几行。
  92. db, err := sql.Open("sqlite", dstPath)
  93. if err != nil {
  94. t.Fatal(err)
  95. }
  96. defer db.Close()
  97. var n int
  98. if err := db.QueryRow(`SELECT COUNT(*) FROM t1`).Scan(&n); err != nil {
  99. t.Fatalf("查行数: %v", err)
  100. }
  101. if n != c.wantRows {
  102. t.Fatalf("目标表应当留下 %d 行, 实际 %d 行; Run 错误: %v", c.wantRows, n, runErr)
  103. }
  104. })
  105. }
  106. }
  107. // TestAbortWriterPrefersDiscard 盯住 runTable 出错路径的选择: 实现了 Discarder 就
  108. // 走 Discard, 没实现才退回 Close。VRTX 写端没实现它 (它的 Close 不提交), 所以两条
  109. // 路都必须留着 —— 少了哪一支, 要么 SQL 侧提交半截数据, 要么 VRTX 侧收尾漏掉。
  110. func TestAbortWriterPrefersDiscard(t *testing.T) {
  111. d := &discardProbe{}
  112. abortWriter(d)
  113. if !d.discarded {
  114. t.Fatal("实现了 Discarder 的写入器必须走 Discard")
  115. }
  116. if d.closed {
  117. t.Fatal("走了 Discard 就不该再走 Close")
  118. }
  119. c := &closeOnlyProbe{}
  120. abortWriter(c)
  121. if !c.closed {
  122. t.Fatal("没实现 Discarder 的写入器必须退回 Close")
  123. }
  124. }
  125. type discardProbe struct {
  126. discarded bool
  127. closed bool
  128. }
  129. func (p *discardProbe) Write([]Row) error { return nil }
  130. func (p *discardProbe) Close() error { p.closed = true; return nil }
  131. func (p *discardProbe) Discard() error { p.discarded = true; return nil }
  132. type closeOnlyProbe struct {
  133. closed bool
  134. }
  135. func (p *closeOnlyProbe) Write([]Row) error { return nil }
  136. func (p *closeOnlyProbe) Close() error { p.closed = true; return nil }