Convert Figma logo to code with AI

rust-db logorefinery

Powerful SQL migration toolkit for Rust.

1,298
121
1,298
34

Top Related Projects

12,518

A safe, extensible ORM and Query Builder for Rust

12,950

🧰 The Rust SQL Toolkit. An async, pure Rust SQL crate featuring compile-time checked queries without a DSL. Supports PostgreSQL, MySQL, and SQLite.

6,922

🐚 An async & dynamic ORM for Rust

Native PostgreSQL driver for the Rust programming language

🚂 Engine components of Prisma ORM

2,246

Rust Compile Time ORM robustness,async, pure Rust Dynamic SQL

Quick Overview

Refinery is a powerful database migration tool for Rust. It provides a simple and flexible way to manage database schema changes, supporting multiple database backends including PostgreSQL, MySQL, and SQLite. Refinery allows developers to version control their database schema and apply migrations programmatically or via CLI.

Pros

  • Supports multiple database backends (PostgreSQL, MySQL, SQLite)
  • Offers both programmatic and CLI-based migration management
  • Provides a simple and intuitive API for defining and applying migrations
  • Integrates well with Rust's async ecosystem

Cons

  • Limited to Rust projects, not suitable for non-Rust applications
  • Relatively new project, may have fewer features compared to more established migration tools
  • Documentation could be more comprehensive for advanced use cases

Code Examples

  1. Defining a migration:
use refinery::embed_migrations;

embed_migrations!("./migrations");

This code embeds migration files from the "./migrations" directory into the binary.

  1. Running migrations programmatically:
use refinery::Runner;

let mut runner = Runner::new(&migrations)
    .set_abort_divergent(true)
    .set_abort_missing(true);

runner.run(&mut conn)?;

This example creates a migration runner and applies the migrations to the database connection.

  1. Async migration execution:
use refinery::AsyncRunner;

let mut runner = AsyncRunner::new(&migrations);
runner.run(&mut conn).await?;

This code demonstrates how to run migrations asynchronously using Refinery's AsyncRunner.

Getting Started

  1. Add Refinery to your Cargo.toml:
[dependencies]
refinery = { version = "0.8", features = ["postgresql"] }
  1. Create a migration file in ./migrations/V1__create_users_table.sql:
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL
);
  1. Use Refinery in your Rust code:
use refinery::embed_migrations;

embed_migrations!("./migrations");

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut conn = // ... establish database connection

    let report = migrations::runner().run(&mut conn)?;
    println!("Migration report: {:?}", report);

    Ok(())
}

This example embeds migrations, establishes a database connection, and runs the migrations.

Competitor Comparisons

12,518

A safe, extensible ORM and Query Builder for Rust

Pros of Diesel

  • More mature and widely adopted ORM with a larger ecosystem
  • Provides a powerful query builder and type-safe SQL abstractions
  • Supports multiple database backends (PostgreSQL, MySQL, SQLite)

Cons of Diesel

  • Steeper learning curve due to its more complex API
  • Requires macro-heavy code, which can be harder to debug
  • Slower compile times due to extensive macro usage

Code Comparison

Diesel migration:

diesel::migration_macros::embed_migrations!();

fn run_migrations(conn: &PgConnection) {
    embedded_migrations::run(conn).unwrap();
}

Refinery migration:

let migration = refinery::Migration::new("V1__create_users_table")
    .with_sql(include_str!("./migrations/V1__create_users_table.sql"));
let mut runner = Runner::new(&[migration]);
runner.run(&mut conn)?;

Refinery focuses on simplicity and ease of use for database migrations, while Diesel provides a more comprehensive ORM solution with additional features. Refinery allows for more straightforward migration management without the need for complex macros, making it easier to get started with and maintain. However, Diesel offers more advanced querying capabilities and broader database support, making it suitable for larger and more complex projects.

12,950

🧰 The Rust SQL Toolkit. An async, pure Rust SQL crate featuring compile-time checked queries without a DSL. Supports PostgreSQL, MySQL, and SQLite.

Pros of SQLx

  • Supports async/await and multiple database backends (PostgreSQL, MySQL, SQLite)
  • Provides compile-time checked queries, ensuring type safety
  • Offers a rich set of features, including connection pooling and migrations

Cons of SQLx

  • Steeper learning curve due to its extensive feature set
  • Requires macros for compile-time query checking, which can be complex for beginners
  • Larger dependency footprint compared to Refinery

Code Comparison

SQLx:

let pool = SqlitePool::connect("sqlite::memory:").await?;
let row: (i64,) = sqlx::query_as("SELECT $1")
    .bind(150_i64)
    .fetch_one(&pool).await?;

Refinery:

let mut conn = Connection::open_in_memory()?;
let migration = Migration::new("CREATE TABLE users (id INTEGER PRIMARY KEY)");
Runner::new(&[migration])
    .run(&mut conn)?;

Summary

SQLx is a feature-rich, async-first database library with compile-time query checking, supporting multiple backends. Refinery focuses primarily on database migrations with a simpler API. SQLx offers more comprehensive database interaction capabilities but comes with a steeper learning curve, while Refinery provides a straightforward solution for managing database schema changes.

6,922

🐚 An async & dynamic ORM for Rust

Pros of sea-orm

  • Full-featured ORM with entity generation, query building, and migrations
  • Supports multiple database backends (MySQL, PostgreSQL, SQLite)
  • Active development with frequent updates and community support

Cons of sea-orm

  • Steeper learning curve due to more complex API and features
  • Potentially higher overhead for simple database operations
  • Requires more boilerplate code for basic CRUD operations

Code Comparison

sea-orm example:

let post: Option<post::Model> = Post::find_by_id(1).one(&db).await?;
let comments: Vec<comment::Model> = post.find_related(Comment).all(&db).await?;

refinery example:

let migration = Migration::new("V1__create_users_table")
    .with_up("CREATE TABLE users (id INTEGER PRIMARY KEY)");
runner.run(&[migration])?;

Summary

sea-orm is a comprehensive ORM solution with more features and flexibility, while refinery focuses specifically on database migrations. sea-orm offers a higher-level abstraction for database operations but may introduce more complexity. refinery provides a simpler approach to managing database schema changes but lacks the full ORM capabilities of sea-orm.

Native PostgreSQL driver for the Rust programming language

Pros of rust-postgres

  • More comprehensive PostgreSQL driver with full-featured API
  • Supports asynchronous operations with tokio
  • Extensive documentation and examples

Cons of rust-postgres

  • Focused solely on PostgreSQL, not a general-purpose database migration tool
  • Steeper learning curve for basic database operations

Code Comparison

rust-postgres:

let mut client = Client::connect("host=localhost user=postgres", NoTls)?;
let rows = client.query("SELECT $1::TEXT", &[&"Hello, World!"])?;
let value: &str = rows[0].get(0);

refinery:

let migration = Migration::new("V1__create_users_table")
    .with_up("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)")
    .with_down("DROP TABLE users");
runner.run(&[migration])?;

Key Differences

  • refinery is a database migration tool supporting multiple databases, while rust-postgres is a PostgreSQL-specific driver
  • rust-postgres offers more low-level control and advanced PostgreSQL features
  • refinery provides a higher-level abstraction for managing database schema changes

Use Cases

  • Choose refinery for managing database migrations across different database systems
  • Opt for rust-postgres when building PostgreSQL-specific applications requiring advanced features and fine-grained control

🚂 Engine components of Prisma ORM

Pros of Prisma Engines

  • More comprehensive database tooling, including query engine and migration engine
  • Supports multiple databases (PostgreSQL, MySQL, SQLite, SQL Server)
  • Active development with frequent updates and improvements

Cons of Prisma Engines

  • Larger and more complex codebase, potentially harder to contribute to
  • Tightly coupled with the Prisma ecosystem, less flexible for standalone use
  • Steeper learning curve due to its extensive feature set

Code Comparison

Refinery migration:

V1__create_users_table {
    fn up(&self) -> String {
        "CREATE TABLE users (
            id SERIAL PRIMARY KEY,
            name VARCHAR NOT NULL
        )".to_string()
    }
}

Prisma Engines migration:

model User {
  id   Int    @id @default(autoincrement())
  name String
}

Key Differences

  • Refinery focuses solely on database migrations, while Prisma Engines provides a broader set of database-related tools
  • Refinery uses raw SQL for migrations, whereas Prisma Engines uses a declarative schema language
  • Prisma Engines is part of a larger ORM ecosystem, while Refinery is a standalone migration tool

Use Cases

  • Choose Refinery for lightweight, SQL-based migrations in Rust projects
  • Opt for Prisma Engines when building applications with the Prisma ORM and requiring a full-featured database toolkit
2,246

Rust Compile Time ORM robustness,async, pure Rust Dynamic SQL

Pros of rbatis

  • More comprehensive ORM functionality, including CRUD operations and query building
  • Supports multiple database backends (MySQL, PostgreSQL, SQLite)
  • Provides async support out of the box

Cons of rbatis

  • Steeper learning curve due to more complex API
  • Heavier dependency footprint
  • May introduce more overhead for simple database operations

Code Comparison

rbatis:

#[crud_table]
#[derive(Clone, Debug)]
pub struct User {
    pub id: Option<i32>,
    pub name: String,
}

let user = User { id: None, name: "John".to_string() };
rb.save(&user, &[]).await?;

refinery:

#[derive(Queryable)]
pub struct User {
    pub id: i32,
    pub name: String,
}

diesel::insert_into(users::table)
    .values(&NewUser { name: "John" })
    .execute(conn)?;

Summary

rbatis offers a more feature-rich ORM experience with support for multiple databases and async operations, while refinery focuses on database migrations and schema management. rbatis may be better suited for complex applications with diverse database needs, whereas refinery provides a simpler, more focused solution for managing database schemas and migrations in Rust projects.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

refinery Logo

Powerful SQL migration toolkit for Rust.

Crates.io docs.rs MIT licensed Build Status


Refinery strives to make running migrations for different databases as easy as possible. It works by running your migrations on a provided database connection, either by embedding them on your Rust code, or via the refinery_cli.

Currently postgres, tokio-postgres , mysql, mysql_async, rusqlite and tiberius are supported. If you are using a driver that is not yet supported, namely SQLx you can run migrations providing a Config instead of the connection type, as Config impl's Migrate. You will still need to provide the postgres/mysql/rusqlite/tiberius driver as a feature for Runner::run and tokio-postgres/mysql_async for Runner::run_async. refinery works best with Barrel but you can also have your migrations in .sql files or use any other Rust crate for schema generation.

Usage

  • Add refinery to your Cargo.toml dependencies with the selected driver as feature eg: refinery = { version = "0.8", features = ["rusqlite"]}
  • Migrations can be defined in .sql files or Rust modules that must have a function called migration that returns a String.
  • Migrations can be strictly versioned by prefixing the file with V or not strictly versioned by prefixing the file with U.
  • Migrations, both .sql files and Rust modules must be named in the format [U|V]{1}__{2}.sql or [U|V]{1}__{2}.rs, where {1} represents the migration version and {2} the name.
  • Migrations can be run either by embedding them in your Rust code with embed_migrations macro, or via refinery_cli.

Example: Library

use rusqlite::Connection;

mod embedded {
    use refinery::embed_migrations;
    embed_migrations!("./tests/sql_migrations");
}

fn main() {
    let mut conn = Connection::open_in_memory().unwrap();
    embedded::migrations::runner().run(&mut conn).unwrap();
}

For more library examples, refer to the examples.

Example: CLI

NOTE:

  • Contiguous (adjacent) migration version numbers are restricted to u32 (unsigned, 32-bit integers).
  • Non-contiguous (not adjacent) migration version numbers are restricted to u32 (unsigned, 32-bit integers).
export DATABASE_URL="postgres://postgres:secret@localhost:5432/your-db"
pushd migrations
    # Runs ./src/V1__*.rs or ./src/V1__*.sql 
    refinery migrate -e DATABASE_URL -p ./src -t 1
popd

Example: Deadpool

let mut conn = pool.get().await?;
let client = conn.deref_mut().deref_mut();
let report = embedded::migrations::runner().run_async(client).await?;

Non-contiguous VS Contiguous migrations

Depending on how your project/team has been structured will define whether you want to use contiguous (adjacent) migrations V{1}__{2}.[sql|rs] or non-contiguous (not adjacent) migrations U{1}__{2}.[sql|rs]. If migration sequential numbering reflects the order they were developed and, they are deployed in the order they are numbered, you won't run into any problems using contiguous migrations. This is because you can be sure the next migration being run is always going to have a version number greater than the previous.

With non-contiguous migrations there is more flexibility in the order that the migrations can be created and deployed. If developer 1 creates a PR with a migration today U11__update_cars_table.sql, but it is reviewed for a week. Meanwhile, developer 2 creates a PR with migration U12__create_model_tags.sql that is much simpler and gets merged and deployed immediately. This would stop developer 1's migration from ever running if you were using contiguous migrations because the next migration would need to be > 12.

Implementation details

refinery works by creating a table that keeps all the applied migrations' versions and their metadata. When you run the migrations Runner, refinery compares the applied migrations with the ones to be applied, checking for divergent and missing and executing unapplied migrations.
By default, refinery runs each migration in a single transaction. Alternatively, you can also configure refinery to wrap the entire execution of all migrations in a single transaction by setting set_grouped to true.

Rollback

refinery's design was based on flyway and so, it shares its earlier philosophy on undo/rollback migrations. Flyway has since changed it's opinion but refinery hasn't. To undo/rollback a migration, you have to generate a new one and write specifically what you want to undo.

Support for Additional Database Drivers

While initially it seemed beneficial to support as many aditional drivers as possible in this repo, with the current bandwidth available by the maintainers it's preferable to create them and maintain them on external repositories (see here for context).

Notable external database drivers:

MSRV

refinery aims to support stable Rust, the previous Rust version, and nightly.

Contributing

:balloon: Thanks for your help to improve the project! No contribution is too small and all contributions are valued, feel free to open Issues and submit Pull Requests.

License

This project is licensed under the MIT license.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in refinery by you, shall be licensed as MIT, without any additional terms or conditions.