#spring #migration #database-migrations #framework #sql #di

springtime-migrate-refinery

SQL migration framework based on dependency injection

2 releases

0.2.1 Nov 27, 2023
0.2.0 May 8, 2023

#2299 in Database interfaces

MIT license

165KB
3K SLoC

Springtime Migrate Refinery

crates.io version build status Maintenance

refinery is powerful SQL migration toolkit for Rust, which makes creating migrations easy. This crate integrates refinery with the broader Springtime Framework allowing for providing database clients and migrations via dependency injection, which further eases creating and applying migrations, either from files or Rust code.

Note: in addition to this crate, you need to also import springtime-di.

Features

  • Automatic migration discovery
  • File-based and code-based migrations
  • Automatic migration application on startup for configured db clients
  • All refinery db clients supported

Basic usage

As with refinery, the basic usage consists of creating or embedding migrations and providing a runner for desired database.

The following example assumes familiarity with springtime and springtime-di.

use refinery_core::Runner;
use springtime::application;
use springtime::future::{BoxFuture, FutureExt};
use springtime_di::instance_provider::ErrorPtr;
use springtime_migrate_refinery::migration::embed_migrations;
use springtime_migrate_refinery::runner::MigrationRunnerExecutor;

// this is all that's needed to embed sql migrations from the given folder (the default path is
// "migrations")
embed_migrations!("examples/migrations");

// this is a migration source, which can provide migrations from code, instead of sql files
#[derive(Component)]
struct ExampleMigrationSource;

// register the source with dependency injection
#[component_alias]
impl MigrationSource for ExampleMigrationSource {
    fn migrations(&self) -> Result<Vec<Migration>, ErrorPtr> {
        Migration::unapplied("V00__test", "CREATE TABLE test (id INTEGER PRIMARY KEY);")
            .map(|migration| vec![migration])
            .map_err(|error| Arc::new(error) as ErrorPtr)
    }
}

// refinery migration runner needs a concrete DB client to run - this necessitates an abstraction
// layer; please see MigrationRunnerExecutor for details
struct ExampleMigrationRunnerExecutor;

impl MigrationRunnerExecutor for ExampleMigrationRunnerExecutor {
    fn run_migrations(&self, _runner: &Runner) -> BoxFuture<'_, Result<(), ErrorPtr>> {
        // run migrations here with the given runner
        async { Ok(()) }.boxed()
    }
}

// note: for the sake of simplicity, errors are unwrapped, rather than gracefully handled
#[tokio::main]
async fn main() {
    // create our application, which will run refinery migration runner before other runners
    let mut application =
        application::create_default().expect("unable to create default application");

    // will run migrations from the "migrations" folder if MigrationRunnerExecutor(s) are available
    application.run().await.expect("error running application");
}

Dependencies

~9–28MB
~427K SLoC