package core import ( "encoding/binary" "testing" "unsafe" "git.ycl.pub/yl32940/TsDB2/DB/core/types" ) // TestRecoverWALRefreshesTableMeta 回归: Open 必须先做 WAL 恢复、再载入表元数据。 // // 背景 (wal_crash_chaos 压测实锤): 旧顺序 tablePage.Init → RecoverWAL 会让内存 // Table 对象持有【恢复前】的表信息页 (TotalRows/堆链指针/RowSize), 恢复重放的 // 新元数据只到磁盘 —— 内存与磁盘分叉: 行数统计错误, 堆链指针过期后后续写入 // 按过期结构分配 = 损坏。 func TestRecoverWALRefreshesTableMeta(t *testing.T) { // 建库: 一张表, 不提交任何行 (TotalRows=0) path := t.TempDir() + "/meta_refresh.db" db, err := Open(path) if err != nil { t.Fatalf("建库: %v", err) } db.SetAutoCommitDelay(0) tbl, err := db.CreateTable("t", []types.FieldInfo{ {FieldType: types.FieldType_Uint64, FieldName: []byte("id")}, {FieldType: types.FieldType_Uint64, FieldName: []byte("c")}, {FieldType: types.FieldType_String, FieldName: []byte("p")}, }) if err != nil { t.Fatalf("建表: %v", err) } if _, err := tbl.CreateIndex("pk", 2, []byte{0}); err != nil { t.Fatalf("建索引: %v", err) } tablePID := tbl.TablePageID() startOff := tbl.TableStartOffset() // 表信息结构在表页内的偏移 (页头之后) if err := db.Close(); err != nil { t.Fatalf("关闭: %v", err) } // 伪造一条已提交 WAL: 把表信息页的 TotalRows 改成 4242 后整页镜像入链 ff, err := OpenFile(path) if err != nil { t.Fatalf("裸开: %v", err) } walPID := nextFreePage(t, ff) pageBuf := make([]byte, PageSize) if err := ff.readPageDisk(tablePID, pageBuf); err != nil { t.Fatalf("读表页: %v", err) } // TotalRows 页内偏移 = 表信息结构起始偏移 + 结构内偏移 (与载入路径同口径) trOff := startOff + uint32(unsafe.Offsetof(TableInfo{}.TotalRows)) before := binary.LittleEndian.Uint64(pageBuf[trOff:]) binary.LittleEndian.PutUint64(pageBuf[trOff:], 4242) // 与真实写端一致: 整页镜像只存有效前缀 (表页 useSize 远小于页容量) useSize := binary.LittleEndian.Uint32(pageBuf[16:]) entry := encodeWALEntry(tablePID, WALOpFullPage, pageBuf[:useSize], 0) first := buildWALFirstPage(t, walPID, 0, 1, entry) publishWALChain(t, ff, first) _ = ff.Close() // 重开: 恢复必须先于表元数据载入, 引擎读到的 TotalRows 必须是恢复后的 4242 db2, err := Open(path) if err != nil { t.Fatalf("重开: %v", err) } defer db2.Close() tbl2, ok := db2.FindTable("t") if !ok { t.Fatal("表丢失") } if got := tbl2.TotalRows(); got != 4242 { t.Fatalf("恢复后的表元数据必须对引擎可见: TotalRows=%d, 想要 4242 (恢复前=%d)", got, before) } }