Refuse rollback after migration ledger identity drift
Why: An already-open Store could otherwise execute a numbered down migration after its ledger name changed. Evidence: The targeted rollback test failed before the guard and passed after it; focused SQLite and isolated PostgreSQL store/migrator checks passed. Impact: MigrationStatus and MigrateDown fail closed on version-name mismatch before changing schema. Refs: feature/rdm-two-trees-latest-templates Backend combination probe
This commit is contained in:
@@ -1381,7 +1381,7 @@ func (s *Store) execInTx(stmts []string) error {
|
||||
|
||||
// MigrationStatus 返回已应用版本列表(诊断/验收用)。
|
||||
func (s *Store) MigrationStatus() ([]int, error) {
|
||||
rows, err := s.db.Query(`SELECT version FROM schema_migrations ORDER BY version`)
|
||||
rows, err := s.db.Query(`SELECT version,name FROM schema_migrations ORDER BY version`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1389,9 +1389,14 @@ func (s *Store) MigrationStatus() ([]int, error) {
|
||||
out := []int{}
|
||||
for rows.Next() {
|
||||
var v int
|
||||
if err := rows.Scan(&v); err != nil {
|
||||
var name string
|
||||
if err := rows.Scan(&v, &name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expected, known := MigrationName(v)
|
||||
if !known || expected != name {
|
||||
return nil, fmt.Errorf("migrate: migration identity mismatch at v%d; refuse to roll back another schema", v)
|
||||
}
|
||||
out = append(out, v)
|
||||
}
|
||||
return out, rows.Err()
|
||||
|
||||
@@ -105,6 +105,22 @@ func TestMigrateRejectsLedgerNameMismatch(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigrateDownRejectsLedgerNameDriftBeforeChangingSchema(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "down-name-drift.db")
|
||||
st := openSQLiteAt(t, path)
|
||||
if _, err := st.db.Exec(`UPDATE schema_migrations SET name='wrong-candidate' WHERE version=40`); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err := st.MigrateDown(39)
|
||||
if err == nil || !strings.Contains(err.Error(), "migration identity mismatch") {
|
||||
t.Fatalf("rollback accepted a changed migration identity: %v", err)
|
||||
}
|
||||
var remain int
|
||||
if err := st.db.QueryRow(`SELECT COUNT(*) FROM schema_migrations WHERE version=40`).Scan(&remain); err != nil || remain != 1 {
|
||||
t.Fatalf("rollback changed ledger before refusing: remaining=%d err=%v", remain, err)
|
||||
}
|
||||
}
|
||||
|
||||
// DDL 与账本同事务:在「DDL 已执行、版本未登记」处注入故障 → 全部回滚,
|
||||
// 不留下 v6 的表,也不留下 v6 的账本行(历史两步实现的中间态缺口)。
|
||||
func TestMigrateAtomicDDLAndLedger(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user