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&SqlitePooland returnResult<(), 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!forINSERT,UPDATE,DELETE, andSELECTreturning 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(())
}
);
}