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

Using migrant_lib

migrant_lib embeds migration management in your own program. The CLI is a thin wrapper around it, so anything the CLI does can be done from Rust. Enable a backend feature (sqlite, postgres, mysql, or all):

[dependencies]
migrant_lib = { version = "1.0.0-rc.1", features = ["postgres"] }

The pieces

  • Settings describes the database connection, built with a typed builder or loaded from a Migrant.toml.
  • Config holds the settings, the set of migrations, and a live connection. It is cheap to clone; clones share the same connection.
  • Migrator applies migrations against a Config.

A minimal run

#![allow(unused)]
fn main() {
use migrant_lib::{Config, Migrator, Settings, EmbeddedMigration};

fn run() -> Result<(), Box<dyn std::error::Error>> {
let settings = Settings::configure_sqlite()
    .database_path("/abs/path/to/db.db")?
    .build()?;

let mut config = Config::with_settings(settings);
config.setup()?; // create the __migrant_migrations table

config.use_migrations(&[
    EmbeddedMigration::with_tag("create-users")
        .up("create table users (id integer primary key, name text);")
        .down("drop table users;")
        .boxed(),
])?;

// Apply everything. The migrator re-reads applied state from the database
// itself, so no manual reload is needed before applying. `apply` returns a
// `Report` of the tags it applied; an empty report means nothing was pending.
let report = Migrator::with_config(&config)
    .all(true)
    .apply()?;
println!("applied {} migration(s)", report.len());
Ok(())
}
}

Config::reload() re-reads the applied set from the database and returns a fresh Config. It is only needed when your code inspects applied state (for example via migration_statuses) after applying; Migrator::apply refreshes its own view.

Settings builders

  • Settings::configure_sqlite(): database_path(...), memory() for an in-memory database, migration_location(...).
  • Settings::configure_postgres(): database_name/user/password/host/port, ssl_cert_file(...), database_params(...).
  • Settings::configure_mysql(): the same name/user/password/host/port and database_params.

Or load from a file: Config::from_settings_file("Migrant.toml").

Relative paths resolve against the settings file’s directory when there is one. With Config::with_settings (no file on disk) there is nothing to resolve against: a relative migration_location falls back to the current directory, and a relative sqlite database_path is an error, so give an absolute path (or :memory:).

Inspecting status

migration_statuses(&config) returns every managed migration with an applied flag; pending_migrations(&config) returns just the un-applied tags, in the order they would run. Both read the config’s current applied set, so reload() first if you need it fresh.

The Migrator

#![allow(unused)]
fn main() {
Migrator::with_config(&config)
    .direction(migrant_lib::Direction::Up) // or Down
    .all(true)          // every remaining migration, not just the next
    .force(migrant_lib::ForceMode::Off)    // or AcceptFailures / SkipFailures
    .fake(false)        // record without running SQL
    .synchronized(true) // advisory lock for server databases (default)
    .show_output(true)
    .apply()?;
}

ForceMode controls failed-migration handling: Off (default) aborts the run, AcceptFailures continues and records the failed migration as applied, SkipFailures continues without recording it so the next run retries it.

synchronized controls the advisory lock; see Concurrency and locking. Transaction wrapping is per migration; see Migration types and Transactions.

In-memory SQLite

The path :memory: (via Settings::configure_sqlite().memory()) selects an in-memory database. Its entire state lives in one connection, which the Config keeps alive and shares with its clones, so migrations and later queries see the same database. A function migration reaches it with ConnConfig::sqlite_connection().

See the examples for complete programs.