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
Settingsdescribes the database connection, built with a typed builder or loaded from aMigrant.toml.Configholds the settings, the set of migrations, and a live connection. It is cheap to clone; clones share the same connection.Migratorapplies migrations against aConfig.
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 anddatabase_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.