Skip to content

Commit

Permalink
feat: support fake plan creation using library
Browse files Browse the repository at this point in the history
Signed-off-by: Saurav Sharma <[email protected]>
  • Loading branch information
iamsauravsharma committed Jan 11, 2025
1 parent 2818475 commit 31c7d60
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 43 deletions.
46 changes: 25 additions & 21 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -163,25 +163,31 @@ 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?;
// 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();
// 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;

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)]
Expand Down Expand Up @@ -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
`<COMMAND_NAME> 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<sqlx::Postgres> 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: `<migrator_cli_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
Expand Down
18 changes: 4 additions & 14 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?;
Expand All @@ -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::<Vec<_>>();
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)",
Expand Down Expand Up @@ -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?;
Expand All @@ -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)",
Expand Down
43 changes: 35 additions & 8 deletions src/migrator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ pub struct Plan {
plan_type: PlanType,
app_migration: Option<(String, Option<String>)>,
count: Option<usize>,
fake: bool,
}

impl Plan {
Expand All @@ -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 {
Expand Down Expand Up @@ -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?;
Expand All @@ -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?;
Expand Down

0 comments on commit 31c7d60

Please sign in to comment.