| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- package core
- // 事务与 DDL 交互的回归测试。
- //
- // 背景 (txn_crash_chaos 压测实锤): 建表/建索引后首个事务回滚, 二次插入立刻
- // "load failed" —— DDL 未提交时, Rollback 按页丢弃缓存, 而 Table.info /
- // IndexMeta.metas 是独立于页缓存的 Go 堆副本, 仍指向事务中分配、已被回收
- // 的 B+ 树根页; ReloadInfos 读表页失败 (文件还没长到那里) 又静默跳过,
- // 内存元数据与磁盘真值永久分叉。
- // 修复: DDL 隐式提交 (无事务时) + 事务内 DDL 显式拒绝。
- import (
- "strings"
- "testing"
- "git.ycl.pub/yl32940/TsDB2/DB/core/page"
- "git.ycl.pub/yl32940/TsDB2/DB/core/types"
- )
- func txnDDLTable(t *testing.T, db *DB) *Table {
- t.Helper()
- tbl, err := db.CreateTable("t", []types.FieldInfo{
- {FieldType: types.FieldType_Uint64, FieldName: []byte("id")},
- {FieldType: types.FieldType_String, Flag: types.WidthCodeFor(32), FieldName: []byte("v")},
- })
- if err != nil {
- t.Fatalf("建表: %v", err)
- }
- if _, err := tbl.CreateIndex("pk", page.IndexTypePrimary, []byte{0}); err != nil {
- t.Fatalf("建索引: %v", err)
- }
- return tbl
- }
- // TestRollbackAfterDDLKeepsIndexUsable 回归: DDL (建表+建索引) 后首个事务回滚,
- // 索引元数据必须恢复到已提交的初始状态 (EntryPage=0), 后续插入重建树根, 不迷路。
- func TestRollbackAfterDDLKeepsIndexUsable(t *testing.T) {
- db := fixOpenDB(t)
- tbl := txnDDLTable(t, db)
- // 首个事务写入后回滚
- tx := db.BeginTransaction()
- for i := uint64(1); i <= 5; i++ {
- if _, err := tbl.InsertRowWithID(i, []types.FieldValue{
- {FieldName: []byte("id"), Value: i},
- {FieldName: []byte("v"), Value: "x"},
- }); err != nil {
- t.Fatalf("insert %d: %v", i, err)
- }
- }
- if err := tx.Rollback(); err != nil {
- t.Fatalf("rollback: %v", err)
- }
- // 回滚后索引元数据必须已是 committed 初值, 插入能正常重建
- meta, _, ok := tbl.indexMeta.rowStoreMeta()
- if !ok {
- t.Fatal("rowstore 丢失")
- }
- if meta.EntryPage != 0 {
- t.Fatalf("回滚后 EntryPage=%d, 想要 0 (指向已回收页 = 元数据分叉)", meta.EntryPage)
- }
- tx2 := db.BeginTransaction()
- if _, err := tbl.InsertRowWithID(1, []types.FieldValue{
- {FieldName: []byte("id"), Value: uint64(1)},
- {FieldName: []byte("v"), Value: "y"},
- }); err != nil {
- t.Fatalf("回滚后二次插入: %v", err)
- }
- if err := tx2.Commit(); err != nil {
- t.Fatalf("commit: %v", err)
- }
- if _, err := tbl.FindByPK(1); err != nil {
- t.Fatalf("提交后按主键查询: %v", err)
- }
- }
- // TestDDLInsideTransactionRejected 事务内 DDL 必须显式拒绝: 未提交的 DDL 随
- // 回滚消失后无法恢复内存元数据, 宁可拒绝也不让调用方踩进不可恢复的坑。
- func TestDDLInsideTransactionRejected(t *testing.T) {
- db := fixOpenDB(t)
- tbl := txnDDLTable(t, db)
- tx := db.BeginTransaction()
- defer func() { _ = tx.Rollback() }()
- if _, err := db.CreateTable("t2", []types.FieldInfo{
- {FieldType: types.FieldType_Uint64, FieldName: []byte("id")},
- }); err == nil || !strings.Contains(err.Error(), "DDL") {
- t.Fatalf("事务内 CreateTable 必须报 DDL 错误, 实际: %v", err)
- }
- if _, err := tbl.CreateIndex("idx_v", page.IndexTypeBTree, []byte{1}); err == nil || !strings.Contains(err.Error(), "DDL") {
- t.Fatalf("事务内 CreateIndex 必须报 DDL 错误, 实际: %v", err)
- }
- if err := tbl.AddField(types.FieldInfo{FieldType: types.FieldType_Uint64, FieldName: []byte("w")}); err == nil || !strings.Contains(err.Error(), "DDL") {
- t.Fatalf("事务内 AddField 必须报 DDL 错误, 实际: %v", err)
- }
- if err := tbl.DropField("v"); err == nil || !strings.Contains(err.Error(), "DDL") {
- t.Fatalf("事务内 DropField 必须报 DDL 错误, 实际: %v", err)
- }
- if err := db.DropTable("t"); err == nil || !strings.Contains(err.Error(), "DDL") {
- t.Fatalf("事务内 DropTable 必须报 DDL 错误, 实际: %v", err)
- }
- }
- // TestDDLImplicitCommit DDL (无事务时) 必须隐式提交: 建表/建索引后立即对后续
- // 事务的回滚可见 —— 回滚只应撤销事务内的数据修改, 不应波及已提交的 DDL。
- func TestDDLImplicitCommit(t *testing.T) {
- db := fixOpenDB(t)
- tbl := txnDDLTable(t, db)
- // DDL 之后不开事务直接杀 (模拟): 隐式提交意味着表与索引此刻已在盘上。
- // 用新句柄重开验证 (不重开则缓存可能掩盖落盘问题)。
- path := db.path
- if err := db.Close(); err != nil {
- t.Fatalf("close: %v", err)
- }
- db2, err := Open(path)
- if err != nil {
- t.Fatalf("重开: %v", err)
- }
- defer db2.Close()
- tbl2, ok := db2.FindTable("t")
- if !ok {
- t.Fatal("重开后表丢失 —— DDL 未隐式提交")
- }
- meta, _, ok := tbl2.indexMeta.rowStoreMeta()
- if !ok || meta == nil {
- t.Fatal("重走后索引元数据丢失 —— CreateIndex 未隐式提交")
- }
- _ = tbl
- }
|