feat(database-migrations): implement database migration manager and related components

- Add DatabaseMigrationManager for orchestrating database migrations, including planning and executing migration steps.
- Introduce models for migration state, execution context, and migration steps.
- Implement MigrationPlanner to generate migration plans based on current and target versions.
- Create MigrationRegistry for registering and managing migration steps.
- Develop SchemaVersionResolver to determine the current database schema version.
- Add SQLiteSchemaInspector for inspecting SQLite database structures.
- Implement progress reporting tools using rich for visualizing migration progress.
- Introduce SQLiteUserVersionStore for managing schema version storage in SQLite.
This commit is contained in:
DrSmoothl
2026-03-31 09:16:25 +08:00
parent ea4cea39f2
commit c2c992ff01
15 changed files with 4025 additions and 4 deletions

View File

@@ -0,0 +1,171 @@
"""数据库迁移启动桥接层。"""
from typing import Optional
from sqlalchemy.engine import Engine
from src.common.logger import get_logger
from .builtin import (
LATEST_SCHEMA_VERSION,
build_default_migration_registry,
build_default_schema_version_resolver,
)
from .exceptions import DatabaseMigrationExecutionError
from .manager import DatabaseMigrationManager
from .models import DatabaseMigrationState, MigrationPlan, ResolvedSchemaVersion, SchemaVersionSource
from .registry import MigrationRegistry
from .resolver import SchemaVersionResolver
from .version_store import SQLiteUserVersionStore
logger = get_logger("database_migration")
class DatabaseMigrationBootstrapper:
"""数据库迁移启动桥接器。
该桥接器负责把数据库迁移基础设施接入现有启动流程,同时保持如下约束:
1. 若数据库为空,则直接交给当前模型定义建出最新结构;
2. 若数据库版本高于当前代码支持的最新版本,则立即终止启动;
3. 若存在待执行迁移步骤,则在正常建表流程之前先执行迁移;
4. 若数据库已是最新结构但尚未写入 ``user_version``,则在建表后补写版本号。
"""
def __init__(
self,
manager: DatabaseMigrationManager,
latest_schema_version: int = LATEST_SCHEMA_VERSION,
) -> None:
"""初始化数据库迁移启动桥接器。
Args:
manager: 数据库迁移编排器。
latest_schema_version: 当前代码支持的最新 schema 版本号。
"""
self.manager = manager
self.latest_schema_version = latest_schema_version
def prepare_database(self) -> DatabaseMigrationState:
"""为数据库初始化阶段准备迁移状态。
Returns:
DatabaseMigrationState: 迁移准备完成后的数据库状态。
Raises:
DatabaseMigrationExecutionError: 当数据库版本高于当前代码支持版本时抛出。
"""
with self.manager.engine.connect() as connection:
resolved_version = self.manager.resolver.resolve(connection)
if resolved_version.version > self.latest_schema_version:
raise DatabaseMigrationExecutionError(
"当前数据库版本高于代码内注册的最新迁移版本,已拒绝继续启动。"
f" 数据库版本={resolved_version.version},代码支持版本={self.latest_schema_version}"
)
if resolved_version.source == SchemaVersionSource.EMPTY_DATABASE:
logger.info(
"检测到空数据库,将直接根据当前模型创建最新结构。"
f" 目标版本={self.latest_schema_version}"
)
return self._build_noop_state(
current_version=resolved_version.version,
target_version=self.latest_schema_version,
resolved_state=resolved_version,
)
migration_state = self.manager.describe_state(target_version=self.latest_schema_version)
if not migration_state.requires_migration():
logger.info(
f"数据库 schema 已是目标版本,无需迁移。当前版本={migration_state.resolved_version.version}"
)
return migration_state
logger.info(
"检测到数据库需要迁移,"
f" 当前版本={migration_state.resolved_version.version},目标版本={migration_state.target_version}"
)
self.manager.migrate(target_version=self.latest_schema_version)
return self.manager.describe_state(target_version=self.latest_schema_version)
def finalize_database(self, migration_state: DatabaseMigrationState) -> None:
"""在数据库初始化末尾补写最终 schema 版本号。
该方法主要负责两类场景:
1. 空库首次建表完成后,将 ``user_version`` 写入为最新版本;
2. 已是最新结构但此前未写入 ``user_version`` 的数据库,补写版本号。
Args:
migration_state: 初始化前解析得到的迁移状态。
"""
if migration_state.requires_migration():
return
if migration_state.target_version <= 0:
return
if migration_state.resolved_version.source == SchemaVersionSource.PRAGMA:
return
with self.manager.engine.begin() as connection:
self.manager.version_store.write_version(connection, migration_state.target_version)
logger.info(
"数据库 schema 版本写入完成。"
f" 来源={migration_state.resolved_version.source.value}"
f" 写入版本={migration_state.target_version}"
)
def _build_noop_state(
self,
current_version: int,
target_version: int,
resolved_state: ResolvedSchemaVersion,
) -> DatabaseMigrationState:
"""构建无迁移动作的数据库状态对象。
Args:
current_version: 当前数据库版本号。
target_version: 当前初始化流程期望达到的目标版本号。
resolved_state: 已解析的数据库版本状态。
Returns:
DatabaseMigrationState: 不包含迁移步骤的状态对象。
"""
return DatabaseMigrationState(
resolved_version=resolved_state,
target_version=target_version,
plan=MigrationPlan(current_version=current_version, target_version=target_version, steps=[]),
)
def create_database_migration_bootstrapper(
engine: Engine,
registry: Optional[MigrationRegistry] = None,
resolver: Optional[SchemaVersionResolver] = None,
version_store: Optional[SQLiteUserVersionStore] = None,
latest_schema_version: int = LATEST_SCHEMA_VERSION,
) -> DatabaseMigrationBootstrapper:
"""创建数据库迁移启动桥接器。
Args:
engine: 目标数据库引擎。
registry: 迁移步骤注册表;未提供时使用默认注册表。
resolver: 数据库版本解析器;未提供时使用默认解析器。
version_store: 版本存储器;未提供时使用默认存储器。
latest_schema_version: 当前代码支持的最新 schema 版本号。
Returns:
DatabaseMigrationBootstrapper: 配置完成的数据库迁移启动桥接器。
"""
migration_registry = registry or build_default_migration_registry()
migration_resolver = resolver or build_default_schema_version_resolver()
migration_version_store = version_store or SQLiteUserVersionStore()
migration_manager = DatabaseMigrationManager(
engine=engine,
registry=migration_registry,
resolver=migration_resolver,
version_store=migration_version_store,
)
return DatabaseMigrationBootstrapper(
manager=migration_manager,
latest_schema_version=latest_schema_version,
)