fix(database): migrate versioned schemas before create_all

This commit is contained in:
jxxghp
2026-08-30 16:46:35 +08:00
parent b0895ad40f
commit 26366c65a6
2 changed files with 35 additions and 3 deletions

View File

@@ -90,7 +90,7 @@ def _validate_migration_lineage(
def prepare_database(*, before_alembic: Callable[[], None] | None = None) -> None:
"""在建表或迁移前完成版本校验及可选备份。"""
"""在建表或迁移前完成版本校验、可选备份与安全的结构升级。"""
engine = get_engine()
alembic_cfg = _build_alembic_config(engine)
has_existing_database, current_heads, target_heads = _migration_state(
@@ -101,6 +101,7 @@ def prepare_database(*, before_alembic: Callable[[], None] | None = None) -> Non
has_existing_database
and set(current_heads) != set(target_heads)
)
migrate_before_create = requires_migration and bool(current_heads)
if (
requires_migration
and get_runtime_setting('DB_BACKUP_ENABLE')
@@ -114,11 +115,17 @@ def prepare_database(*, before_alembic: Callable[[], None] | None = None) -> Non
)
build_database_governance().create_backup()
if migrate_before_create:
# 已标记的旧结构必须先沿 Alembic 链升级;当前元数据可能包含依赖新列的外键,
# 提前 create_all 会在 PostgreSQL 上因父表仍是旧结构而失败。
update_db(alembic_cfg)
init_db()
if before_alembic:
# 首次初始化需要先建立用户表,再把管理员密码交给 Alembic 基础迁移消费。
before_alembic()
update_db(alembic_cfg)
if not migrate_before_create:
update_db(alembic_cfg)
def verify_database_revision() -> None:

View File

@@ -101,15 +101,40 @@ def test_prepare_database_creates_backup_before_schema_changes(monkeypatch) -> N
assert calls == [
"backup",
"alembic",
"create_all",
"before_alembic",
"alembic",
]
assert logged_messages == [
"数据库需要从版本 old 升级到 head,正在创建迁移前备份"
]
def test_prepare_database_keeps_create_all_first_for_unversioned_database(
monkeypatch,
) -> None:
"""未标记旧库仍需先补齐基础表,再从 Alembic base 执行数据迁移。"""
calls: list[str] = []
monkeypatch.setattr(db_init, "get_engine", lambda: object())
monkeypatch.setattr(db_init, "_build_alembic_config", lambda _engine: object())
monkeypatch.setattr(
db_init,
"_migration_state",
lambda *_: (True, (), ("head",)),
)
monkeypatch.setattr(db_init.settings, "DB_BACKUP_ENABLE", False)
monkeypatch.setattr(db_init, "init_db", lambda: calls.append("create_all"))
monkeypatch.setattr(
db_init,
"update_db",
lambda _config: calls.append("alembic"),
)
db_init.prepare_database(before_alembic=lambda: calls.append("before_alembic"))
assert calls == ["create_all", "before_alembic", "alembic"]
@pytest.mark.parametrize(
("has_existing_database", "current_heads", "backup_enabled", "upgrade_enabled"),
(