22 releases (13 breaking)
0.15.0 | Aug 19, 2024 |
---|---|
0.14.0 | Mar 28, 2024 |
0.13.0 | Mar 20, 2024 |
0.10.0 | Nov 24, 2023 |
0.1.0 | May 28, 2022 |
#159 in Database interfaces
410 downloads per month
120KB
2.5K
SLoC
SQLX migrator
A Rust library for writing SQLX migrations using Rust instead of SQL.
License | Crates Version | Docs |
---|---|---|
Supported Databases:
- PostgreSQL
- SQLite
- MySql
- Any
Installation
Add sqlx_migrator
to your Cargo.toml
with the appropriate database feature:
sqlx_migrator = { version = "0.15.0", features=["postgres"] }
OR
sqlx_migrator = { version = "0.15.0", features=["mysql"] }
OR
sqlx_migrator = { version = "0.15.0", features=["sqlite"] }
OR
sqlx_migrator = { version = "0.15.0", features=[
"any",
# Plus any one of above database driver
] }
Usage
To use sqlx_migrator
, implement the Operation
trait to define your migration logic. Here's an example using PostgreSQL:
use sqlx_migrator::error::Error;
use sqlx_migrator::operation::Operation;
pub(crate) struct FirstOperation;
#[async_trait::async_trait]
impl Operation<sqlx::Postgres> for FirstOperation {
// Up function runs apply migration
async fn up(&self,
connection: &mut sqlx::PgConnection,
state: &(),
) -> Result<(), Error> {
sqlx::query("CREATE TABLE sample (id INTEGER PRIMARY KEY, name TEXT)")
.execute(connection)
.await?;
Ok(())
}
// down migration runs down migration
async fn down(
connection: &mut sqlx::PgConnection,
state: &(),
) -> Result<(), Error> {
sqlx::query("DROP TABLE sample").execute(connection).await?;
Ok(())
}
}
After defining your operations, you can create a migration:
use sqlx_migrator::error::Error;
use sqlx_migrator::migration::Migration;
use sqlx_migrator::operation::Operation;
pub(crate) struct FirstMigration;
impl Migration<sqlx::Postgres> for FirstMigration {
// app where migration lies can be any value
fn app(&self) -> &str {
"main"
}
// name of migration
// Combination of migration app and name must be unique to work properly expects for virtual migration
fn name(&self) -> &str {
"first_migration"
}
// Use the parent function to add parents of a migration.
// If you cannot access the parent migration, you can also use
// `(A,N) where A: AsRef<str>, N: AsRef<str>` where A is the app name
// and N is the name of the migration.
fn parents(&self) -> Vec<Box<dyn Migration<sqlx::Postgres>>> {
vec![]
// vec![("main", "initial_migration"), AnotherInitialMigration]
}
// use operations function to add operation part of migration
fn operations(&self) -> Vec<Box<dyn Operation<sqlx::Postgres>>> {
vec![Box::new(FirstOperation)]
}
// Migration trait also have multiple other function see docs for usage
}
This migration can be represented in a simpler form using macros:
use sqlx_migrator::vec_box;
sqlx_migrator::migration!(
sqlx::Postgres,
FirstMigration,
"main",
vec_box![],
vec_box![FirstOperation]
);
// OR
sqlx_migrator::postgres_migration!(
FirstMigration,
"main",
vec_box![],
vec_box![FirstOperation]
);
If your up and down queries are simple strings, you can simplify the implementation:
sqlx_migrator::postgres_migration!(
FirstMigration,
"main",
sqlx_migrator::vec_box![],
sqlx_migrator::vec_box![
(
"CREATE TABLE sample (id INTEGER PRIMARY KEY, name TEXT)",
"DROP TABLE sample"
)
]
);
Finally, create a migrator to run your migrations:
use sqlx_migrator::migrator::{Info, Migrate, Migrator};
use sqlx::Postgres;
#[tokio::main]
async fn main() {
let uri = std::env::var("DATABASE_URL").unwrap();
let pool = sqlx::Pool::<Postgres>::connect(&uri).await.unwrap();
let mut migrator = Migrator::default();
migrator.add_migration(Box::new(FirstMigration));
}
Running Migrations
You can run migrations directly or integrate them into a CLI:
Direct Execution
use sqlx_migrator::migrator::Plan;
let mut conn = pool.acquire().await?;
// use apply all to apply all pending migration
migrator.run(&mut *conn, Plan::apply_all()).await.unwrap();
// or use revert all to revert all applied migrations
migrator.run(&mut *conn, Plan::revert_all()).await.unwrap();
CLI Integration
use sqlx_migrator::cli::MigrationCommand;
MigrationCommand::parse_and_run(&mut *conn, Box::new(migrator)).await.unwrap();
Or extend your own CLI with migrator support:
#[derive(clap::Parser)]
struct Cli {
#[command(subcommand)]
sub_command: CliSubcommand
}
#[derive(clap::Subcommand)]
enum CliSubcommand {
#[command()]
Migrator(sqlx_migrator::cli::MigrationCommand)
}
impl Cli {
async fn run() {
let cli = Self::parse();
// create connection
match cli.sub_command {
Migrator(m) => {
m.run(&mut conn, Box::new(migrator)).await.unwrap()
}
}
}
}
Migrate from sqlx default sql based migration
To migrate from sqlx sql based migration you have two alternative:
- Rewrite SQL migrations as Rust operations and run fake apply cli command
<COMMAND_NAME> apply --fake
: Follow the usage example above. - Create a single Rust operation to apply/revert SQL migrations:
use sqlx_migrator::error::Error;
use sqlx_migrator::operation::Operation;
pub(crate) struct SqlxOperation;
#[async_trait::async_trait]
impl Operation<sqlx::Postgres> for SqlxOperation {
async fn up(&self, connection: &mut sqlx::PgConnection) -> Result<(), Error> {
sqlx::migrate!("migrations").run(connection).await?;
Ok(())
}
async fn down(&self, connection: &mut sqlx::PgConnection) -> Result<(), Error> {
sqlx::migrate!("migrations").undo(connection, 0).await?;
Ok(())
}
}
Advance usage: Custom state management
The Operation
trait in sqlx_migrator
allows for advanced customization by utilizing a State
parameter.
This State
is designed to hold any additional information that your migrations might need to access during execution.
Default Behavior
By default, the State
is set to an empty tuple ()
, which means no additional state is passed:
async fn up(
&self,
connection: &mut <DB as sqlx::Database>::Connection,
state: &(),
) -> Result<(), Error>;
Custom State
However, you can replace this with any type that implements Send
and Sync
.
For example, you could pass a custom configuration struct or other context-specific data to your migrations:
struct MyCustomState {
logging_enabled: bool,
}
#[async_trait::async_trait]
impl Operation<sqlx::Postgres, MyCustomState> for FirstOperation {
async fn up(
&self,
connection: &mut sqlx::PgConnection,
state: &MyCustomState,
) -> Result<(), Error> {
if state.logging_enabled {
println!("Applying migration");
}
// Perform migration operations here
Ok(())
}
}
Using State with the Migrator
To pass a custom state to your migrator, use the Migrator::new
function:
let state = MyCustomState {
logging_enabled: true,
};
let mut migrator = Migrator::new(state);
migrator.add_migration(Box::new(FirstMigration));
Dependencies
~10–24MB
~384K SLoC