Skip to main content

sofie_db/migrate/
status.rs

1use sofie_core::Result;
2
3use super::{Applied, Migrations, Schema, applied_migrations, checksum, name_order};
4use crate::{Engine, Step};
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub enum MigrationState {
8    Applied,
9    Pending,
10    EditedAfterApplied,
11    MissingFile,
12}
13
14#[derive(Clone, Debug, PartialEq, Eq)]
15pub struct MigrationStatus {
16    pub name: String,
17    pub state: MigrationState,
18}
19
20#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct ModuleStatus {
22    pub module: String,
23    pub migrations: Vec<MigrationStatus>,
24}
25
26impl ModuleStatus {
27    #[must_use]
28    pub fn is_consistent(&self) -> bool {
29        self.migrations.iter().all(|migration| {
30            matches!(
31                migration.state,
32                MigrationState::Applied | MigrationState::Pending
33            )
34        })
35    }
36}
37
38impl Schema<'_> {
39    /// A module whose files are all gone still shows through its records.
40    pub async fn migration_status(self, modules: &[Migrations]) -> Result<Vec<ModuleStatus>> {
41        let dialect = self.db.driver().dialect();
42        self.db
43            .transaction(vec![Step::Script(dialect.migrations_setup().to_owned())])
44            .await?;
45        let mut applied = applied_migrations(self.db).await?;
46        let framework = dialect.framework_migrations();
47        let mut statuses = std::iter::once(&framework)
48            .chain(modules)
49            .map(|migrations| module_status(migrations, dialect.engine(), &mut applied))
50            .collect::<Result<Vec<ModuleStatus>>>()?;
51        statuses.extend(unknown_modules(applied));
52        Ok(statuses)
53    }
54}
55
56fn module_status(
57    migrations: &Migrations,
58    engine: Engine,
59    applied: &mut Applied,
60) -> Result<ModuleStatus> {
61    let module = migrations.module.to_owned();
62    let mut statuses: Vec<MigrationStatus> = migrations
63        .in_name_order(engine)?
64        .into_iter()
65        .map(|(name, sql)| {
66            let recorded = applied.remove(&(module.clone(), name.to_owned()));
67            let state = match recorded {
68                None => MigrationState::Pending,
69                Some(sum) if sum == checksum(&sql) => MigrationState::Applied,
70                Some(_) => MigrationState::EditedAfterApplied,
71            };
72            MigrationStatus {
73                name: name.to_owned(),
74                state,
75            }
76        })
77        .collect();
78    let missing = applied.extract_if(|(recorded_module, _), _| *recorded_module == module);
79    statuses.extend(missing.map(|((_, name), _)| MigrationStatus {
80        name,
81        state: MigrationState::MissingFile,
82    }));
83    statuses.sort_by(|first, second| name_order(&first.name, &second.name));
84    Ok(ModuleStatus {
85        module,
86        migrations: statuses,
87    })
88}
89
90fn unknown_modules(applied: Applied) -> Vec<ModuleStatus> {
91    let mut records: Vec<(String, String)> = applied.into_keys().collect();
92    records.sort();
93    let mut statuses: Vec<ModuleStatus> = Vec::new();
94    for (module, name) in records {
95        let missing = MigrationStatus {
96            name,
97            state: MigrationState::MissingFile,
98        };
99        match statuses.last_mut() {
100            Some(last) if last.module == module => last.migrations.push(missing),
101            _ => statuses.push(ModuleStatus {
102                module,
103                migrations: vec![missing],
104            }),
105        }
106    }
107    statuses
108}