1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
use crate::{json_rpc::types::*, CoreError, CoreResult};
use schema_connector::SchemaConnector;
use user_facing_errors::schema_engine::{CannotRollBackSucceededMigration, CannotRollBackUnappliedMigration};

/// Mark a migration as rolled back.
pub async fn mark_migration_rolled_back(
    input: MarkMigrationRolledBackInput,
    connector: &mut dyn SchemaConnector,
) -> CoreResult<MarkMigrationRolledBackOutput> {
    connector.acquire_lock().await?;

    let all_migrations = connector
        .migration_persistence()
        .list_migrations()
        .await?
        .map_err(|_err| {
            CoreError::from_msg(
                "Invariant violation: called markMigrationRolledBack on a database without migrations table.".into(),
            )
        })?;

    let relevant_migrations: Vec<_> = all_migrations
        .into_iter()
        .filter(|migration| migration.migration_name == input.migration_name)
        .collect();

    if relevant_migrations.is_empty() {
        return Err(CoreError::user_facing(CannotRollBackUnappliedMigration {
            migration_name: input.migration_name.clone(),
        }));
    }

    if relevant_migrations
        .iter()
        .all(|migration| migration.finished_at.is_some())
    {
        return Err(CoreError::user_facing(CannotRollBackSucceededMigration {
            migration_name: input.migration_name.clone(),
        }));
    }

    let migrations_to_roll_back = relevant_migrations
        .iter()
        .filter(|migration| migration.finished_at.is_none() && migration.rolled_back_at.is_none());

    for migration in migrations_to_roll_back {
        tracing::info!(
            migration_id = migration.id.as_str(),
            migration_name = migration.migration_name.as_str(),
            "Marking migration as rolled back."
        );
        connector
            .migration_persistence()
            .mark_migration_rolled_back_by_id(&migration.id)
            .await?;
    }

    Ok(MarkMigrationRolledBackOutput {})
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn mark_migration_rolled_back_output_serializes_as_expected() {
        let output = MarkMigrationRolledBackOutput {};

        let expected = serde_json::json!({});
        let actual = serde_json::to_value(output).unwrap();

        assert_eq!(actual, expected);
    }
}