diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 47e5b51e..77f7d4e2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -77,16 +77,27 @@ jobs: uses: dtolnay/rust-toolchain@nightly - name: Install cargo make uses: davidB/rust-cargo-make@v1 - - name: Run example + - name: Print cli help message + run: | + cargo make run_postgres_example --help + cargo make run_postgres_example apply --help + cargo make run_postgres_example drop --help + cargo make run_postgres_example list --help + cargo make run_postgres_example revert --help + - name: Run postgres example run: | cargo make run_postgres_example apply cargo make run_postgres_example list cargo make run_postgres_example revert --all --force cargo make run_postgres_example list + - name: Run sqlite example + run: | cargo make run_sqlite_example apply cargo make run_sqlite_example list cargo make run_sqlite_example revert --all --force cargo make run_sqlite_example list + - name: Run mysql example + run: | cargo make run_mysql_example apply cargo make run_mysql_example list cargo make run_mysql_example revert --all --force diff --git a/src/cli.rs b/src/cli.rs index db15a120..6f091630 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -146,6 +146,9 @@ pub struct Apply { /// Check for pending migration #[arg(long)] check: bool, + /// Number of migration to apply. Conflicts with app args + #[arg(long, conflicts_with = "app")] + count: Option, /// Make migration applied without running migration operations #[arg(long)] fake: bool, @@ -176,7 +179,14 @@ impl Apply { self.app.clone(), self.migration.clone(), )?; - let migrations = migrator.generate_migration_plan(plan, connection).await?; + let mut migrations = migrator.generate_migration_plan(plan, connection).await?; + if let Some(count) = self.count { + let actual_len = migrations.len(); + if count > actual_len { + return Err(Error::CountGreater { actual_len, count }); + } + migrations.truncate(count); + } if self.check && !migrations.is_empty() { return Err(Error::PendingMigrationPresent); } @@ -235,13 +245,16 @@ impl Apply { /// CLI struct for revert subcommand #[allow(clippy::struct_excessive_bools)] pub struct Revert { - /// Revert all migration - #[arg(long)] + /// Revert all migration. Conflicts with app args + #[arg(long, conflicts_with = "app")] all: bool, /// Revert migration till app migrations is reverted. If it is present /// alongside migration options than only till migration is reverted #[arg(long)] app: Option, + /// Number of migration to revert. Conflicts with all and app args + #[arg(long, conflicts_with_all = ["all", "app"])] + count: Option, /// Make migration reverted without running revert operation #[arg(long)] fake: bool, @@ -266,21 +279,22 @@ impl Revert { DB: sqlx::Database, { migrator.lock(connection).await?; - let app_is_some = self.app.is_some(); let plan = Plan::new( crate::migrator::PlanType::Revert, self.app.clone(), self.migration.clone(), )?; - let revert_plan = migrator.generate_migration_plan(plan, connection).await?; - let revert_migrations; - if self.all || app_is_some { - revert_migrations = revert_plan; - } else if let Some(latest_migration) = revert_plan.first() { - revert_migrations = vec![latest_migration]; - } else { - revert_migrations = vec![]; + let mut revert_migrations = migrator.generate_migration_plan(plan, connection).await?; + if let Some(count) = self.count { + let actual_len = revert_migrations.len(); + if count > actual_len { + return Err(Error::CountGreater { actual_len, count }); + } + revert_migrations.truncate(count); + } else if !self.all && self.app.is_none() && !revert_migrations.is_empty() { + revert_migrations.truncate(1); } + if self.plan { let first_width = 10; let second_width = 50; diff --git a/src/error.rs b/src/error.rs index f22f33ea..6c35c38b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -56,6 +56,16 @@ pub enum Error { #[error("applied migrations exists. Revert all using revert subcommand")] AppliedMigrationExists, + #[cfg(feature = "cli")] + /// Error when count of migrations is big than total number of migration + #[error("count of migrations is big only {actual_len} present passed {count}")] + CountGreater { + /// Actual length of migration + actual_len: usize, + /// Count passed in option + count: usize, + }, + /// Error when unsupported database is used as any database #[error("database not supported for any migrator")] UnsupportedDatabase, diff --git a/src/operation.rs b/src/operation.rs index 48fc3269..27563d60 100644 --- a/src/operation.rs +++ b/src/operation.rs @@ -47,9 +47,9 @@ where } /// Whether up operation is destructible or not. If operation is - /// destructible than user should answer before running migration. Down - /// operation are always destructible by default and it cannot be changed - /// By default it is false + /// destructible than user should answer before running migration. By + /// default up operation are false. Down operation are always + /// destructible by default and it cannot be changed fn is_destructible(&self) -> bool { false }