diff --git a/src/migration.rs b/src/migration.rs index 29645f4b..6248d3a4 100644 --- a/src/migration.rs +++ b/src/migration.rs @@ -114,6 +114,18 @@ pub struct AppliedMigrationSqlRow { applied_time: String, } +impl AppliedMigrationSqlRow { + #[cfg(test)] + pub(crate) fn new(id: i32, app: &str, name: &str) -> Self { + Self { + id, + app: app.to_string(), + name: name.to_string(), + applied_time: String::new(), + } + } +} + impl AppliedMigrationSqlRow { /// Return id value present on database #[must_use] diff --git a/src/migrator/mod.rs b/src/migrator/mod.rs index 1186d239..ce1b3545 100644 --- a/src/migrator/mod.rs +++ b/src/migrator/mod.rs @@ -358,6 +358,71 @@ fn populate_recursive<'populate, DB, State>( } } +fn get_parent_recursive(with: &BoxMigration) -> Vec> { + let mut parents = with.parents(); + for parent in with.parents() { + parents.extend(get_parent_recursive(&parent)); + } + parents +} + +fn get_run_before_recursive( + with: &BoxMigration, +) -> Vec> { + let mut run_before_list = with.run_before(); + for run_before in with.run_before() { + run_before_list.extend(get_run_before_recursive(&run_before)); + } + run_before_list +} + +fn is_apply_related( + with: &BoxMigration, + migration: &BoxMigration, +) -> bool { + migration.replaces().iter().any(|migration_replace| { + migration_replace == with || is_apply_related(with, migration_replace) + }) || migration.run_before().iter().any(|migration_run_before| { + migration_run_before == with || is_apply_related(with, migration_run_before) + }) +} + +fn is_revert_related( + with: &BoxMigration, + migration: &BoxMigration, +) -> bool { + let parents = get_parent_recursive(migration); + parents.contains(with) +} + +fn only_related_migration( + migration_list: &mut MigrationVec, + with: &BoxMigration, + plan_type: &PlanType, +) { + let mut related_migrations = vec![]; + match plan_type { + PlanType::Apply => { + let with_parents = get_parent_recursive(with); + for &migration in migration_list.iter() { + if with_parents.contains(migration) || is_apply_related(with, migration) { + related_migrations.push(migration); + } + } + } + PlanType::Revert => { + let with_run_before = get_run_before_recursive(with); + for &migration in migration_list.iter() { + if with_run_before.contains(migration) || is_revert_related(with, migration) { + related_migrations.push(migration); + } + } + } + } + migration_list + .retain(|&migration| related_migrations.contains(&migration) || migration == with); +} + /// Process plan to provided migrations list fn process_plan( migration_list: &mut MigrationVec, @@ -376,7 +441,7 @@ where migration_list.retain(|migration| applied_migrations.contains(migration)); migration_list.reverse(); } - }; + } if let Some((app, migration_name)) = &plan.app_migration { // Find position of last migration which matches condition of provided app and @@ -412,6 +477,8 @@ where pos }; migration_list.truncate(position + 1); + let pos_elem = migration_list[position]; + only_related_migration(migration_list, pos_elem, &plan.plan_type); } else if let Some(count) = plan.count { let actual_len = migration_list.len(); if count > actual_len { diff --git a/src/migrator/tests.rs b/src/migrator/tests.rs index 04305a76..d2d5b825 100644 --- a/src/migrator/tests.rs +++ b/src/migrator/tests.rs @@ -9,6 +9,25 @@ use crate::vec_box; #[derive(Default)] struct CustomMigrator { migrations: Vec>>, + applied_migrations: Vec, +} + +impl CustomMigrator { + fn add_applied_migrations(&mut self, migrations: Vec>>) { + for migration in migrations { + self.add_applied_migration(&migration); + } + } + + #[allow(clippy::borrowed_box)] + fn add_applied_migration(&mut self, migration: &Box>) { + let current_length = self.migrations.len(); + self.applied_migrations.push(AppliedMigrationSqlRow::new( + i32::try_from(current_length).unwrap(), + migration.app(), + migration.name(), + )); + } } impl Info for CustomMigrator { @@ -61,7 +80,7 @@ impl DatabaseOperation for CustomMigrator { &self, _connection: &mut ::Connection, ) -> Result, Error> { - Ok(vec![]) + Ok(self.applied_migrations.clone()) } async fn lock( @@ -112,7 +131,7 @@ macro_rules! migration { }; } -async fn generate_plan( +async fn generate_apply_all_plan( migrator: &mut CustomMigrator, migration_list: Vec>>, ) -> Result>>, Error> { @@ -133,7 +152,7 @@ async fn simple_test() { struct C; migration!(C, "c", vec_box!(B), vec_box!(), vec_box!()); let mut migrator = CustomMigrator::default(); - let plan = generate_plan(&mut migrator, vec_box!(A, B, C)) + let plan = generate_apply_all_plan(&mut migrator, vec_box!(A, B, C)) .await .unwrap(); assert!(plan.contains(&&(Box::new(A) as Box>))); @@ -152,7 +171,7 @@ async fn replace_test() { struct D; migration!(D, "d", vec_box!(), vec_box!(C), vec_box!()); let mut migrator = CustomMigrator::default(); - let plan = generate_plan(&mut migrator, vec_box!(A, B, C, D)) + let plan = generate_apply_all_plan(&mut migrator, vec_box!(A, B, C, D)) .await .unwrap(); let d_position = plan @@ -180,7 +199,7 @@ async fn run_before_test() { struct D; migration!(D, "d", vec_box!(), vec_box!(), vec_box!(C)); let mut migrator = CustomMigrator::default(); - let plan = generate_plan(&mut migrator, vec_box!(A, B, C, D)) + let plan = generate_apply_all_plan(&mut migrator, vec_box!(A, B, C, D)) .await .unwrap(); let d_position = plan @@ -210,7 +229,7 @@ async fn replaces_multiple_times() { struct D; migration!(D, "d", vec_box!(), vec_box!(B), vec_box!()); let mut migrator = CustomMigrator::default(); - let plan = generate_plan(&mut migrator, vec_box!(A, B, C, D)).await; + let plan = generate_apply_all_plan(&mut migrator, vec_box!(A, B, C, D)).await; assert!(plan.is_err()); } @@ -225,7 +244,7 @@ async fn replace_run_before_cond_1() { struct D; migration!(D, "d", vec_box!(), vec_box!(), vec_box!(B)); let mut migrator = CustomMigrator::default(); - let plan = generate_plan(&mut migrator, vec_box!(A, B, C, D)).await; + let plan = generate_apply_all_plan(&mut migrator, vec_box!(A, B, C, D)).await; assert!(plan.is_ok(), "{:?}", plan.err()); } @@ -242,7 +261,7 @@ async fn replaces_run_before_cond_2() { struct E; migration!(E, "e", vec_box!(), vec_box!(), vec_box!(C)); let mut migrator = CustomMigrator::default(); - let plan = generate_plan(&mut migrator, vec_box!(A, B, C, D, E)).await; + let plan = generate_apply_all_plan(&mut migrator, vec_box!(A, B, C, D, E)).await; assert!(plan.is_ok(), "{:?}", plan.err()); } @@ -259,7 +278,7 @@ async fn replaces_run_before_cond_3() { struct E; migration!(E, "e", vec_box!(), vec_box!(), vec_box!(D)); let mut migrator = CustomMigrator::default(); - let plan = generate_plan(&mut migrator, vec_box!(A, B, C, D, E)).await; + let plan = generate_apply_all_plan(&mut migrator, vec_box!(A, B, C, D, E)).await; assert!(plan.is_ok(), "{:?}", plan.err()); } @@ -276,7 +295,7 @@ async fn replaces_run_before_cond_4() { struct E; migration!(E, "e", vec_box!(), vec_box!(), vec_box!(C)); let mut migrator = CustomMigrator::default(); - let plan = generate_plan(&mut migrator, vec_box!(A, B, C, D, E)).await; + let plan = generate_apply_all_plan(&mut migrator, vec_box!(A, B, C, D, E)).await; assert!(plan.is_ok(), "{:?}", plan.err()); } @@ -291,7 +310,7 @@ async fn replaces_run_before_cond_5() { struct D; migration!(D, "d", vec_box!(), vec_box!(C), vec_box!(C)); let mut migrator = CustomMigrator::default(); - let plan = generate_plan(&mut migrator, vec_box!(A, B, C, D)).await; + let plan = generate_apply_all_plan(&mut migrator, vec_box!(A, B, C, D)).await; assert!(plan.is_err()); } @@ -308,7 +327,7 @@ async fn replaces_run_before_cond_6() { struct E; migration!(E, "e", vec_box!(), vec_box!(D), vec_box!(C)); let mut migrator = CustomMigrator::default(); - let plan = generate_plan(&mut migrator, vec_box!(A, B, C, D, E)).await; + let plan = generate_apply_all_plan(&mut migrator, vec_box!(A, B, C, D, E)).await; assert!(plan.is_err()); } @@ -323,7 +342,7 @@ async fn replaces_run_before_cond_7() { struct D; migration!(D, "d", vec_box!(), vec_box!(C), vec_box!()); let mut migrator = CustomMigrator::default(); - let plan = generate_plan(&mut migrator, vec_box!(A, B, C, D)).await; + let plan = generate_apply_all_plan(&mut migrator, vec_box!(A, B, C, D)).await; assert!(plan.is_err()); } @@ -334,6 +353,101 @@ async fn loop_error() { struct B; migration!(B, "b", vec_box!(A), vec_box!(), vec_box!(A)); let mut migrator = CustomMigrator::default(); - let plan = generate_plan(&mut migrator, vec_box!(A, B)).await; + let plan = generate_apply_all_plan(&mut migrator, vec_box!(A, B)).await; assert!(plan.is_err()); } + +#[tokio::test] +async fn apply_plan_size_test() { + struct A; + migration!(A, "a", vec_box!(), vec_box!(), vec_box!()); + struct B; + migration!(B, "b", vec_box!(A), vec_box!(), vec_box!()); + struct C; + migration!(C, "c", vec_box!(B), vec_box!(), vec_box!()); + struct D; + migration!(D, "d", vec_box!(B), vec_box!(), vec_box!()); + struct E; + migration!(E, "e", vec_box!(C), vec_box!(), vec_box!()); + struct F; + migration!(F, "f", vec_box!(D), vec_box!(), vec_box!()); + struct G; + migration!(G, "g", vec_box!(E), vec_box!(), vec_box!()); + let mut migrator = CustomMigrator::default(); + migrator.add_migrations(vec_box!(A, B, C, D, E, F, G)); + let sqlite = SqlitePool::connect("sqlite::memory:").await.unwrap(); + let mut conn = sqlite.acquire().await.unwrap(); + let full_plan = migrator + .generate_migration_plan(Some(&Plan::apply_all()), &mut conn) + .await + .unwrap(); + assert!(full_plan.len() == 7); + let plan_till_f = migrator + .generate_migration_plan( + Some(&Plan::apply_name("test", &Some("f".to_string()))), + &mut conn, + ) + .await + .unwrap(); + assert!(plan_till_f.len() == 4); + let plan_till_g = migrator + .generate_migration_plan( + Some(&Plan::apply_name("test", &Some("g".to_string()))), + &mut conn, + ) + .await + .unwrap(); + assert!(plan_till_g.len() == 5); +} + +#[tokio::test] +async fn revert_plan_size_test() { + struct A; + migration!(A, "a", vec_box!(), vec_box!(), vec_box!()); + struct B; + migration!(B, "b", vec_box!(A), vec_box!(), vec_box!()); + struct C; + migration!(C, "c", vec_box!(B), vec_box!(), vec_box!()); + struct D; + migration!(D, "d", vec_box!(B), vec_box!(), vec_box!()); + struct E; + migration!(E, "e", vec_box!(C), vec_box!(), vec_box!()); + struct F; + migration!(F, "f", vec_box!(D), vec_box!(), vec_box!()); + struct G; + migration!(G, "g", vec_box!(E), vec_box!(), vec_box!()); + let mut migrator = CustomMigrator::default(); + migrator.add_migrations(vec_box!(A, B, C, D, E, F, G)); + migrator.add_applied_migrations(vec_box!(A, B, C, D, E, F, G)); + let sqlite = SqlitePool::connect("sqlite::memory:").await.unwrap(); + let mut conn = sqlite.acquire().await.unwrap(); + let apply_plan = migrator + .generate_migration_plan(Some(&Plan::apply_all()), &mut conn) + .await + .unwrap(); + assert!(apply_plan.is_empty()); + let plan_till_f = migrator + .generate_migration_plan( + Some(&Plan::revert_name("test", &Some("f".to_string()))), + &mut conn, + ) + .await + .unwrap(); + assert!(plan_till_f.len() == 1); + let plan_till_c = migrator + .generate_migration_plan( + Some(&Plan::revert_name("test", &Some("c".to_string()))), + &mut conn, + ) + .await + .unwrap(); + assert!(plan_till_c.len() == 3); + let plan_till_b = migrator + .generate_migration_plan( + Some(&Plan::revert_name("test", &Some("b".to_string()))), + &mut conn, + ) + .await + .unwrap(); + assert!(plan_till_b.len() == 6); +}