Anarlog db-migrate 深度解析:基于 sqlx 语义的应用数据库迁移编排与 CloudSync 连接亲和性实现

Anarlog db-migrate 深度解析:基于 sqlx 语义的应用数据库迁移编排与 CloudSync 连接亲和性实现 Anarlog db-migrate 深度解析基于 sqlx 语义的应用数据库迁移编排与 CloudSync 连接亲和性实现【免费下载链接】anarlogOpen source Granola AI Alternative项目地址: https://gitcode.com/GitHub_Trending/hy/anarlog本指南以 crates/db-migrate/AGENTS.md 为核心结合 db-migrate 的源码、单元测试与端到端测试完整讲解 Anarlog 桌面应用数据库迁移模块的设计边界、执行语义与兼容性护栏。读完你将领会MigrationStep与MigrationScopePlainvsCloudsyncAlter的数据模型、sqlx 迁移语义的保留方式、-- breaking兼容性地板机制以及 CloudSync DDL 必须绑定单一连接的底层原因。模块定位sqlx 迁移行为的窄移植与一处刻意分歧db-migrate在 Anarlog 中的职责是应用数据库App-database迁移执行。它没有重新发明迁移框架而是对sqlx的迁移行为做了一次窄范围narrow移植并保留了一处有意分歧继承 sqlx 语义有序应用ordered apply、校验和验证checksum validation、脏版本检测dirty-version detection、幂等重跑idempotent re-run刻意分歧每个迁移步骤携带显式的MigrationScope作用域声明Plain或CloudsyncAlter用于在 CloudSync DDL 期间强制执行连接亲和性connection affinity——begin_alter→ DDL →commit_alter必须发生在同一条被检出checked-out的连接上不允许在连接池层面执行。从依赖关系看Cargo.toml 只声明了anlg-db-core与启用了runtime-tokio、sqlite、sqlite-unbundled、migrate特性的sqlx库体以#![forbid(unsafe_code)]强制无 unsafe 实现见 lib.rs。Role / Owns / Does Not Own职责边界的刻意收缩AGENTS.md 用三组清单严格划分了该模块的边界这直接决定了它的 API 形态类别内容Owns拥有迁移编排与_sqlx_migrations记账MigrationStep→sqlx::migrate::Migration翻译step id、重复版本、CloudSync 目标资格的校验Plain与CloudsyncAlter { table_name }的执行语义Does Not Own不拥有连接池创建与数据库打开CloudSync 扩展加载与网络设置应用表定义、行类型、查询 API、迁移 SQL 内容某步骤是否看起来像 CloudSync alter 的推断Invariants不变量CloudsyncAlter要求begin_alter→ DDL →commit_alter在同一检出连接上CloudSync 禁用时回退为普通 SQLite 执行保留 sqlx 语义迁移作用域必须在 manifest 中显式声明绝不从 SQL 文本推断最后一条不变量是理解整个模块的关键一个步骤是普通迁移还是 CloudSync 变更完全由 manifestMigrationStep.scope字段说了算任何基于 SQL 文本猜测行为的设计都被显式排除。核心数据模型三步一表src/schema.rs 定义了迁移模块的全部公共数据类型全部为static静态生命周期、Copy语义的轻量结构pub enum MigrationScope { Plain, CloudsyncAlter { table_name: static str }, } pub struct MigrationStep { pub id: static str, // VERSION_DESCRIPTION如 20260415010101_create_widgets pub scope: MigrationScope, pub sql: static str, } pub struct DbSchema { pub steps: static [MigrationStep], pub validate_cloudsync_table: fn(str) - bool, }MigrationScope::CloudsyncAlter { table_name }显式声明该步骤要修改的同步表名供执行期做连接亲和性处理DbSchema.validate_cloudsync_table是一个回调函数由上层db-app注入用于判定目标表是否为真正的 CloudSync 同步表从而把表资格的认知留给业务层db-migrate自身不做任何推断。上层通过migrate/migrate_with_progress两个入口触发执行lib.rspub async fn migrate(db: Db, schema: DbSchema) - Result(), MigrateError { migrate_with_progress(db, schema, |_| {}).await } pub async fn migrate_with_progress( db: Db, schema: DbSchema, on_progress: impl FnMut(MigrationProgress) Send, ) - Result(), MigrateErrorMigrationProgress { completed: usize, total: usize }用于向调用方回报进度其语义是只统计待执行的步骤——单元测试migration_progress_only_counts_pending_steps验证了当 3 个步骤中已有 1 个应用过时进度回调依次为(0/2)、(1/2)、(2/2)已应用的步骤不参与计数lib.rs。迁移执行流程解析、校验、锁定、记账真正的执行逻辑位于 src/migrate.rsrun_migrations按如下顺序推进1.resolve_migrationsmanifest 翻译与元数据校验将静态的DbSchema翻译为sqlx::migrate::Migration列表migrate.rs对每个步骤调用validate_stepCloudsyncAlter 目标表必须通过validate_cloudsync_table否则返回InvalidCloudsyncStep见 migrate.rs调用parse_step_id解析 step id要求形如VERSION_DESCRIPTION版本为正整数描述非空否则返回InvalidStepIdmigrate.rs检测重复版本号发现即返回DuplicateStepVersion同时给出两个冲突的 step id以sql.starts_with(-- no-transaction)决定该迁移是否禁用 sqlx 的默认事务包裹最终按版本号升序排序保证有序应用。2.run_direct锁定、记账表初始化与脏检测从anlg_db_core::Db的连接池acquire一条连接包成内部适配器DbMigrateConnection该适配器实现了sqlx::migrate::Migratetraitmigrate.rs依次lock()sqlx 迁移互斥、ensure_migrations_table(_sqlx_migrations)初始化记账表、创建_anlg_schema_compat兼容性表见下文dirty_version()检测脏版本发现即返回sqlx::migrate::MigrateError::Dirty(version)。3. 兼容性校验validate_applied_migrations与两处新版本数据库守卫validate_applied_migrationsmigrate.rs逐条检查已应用的迁移记录已知版本直接放行高于本构建已知最大版本的已应用迁移被容忍前提是兼容性地板允许见下节低于等于最大已知版本但本构建不认识的已应用迁移说明历史已发散返回VersionMissing。随后有两道针对数据库由更新版本应用创建的守卫兼容性地板检查read_min_supported_version()读出的min_supported_version若大于本构建max_known_version返回SchemaFromNewerApp数据库超前检查max_applied_version max_known_version且本构建缺少其中某个版本时返回DatabaseAhead { missing_version, max_applied_version }。这两类错误的渲染文案都包含created by a newer version of Anarlog桌面端启动对话框正是通过匹配该文案把失败归类为需要升级应用见 error.rs 的注释说明。4. 逐步骤应用校验和验证与幂等对待执行列表逐个处理migrate.rs已应用版本比对checksum不一致返回VersionMismatch(version)——这保证了已发布的迁移 SQL 不可被悄悄修改未应用版本执行apply并触发进度回调。兼容性地板-- breaking注释与_anlg_schema_compat这是该模块针对桌面应用长尾版本共存场景设计的核心机制AGENTS.md 未展开源码给出了完整语义migrate.rsis_breaking_step扫描 SQL 的前导注释块连续的空行或以--开头的行若其中存在一整行-- breaking则该步骤被标记为 breakingmigrate.rsbreaking 迁移意味着删列、改名等旧构建无法读取的模式变更应用它会抬高_anlg_schema_compat表中的min_supported_versionUPDATE ... SET min_supported_version MAX(min_supported_version, ?1)抬高动作发生在 DDL 落地之前源码注释给出了严谨的崩溃安全论证若先改 schema 再抬地板崩溃在中间会让旧构建打开一个已经不兼容的库反之先抬地板崩溃只会把旧构建挡在外面而库本身仍是兼容的。配套的单元测试完整覆盖了该机制older_build_tolerates_newer_additive_migrations旧构建打开带更新纯新增迁移的库被允许lib.rsbreaking_migration_blocks_older_builds不包含 breaking 迁移的旧构建返回SchemaFromNewerApplib.rsbreaking_floor_allows_builds_that_include_it包含 breaking 迁移的构建可继续打开且_anlg_schema_compat.min_supported_version被正确记为 20lib.rsnewer_database_rejects_missing_local_migration库版本超前且本地缺失中间版本时返回DatabaseAhead且缺失的表不会被创建lib.rsunknown_migration_below_newest_known_still_fails低于最新已知版本的未知已应用迁移触发VersionMissinglib.rs。CloudsyncAlter 执行语义连接亲和性与事务自包裹apply的 CloudSync 分支migrate.rs是整份文档 Invariants 的代码落脚点启用判定仅当db.cloudsync_enabled()为真且anlg_db_core::cloudsync_is_enabled_on(mut conn, table_name)检测到该表已初始化 CloudSync 时才走 alter 窗口否则直接回退为普通 SQLite 执行对应不变量CloudSync 禁用时回退事务自包裹alter 窗口绕过了 sqlx 的每迁移事务因为begin_alter/commit_alter必须包裹在 DDL 外层且必须绑定同一条连接因此除非迁移以-- no-transaction显式声明否则执行器自行发出BEGIN IMMEDIATE失败则ROLLBACK成功则COMMIT三步序列cloudsync_begin_alter_on(conn, table)→ 执行迁移 SQL 并手动向_sqlx_migrations插入记账行execution_time暂记为 -1→cloudsync_commit_alter_on(conn, table)migrate.rs最后再update_execution_time回填真实耗时纳秒连接来源begin_alter/commit_alter/is_enabled均由 db-core 的 cloudsync 模块 转调anlg_cloudsynccrate 的同名原语三者接受的 executor 正是迁移执行器手里那条PoolConnection——连接亲和性由此闭环。为什么 pool 级执行不可接受CloudSync 的 alter 窗口扩展加载、DDL 钩子、记账要求 begin 与 commit 作用于同一个 SQLite 连接上下文若分别从池中检出两条连接begin_alter建立的扩展状态在连接归还后即丢失commit_alter将无从配对这也是 AGENTS.md 将其列为第一不变量、并明确写出 Pool-level execution is not acceptable 的原因。e2e 测试对这套语义做了可观测验证tests/e2e.rscloudsync_alter_scope_falls_back_to_plain_when_cloudsync_is_disabledCloudSync 整体关闭时alter 步骤按普通 SQLite 执行成功cloudsync_alter_scope_falls_back_to_plain_for_an_uninitialized_table表未cloudsync_init时同样回退且cloudsync_is_enabled_on返回falsecloudsync_alter_scope_preserves_an_initialized_cloudsync_table表已初始化时 alter 正常执行同步状态得以保留failed_cloudsync_alter_leaves_no_partial_schema_and_can_be_retried故意让第二条 SQL 失败ALTER TABLE missing_table ...验证不留下半迁移 schemaapplied_versions仍只有基础版本、表列不变且修复后可安全重跑——这正是事务自包裹的价值。真实 manifest 实践db-app 的 APP_MIGRATION_STEPSdb-migrate是纯编排层真正的迁移清单由上层 crates/db-app/src/lib.rs 以APP_MIGRATION_STEPS常量提供每个步骤通过include_str!(../migrations/id.sql)在编译期内联 SQL 文件例如anlg_db_migrate::MigrationStep { id: 20260413020000_templates, scope: anlg_db_migrate::MigrationScope::Plain, sql: include_str!(../migrations/20260413020000_templates.sql), },该文件头部注释lib.rs给出了与-- breaking机制配套的迁移编写守则旧构建会继续打开携带新迁移的数据库因此默认所有迁移必须降级安全——只做增量变更新列可空或带 DEFAULT不得重命名或删除旧构建仍在读写的东西若某迁移无法降级安全必须在 SQL 前导注释块中写入-- breaking一行旧构建将拒绝打开并弹出明确的 update Anarlog 对话框而不是在无法理解的 schema 上行为异常。清单中同时存在Plain与CloudsyncAlter两类步骤后者显式标注同步表名构成 manifest 显式声明作用域的实际样例。错误模型与测试体系小结MigrateErrorerror.rs是一个围绕迁移场景设计的精简枚举InvalidStepId、DuplicateStepVersion、InvalidCloudsyncStep属于 manifest 静态错误可在迁移执行前被resolve_migrations拦下SchemaFromNewerApp、DatabaseAhead属于版本兼容性错误并刻意在文案中携带机器可匹配的标记串Sqlx/SqlxMigrate透传底层错误。整个模块的可靠性由两层测试共同背书单元测试lib.rs基于DbStorage::Memorycloudsync_enabled: false的内存库覆盖容忍新增迁移、拒绝超前库、breaking 地板、脏历史、幂等与进度计数端到端测试tests/e2e.rs以widgets表为夹具覆盖 checksum 变更拒绝、缺失版本拒绝、非法 manifest 拒绝、CloudSync 回退与失败重试其中 CloudSync 相关用例带有平台条件编译macOS / Linux gnu·musl / Windows 的 aarch64 与 x86_64说明 alter 窗口行为需要真实 CloudSync 扩展环境验证。使用与约束一览接入方式上层先通过anlg_db_core::Db::open参数见 db-core/lib.rs含storage、cloudsync_enabled、journal_mode_wal、foreign_keys、max_connections打开数据库再以DbSchema装配静态迁移清单调用db_migrate::migrate即可SQLite 前置条件CloudSync 启用且存储为本地文件时必须使用 WAL 日志模式否则Db::open直接返回WalRequireddb-core/lib.rs编写新迁移遵循默认降级安全、必要时显式-- breaking的守则step id 必须符合正整数_描述且全局唯一CloudSync 变更必须使用CloudsyncAlter作用域并标注真实同步表名禁止在 SQL 文本中暗示作用域只读定位本仓库为只读研究用途以上说明仅用于查看、理解与本地运行验证例如通过cargo test -p db-migrate执行 e2e.rs 中的用例不建议对仓库内容做修改。【免费下载链接】anarlogOpen source Granola AI Alternative项目地址: https://gitcode.com/GitHub_Trending/hy/anarlog创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考