diff --git a/README.MD b/README.MD index 4776224..3b8fd67 100644 --- a/README.MD +++ b/README.MD @@ -163,7 +163,7 @@ async fn main() { # Running Migrations You can run migrations directly or integrate them into a CLI: -## Direct Execution +## Programmatic Execution ```rust use sqlx_migrator::migrator::Plan; let mut conn = pool.acquire().await?; @@ -171,9 +171,15 @@ let mut conn = pool.acquire().await?; 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(); +// If you need to apply or revert to certain stage than see `Plan` docs ``` ## CLI Integration +To integrate sqlx_migrator into your CLI, you can either use the built-in +`MigrationCommand` or extend your own CLI with migrator support. Below are +examples of both approaches: + +#### Built-in Migration Command ```rust use sqlx_migrator::cli::MigrationCommand; @@ -181,7 +187,7 @@ use sqlx_migrator::cli::MigrationCommand; MigrationCommand::parse_and_run(&mut *conn, Box::new(migrator)).await.unwrap(); ``` -Or extend your own CLI with migrator support: +#### Extending Your Own CLI with Migrator Support ```rust #[derive(clap::Parser)] @@ -211,31 +217,29 @@ impl Cli { # Migrate from sqlx default sql based migration -To migrate from sqlx sql based migration you have two alternative: +To migrate from sqlx sql based migration to rust migration the recommended approach +is to rewrite your SQL migrations as Rust operations and migrations as explained above. +After rewriting your SQL migrations, you need to mark them as applied without re-executing them. +This step ensures that the migration state aligns with the existing database. +There are two ways to perform a fake apply: -1. Rewrite SQL migrations as Rust operations and run fake apply cli command -` apply --fake`: Follow the usage example above. -2. Create a single Rust operation to apply/revert SQL migrations: +#### Programmatic Fake Apply +Use the fake option with the `Plan::apply_all()` function: ```rust -use sqlx_migrator::error::Error; -use sqlx_migrator::operation::Operation; +use sqlx_migrator::migrator::{Plan, Migrator}; -pub(crate) struct SqlxOperation; +migrator.run(&mut *conn, Plan::apply_all().fake(true)).await.unwrap(); +``` -#[async_trait::async_trait] -impl Operation for SqlxOperation { - async fn up(&self, connection: &mut sqlx::PgConnection) -> Result<(), Error> { - sqlx::migrate!("migrations").run(connection).await?; - Ok(()) - } +#### CLI-Based Fake Apply +If you're using a CLI, use the --fake flag with the apply command: ` apply --fake` - async fn down(&self, connection: &mut sqlx::PgConnection) -> Result<(), Error> { - sqlx::migrate!("migrations").undo(connection, 0).await?; - Ok(()) - } -} -``` +### Note: Before writing any other migrations + +Before adding new migrations for future updates, ensure you complete the above steps to mark existing migrations as applied. Run the fake apply only once to align the migration state. After this, remove the `fake(true)` option or the `--fake` flag to allow new migrations to execute normally. + +By following these steps, you can seamlessly transition from SQLX SQL-based migrations to Rust migrations while maintaining an accurate migration state and ensuring compatibility for future updates. [license_badge]: https://img.shields.io/github/license/iamsauravsharma/sqlx_migrator.svg?style=for-the-badge [license_link]: LICENSE diff --git a/src/cli.rs b/src/cli.rs index cd80d95..44f66b2 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -236,6 +236,7 @@ impl Apply { } else { plan = Plan::apply_all(); }; + let plan = plan.fake(self.fake); let migrations = migrator .generate_migration_plan(connection, Some(&plan)) .await?; @@ -259,18 +260,12 @@ impl Apply { ); } } - } else if self.fake { - for migration in migrations { - migrator - .add_migration_to_db_table(connection, migration) - .await?; - } } else { let destructible_migrations = migrations .iter() .filter(|m| m.operations().iter().any(|o| o.is_destructible())) .collect::>(); - if !self.force && !destructible_migrations.is_empty() { + if !self.force && !destructible_migrations.is_empty() && !self.fake { let mut input = String::new(); println!( "Do you want to apply destructible migrations {} (y/N)", @@ -340,6 +335,7 @@ impl Revert { } else { plan = Plan::revert_count(1); }; + let plan = plan.fake(self.fake); let revert_migrations = migrator .generate_migration_plan(connection, Some(&plan)) .await?; @@ -361,14 +357,8 @@ impl Revert { ); } } - } else if self.fake { - for migration in revert_migrations { - migrator - .delete_migration_from_db_table(connection, migration) - .await?; - } } else { - if !self.force && !revert_migrations.is_empty() { + if !self.force && !revert_migrations.is_empty() && !self.fake { let mut input = String::new(); println!( "Do you want to revert {} migrations (y/N)", diff --git a/src/migrator/mod.rs b/src/migrator/mod.rs index 166446d..043a89b 100644 --- a/src/migrator/mod.rs +++ b/src/migrator/mod.rs @@ -185,6 +185,7 @@ pub struct Plan { plan_type: PlanType, app_migration: Option<(String, Option)>, count: Option, + fake: bool, } impl Plan { @@ -197,9 +198,27 @@ impl Plan { plan_type, app_migration, count, + fake: false, } } + /// Sets the plan as a "fake" plan. + /// + /// When the plan is marked as fake, the migration status is updated to + /// either "applied" or "reverted" without actually performing any + /// migration operations. This is useful for scenarios where you want to + /// simulate the effect of applying or reverting a migration, but + /// without making changes to the database. + /// + /// By default, the `fake` flag is set to `false`, and the migration + /// operations are executed as expected. + #[must_use] + pub fn fake(self, fake: bool) -> Self { + let mut plan = self; + plan.fake = fake; + plan + } + /// Creates a new plan to apply all migrations. #[must_use] pub fn apply_all() -> Self { @@ -853,15 +872,19 @@ where tracing::debug!("applying {} : {}", migration.app(), migration.name()); if migration.is_atomic() { let mut transaction = connection.begin().await?; - for operation in migration.operations() { - operation.up(&mut transaction).await?; + if !plan.fake { + for operation in migration.operations() { + operation.up(&mut transaction).await?; + } } self.add_migration_to_db_table(&mut transaction, migration) .await?; transaction.commit().await?; } else { - for operation in migration.operations() { - operation.up(connection).await?; + if !plan.fake { + for operation in migration.operations() { + operation.up(connection).await?; + } } self.add_migration_to_db_table(connection, migration) .await?; @@ -876,15 +899,19 @@ where if migration.is_atomic() { let mut transaction = connection.begin().await?; - for operation in operations { - operation.down(&mut transaction).await?; + if !plan.fake { + for operation in operations { + operation.down(&mut transaction).await?; + } } self.delete_migration_from_db_table(&mut transaction, migration) .await?; transaction.commit().await?; } else { - for operation in operations { - operation.down(connection).await?; + if !plan.fake { + for operation in operations { + operation.down(connection).await?; + } } self.delete_migration_from_db_table(connection, migration) .await?;