diff --git a/sea-orm-cli/src/cli.rs b/sea-orm-cli/src/cli.rs index 4a918a628..1b3d9dedd 100644 --- a/sea-orm-cli/src/cli.rs +++ b/sea-orm-cli/src/cli.rs @@ -67,6 +67,22 @@ pub enum MigrateSubcommands { help = "Number of pending migrations to be rolled back" )] num: u32, + + #[clap( + value_parser, + short = 'V', + long, + help = "Version of pending migrations to be applied" + )] + version: Option, + + #[clap( + value_parser, + short, + long, + help = "force version of pending migrations to be applied" + )] + force: bool, }, #[clap(value_parser, about = "Rollback applied migrations")] Down { @@ -78,6 +94,22 @@ pub enum MigrateSubcommands { help = "Number of pending migrations to be rolled back" )] num: u32, + + #[clap( + value_parser, + short = 'V', + long, + help = "Version of pending migrations to be rolled back" + )] + version: Option, + + #[clap( + value_parser, + short, + long, + help = "force version of pending migrations to be rolled back" + )] + force: bool, }, } diff --git a/sea-orm-cli/src/commands.rs b/sea-orm-cli/src/commands.rs index 5b3936453..b926b8ca1 100644 --- a/sea-orm-cli/src/commands.rs +++ b/sea-orm-cli/src/commands.rs @@ -259,16 +259,30 @@ pub fn run_migrate_command( return Ok(()); } _ => { - let (subcommand, migration_dir, steps, verbose) = match command { - Some(MigrateSubcommands::Fresh) => ("fresh", migration_dir, None, verbose), - Some(MigrateSubcommands::Refresh) => ("refresh", migration_dir, None, verbose), - Some(MigrateSubcommands::Reset) => ("reset", migration_dir, None, verbose), - Some(MigrateSubcommands::Status) => ("status", migration_dir, None, verbose), - Some(MigrateSubcommands::Up { num }) => ("up", migration_dir, Some(num), verbose), - Some(MigrateSubcommands::Down { num }) => { - ("down", migration_dir, Some(num), verbose) + let (subcommand, migration_dir, steps, version, force, verbose) = match command { + Some(MigrateSubcommands::Fresh) => { + ("fresh", migration_dir, None, None, false, verbose) } - _ => ("up", migration_dir, None, verbose), + Some(MigrateSubcommands::Refresh) => { + ("refresh", migration_dir, None, None, false, verbose) + } + Some(MigrateSubcommands::Reset) => { + ("reset", migration_dir, None, None, false, verbose) + } + Some(MigrateSubcommands::Status) => { + ("status", migration_dir, None, None, false, verbose) + } + Some(MigrateSubcommands::Up { + num, + version, + force, + }) => ("up", migration_dir, Some(num), version, force, verbose), + Some(MigrateSubcommands::Down { + num, + version, + force, + }) => ("down", migration_dir, Some(num), version, force, verbose), + _ => ("up", migration_dir, None, None, false, verbose), }; // Construct the `--manifest-path` @@ -293,6 +307,13 @@ pub fn run_migrate_command( if !num.is_empty() { args.extend(["-n", num.as_str()]) } + let version = version.unwrap_or(String::new()); + if !version.is_empty() { + args.extend(["-V", version.as_str()]); + } + if force { + args.push("-f"); + } if verbose { args.push("-v"); } diff --git a/sea-orm-migration/src/cli.rs b/sea-orm-migration/src/cli.rs index 435a53a6a..e8aa89364 100644 --- a/sea-orm-migration/src/cli.rs +++ b/sea-orm-migration/src/cli.rs @@ -20,12 +20,8 @@ where run_migrate(migrator, db, cli.command, cli.verbose).await; } -pub async fn run_migrate( - _: M, - db: &DbConn, - command: Option, - verbose: bool, -) where +pub async fn run_migrate(_: M, db: &DbConn, command: Option, verbose: bool) +where M: MigratorTrait, { let filter = match verbose { @@ -57,8 +53,36 @@ pub async fn run_migrate( Some(MigrateSubcommands::Refresh) => M::refresh(db).await, Some(MigrateSubcommands::Reset) => M::reset(db).await, Some(MigrateSubcommands::Status) => M::status(db).await, - Some(MigrateSubcommands::Up { num }) => M::up(db, Some(num)).await, - Some(MigrateSubcommands::Down { num }) => M::down(db, Some(num)).await, + Some(MigrateSubcommands::Up { + num, + version, + force, + }) => { + if version.is_some() { + if force { + M::change_to_version(db, version.unwrap().as_str()).await + } else { + M::up_to_version(db, version.unwrap().as_str()).await + } + } else { + M::up(db, Some(num)).await + } + } + Some(MigrateSubcommands::Down { + num, + version, + force, + }) => { + if version.is_some() { + if force { + M::change_to_version(db, version.unwrap().as_str()).await + } else { + M::down_to_version(db, version.unwrap().as_str()).await + } + } else { + M::down(db, Some(num)).await + } + } _ => M::up(db, None).await, } .unwrap_or_else(handle_error); diff --git a/sea-orm-migration/src/migrator.rs b/sea-orm-migration/src/migrator.rs index 698045ab1..8794e52dc 100644 --- a/sea-orm-migration/src/migrator.rs +++ b/sea-orm-migration/src/migrator.rs @@ -307,6 +307,109 @@ pub trait MigratorTrait: Send { Ok(()) } + + /// Apply or Rollback migrations to version + async fn change_to_version(db: &DbConn, version: &str) -> Result<(), DbErr> { + Self::up_to_version(db, version).await?; + Self::down_to_version(db, version).await + } + + /// Apply migrations to version + async fn up_to_version(db: &DbConn, version: &str) -> Result<(), DbErr> { + let migrations = Self::prepare_migration(db, MigrationStatus::Pending, version).await?; + Self::exec_migration(db, MigrationStatus::Pending, migrations).await + } + + /// Rollback migrations to version + async fn down_to_version(db: &DbConn, version: &str) -> Result<(), DbErr> { + let mut migrations = Self::prepare_migration(db, MigrationStatus::Applied, version).await?; + if !migrations.is_empty() { + if migrations[migrations.len() - 1].migration.name() == version { + migrations.pop(); + } + } + Self::exec_migration(db, MigrationStatus::Applied, migrations).await + } + + async fn exec_migration( + db: &DbConn, + status: MigrationStatus, + migrations: Vec, + ) -> Result<(), DbErr> { + Self::install(db).await?; + let manager = SchemaManager::new(db); + + match status { + MigrationStatus::Pending => { + for Migration { migration, .. } in migrations { + info!("Applying migration '{}'", migration.name()); + migration.up(&manager).await?; + info!("Migration '{}' has been applied", migration.name()); + let now = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("SystemTime before UNIX EPOCH!"); + seaql_migrations::ActiveModel { + version: ActiveValue::Set(migration.name().to_owned()), + applied_at: ActiveValue::Set(now.as_secs() as i64), + } + .insert(db) + .await?; + } + } + MigrationStatus::Applied => { + for Migration { migration, .. } in migrations { + info!("Rolling back migration '{}'", migration.name()); + migration.down(&manager).await?; + info!("Migration '{}' has been rollbacked", migration.name()); + seaql_migrations::Entity::delete_many() + .filter(seaql_migrations::Column::Version.eq(migration.name())) + .exec(db) + .await?; + } + } + } + Ok(()) + } + + async fn prepare_migration( + db: &DbConn, + status: MigrationStatus, + version: &str, + ) -> Result, DbErr> { + let mut matched = false; + let migrations = Self::get_migration_with_status(db).await?; + let mut rs: Vec = Vec::new(); + match status { + MigrationStatus::Pending => { + for migration in migrations { + let name = migration.migration.name().to_owned(); + if migration.status == MigrationStatus::Pending { + rs.push(migration); + } + if name == version { + matched = true; + break; + } + } + } + MigrationStatus::Applied => { + for migration in migrations.into_iter().rev() { + let name = migration.migration.name().to_owned(); + if migration.status == MigrationStatus::Applied { + rs.push(migration); + } + if name == version { + matched = true; + break; + } + } + } + } + if !matched { + rs.clear(); + } + Ok(rs) + } } pub(crate) fn query_tables(db: &DbConn) -> SelectStatement {