Top Related Projects
A safe, extensible ORM and Query Builder for Rust
🧰 The Rust SQL Toolkit. An async, pure Rust SQL crate featuring compile-time checked queries without a DSL. Supports PostgreSQL, MySQL, and SQLite.
🐚 An async & dynamic ORM for Rust
Native PostgreSQL driver for the Rust programming language
🚂 Engine components of Prisma ORM
Rust Compile Time ORM with Async 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
- Defining a migration:
use refinery::embed_migrations;
embed_migrations!("./migrations");
This code embeds migration files from the "./migrations" directory into the binary.
- 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.
- 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
- Add Refinery to your
Cargo.toml
:
[dependencies]
refinery = { version = "0.8", features = ["postgresql"] }
- 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
);
- 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
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.
🧰 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.
🐚 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
Rust Compile Time ORM with Async 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 designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual CopilotREADME
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 aString
. - Migrations can be strictly versioned by prefixing the file with
V
or not strictly versioned by prefixing the file withU
. - 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:
- Klickhouse (Clickhouse database driver with refinery support)
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.
Top Related Projects
A safe, extensible ORM and Query Builder for Rust
🧰 The Rust SQL Toolkit. An async, pure Rust SQL crate featuring compile-time checked queries without a DSL. Supports PostgreSQL, MySQL, and SQLite.
🐚 An async & dynamic ORM for Rust
Native PostgreSQL driver for the Rust programming language
🚂 Engine components of Prisma ORM
Rust Compile Time ORM with Async Dynamic SQL
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual Copilot