Skip to content

Commit

Permalink
feat: while applying or reverting till migration do minimal migration
Browse files Browse the repository at this point in the history
Signed-off-by: Saurav Sharma <[email protected]>
  • Loading branch information
iamsauravsharma committed Mar 18, 2024
1 parent 497588c commit e881cc2
Show file tree
Hide file tree
Showing 3 changed files with 208 additions and 15 deletions.
12 changes: 12 additions & 0 deletions src/migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
69 changes: 68 additions & 1 deletion src/migrator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,71 @@ fn populate_recursive<'populate, DB, State>(
}
}

fn get_parent_recursive<DB, State>(with: &BoxMigration<DB, State>) -> Vec<BoxMigration<DB, State>> {
let mut parents = with.parents();
for parent in with.parents() {
parents.extend(get_parent_recursive(&parent));
}
parents
}

fn get_run_before_recursive<DB, State>(
with: &BoxMigration<DB, State>,
) -> Vec<BoxMigration<DB, State>> {
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<DB, State>(
with: &BoxMigration<DB, State>,
migration: &BoxMigration<DB, State>,
) -> 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<DB, State>(
with: &BoxMigration<DB, State>,
migration: &BoxMigration<DB, State>,
) -> bool {
let parents = get_parent_recursive(migration);
parents.contains(with)
}

fn only_related_migration<DB, State>(
migration_list: &mut MigrationVec<DB, State>,
with: &BoxMigration<DB, State>,
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<DB, State>(
migration_list: &mut MigrationVec<DB, State>,
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
142 changes: 128 additions & 14 deletions src/migrator/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,25 @@ use crate::vec_box;
#[derive(Default)]
struct CustomMigrator {
migrations: Vec<Box<dyn Migration<Sqlite>>>,
applied_migrations: Vec<AppliedMigrationSqlRow>,
}

impl CustomMigrator {
fn add_applied_migrations(&mut self, migrations: Vec<Box<dyn Migration<Sqlite>>>) {
for migration in migrations {
self.add_applied_migration(&migration);
}
}

#[allow(clippy::borrowed_box)]
fn add_applied_migration(&mut self, migration: &Box<dyn Migration<Sqlite>>) {
let current_length = self.migrations.len();
self.applied_migrations.push(AppliedMigrationSqlRow::new(
i32::try_from(current_length).unwrap(),
migration.app(),
migration.name(),
));
}
}

impl Info<Sqlite, ()> for CustomMigrator {
Expand Down Expand Up @@ -61,7 +80,7 @@ impl DatabaseOperation<Sqlite, ()> for CustomMigrator {
&self,
_connection: &mut <Sqlite as sqlx::Database>::Connection,
) -> Result<Vec<AppliedMigrationSqlRow>, Error> {
Ok(vec![])
Ok(self.applied_migrations.clone())
}

async fn lock(
Expand Down Expand Up @@ -112,7 +131,7 @@ macro_rules! migration {
};
}

async fn generate_plan(
async fn generate_apply_all_plan(
migrator: &mut CustomMigrator,
migration_list: Vec<Box<dyn Migration<Sqlite>>>,
) -> Result<Vec<&Box<dyn Migration<Sqlite>>>, Error> {
Expand All @@ -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<dyn Migration<Sqlite>>)));
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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());
}

Expand All @@ -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());
}

Expand All @@ -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());
}

Expand All @@ -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());
}

Expand All @@ -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());
}

Expand All @@ -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());
}

Expand All @@ -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());
}

Expand All @@ -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());
}

Expand All @@ -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);
}

0 comments on commit e881cc2

Please sign in to comment.