migration

Macro migration 

Source
macro_rules! migration {
    ($(#[$attr:meta])* $id:literal, async fn $name:ident($pool:ident : &$pool_ty:ty) $body:block) => { ... };
    (@internal $(#[$attr:meta])* $id:literal, $name:ident, $pool:ident, $body:block) => { ... };
}
Expand description

Registers an async fn as a Cadmus runtime database migration.

The macro takes a stable string literal ID and an async fn definition. The function is registered into the global migration registry automatically before main runs, via ctor.

Migrations can be defined anywhere inside cadmus-core — co-locate them with the feature they belong to (e.g. library/migrations.rs).

Doc comments placed before the async fn are forwarded onto the generated function and the generated submodule, making them visible in rustdoc. The macro also appends the migration ID and a rerun SQL snippet automatically.

§Arguments

  • $id — Stable string literal identifying this migration in _cadmus_migrations. Never change after deployment.
  • The async fn — Must accept &SqlitePool and return Result<(), anyhow::Error>.

The generated inner module is named after $name, so $name must be unique across all migration! calls in the crate.

§Use Cases

Migrations are one-time operations that run once per deployment. They can:

  • Transform data — backfill metadata, normalize formats, compute derived values
  • Import legacy data — load from filesystem/config into the database
  • Cleanup — prune obsolete rows, remove temporary data, optimize storage
  • Any procedural setup — not limited to SQL; can call filesystem operations, external APIs, or other async code

Note: Schema updates (creating/altering tables) should be handled via .sql files in crates/core/migrations/, not runtime migrations. SQL migrations are applied by sqlx-cli or the migrate! macro before runtime migrations run.

§SQL Best Practices

When executing SQL in migrations, use SQLx’s typed query macros for compile-time verification:

  • Use sqlx::query! for INSERT, UPDATE, DELETE, and SELECT returning rows
  • Use sqlx::query_as! when mapping results into a named struct
  • Use sqlx::query_scalar! for single-column results
  • Pass &mut **tx (double deref) when executing against a transaction

§Example

mod my_migrations {
    use sqlx::SqlitePool;

    cadmus_core::migration!(
        /// Backfills metadata from legacy storage.
        "v1_backfill_metadata",
        async fn backfill_metadata(pool: &SqlitePool) {
            // sqlx::query!(...).execute(pool).await?;
            Ok(())
        }
    );
}