Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Migration types

Config::use_migrations(&[...]) registers an explicit, ordered list of boxed Migratable values. Three types are built in.

FileMigration

Runs up/down SQL loaded from files at runtime.

#![allow(unused)]
fn main() {
use migrant_lib::FileMigration;

fn run() -> Result<(), Box<dyn std::error::Error>> {
FileMigration::with_tag("create-users")
    .up("migrations/create_users/up.sql")?
    .down("migrations/create_users/down.sql")?
    .boxed();
Ok(())
}
}

The files must exist at runtime; relative paths resolve from the working directory.

EmbeddedMigration

Runs up/down SQL from strings compiled into the binary, so no files are needed at runtime. include_str! embeds a file’s contents.

#![allow(unused)]
fn main() {
use migrant_lib::EmbeddedMigration;

fn run() {
EmbeddedMigration::with_tag("create-places")
    .up(include_str!("../migrations/create_places/up.sql"))
    .down("drop table places;")
    .boxed();
}
}

FnMigration

Runs arbitrary Rust with the signature fn(ConnConfig) -> Result<(), Box<dyn std::error::Error>>. Use it for data migrations or anything SQL alone cannot express.

#![allow(unused)]
fn main() {
use migrant_lib::{FnMigration, ConnConfig};

fn seed(conn: ConnConfig) -> Result<(), Box<dyn std::error::Error>> {
    // open a connection from conn.connect_string()? / conn.sqlite_connection()?
    Ok(())
}

fn run() {
FnMigration::with_tag("seed-users")
    .up(seed)
    .down(migrant_lib::migration::noop)
    .boxed();
}
}

Repeatable migrations

Migratable::is_repeatable() marks a migration that re-runs whenever its up-SQL checksum changes, instead of applying exactly once. The default is false.

FileMigration and EmbeddedMigration declare it with the repeatable() builder method, or with a -- migrant:repeatable directive in the up-SQL (the form the CLI reads off disk). Either declares it.

#![allow(unused)]
fn main() {
use migrant_lib::EmbeddedMigration;

fn run() {
EmbeddedMigration::with_tag("seed-roles")
    .repeatable()
    .up("insert into roles (name) values ('admin') on conflict do nothing;")
    .boxed();
}
}

A repeatable migration must have up-SQL to hash and must not define a down direction; use_migrations rejects either with Error::Migration. FnMigration has no SQL to hash, so it cannot be repeatable.

Within a run they apply after every pending versioned migration, at most once each. Report::repeatable_tags() lists the ones a run re-ran, and MigrationStatus::repeatable()/stale() report the state of each. See Writing migrations for the full rules.

Transactions per migration

Migratable::use_transaction(direction) decides whether migrant wraps a migration in a transaction for that direction. The default is true.

  • FileMigration and EmbeddedMigration are wrapped by default. Opt out with no_transaction() (both directions) or a -- migrant:no-transaction directive in the SQL (per direction). A directive in the SQL takes precedence over the builder flag.
  • FnMigration runs arbitrary code and may open its own connections, so it is never wrapped.

See Transactions for the directive rules and backend behavior.

Tags and CLI compatibility

Tags must be unique and contain [a-z0-9-]. To interoperate with the migrant CLI (whose file migrations are timestamp-prefixed), call Config::use_cli_compatible_tags(true) before use_migrations/reload, which requires tags of the form [0-9]{14}_[a-z0-9-]+.