database_migration/action/
mod.rs

1use crate::migration::{
2    ApplicableMigration, Execution, Problem, ProblematicMigration, ScriptContent,
3};
4use chrono::NaiveDateTime;
5use indexmap::IndexMap;
6use std::marker::PhantomData;
7
8pub trait ListOutOfOrder {
9    fn list_out_of_order(
10        &self,
11        defined_migrations: &[ScriptContent],
12        executed_migrations: &IndexMap<NaiveDateTime, Execution>,
13    ) -> Vec<ProblematicMigration>;
14}
15
16pub trait ListChangedAfterExecution {
17    fn list_changed_after_execution(
18        &self,
19        defined_migrations: &[ScriptContent],
20        executed_migrations: &IndexMap<NaiveDateTime, Execution>,
21    ) -> Vec<ProblematicMigration>;
22}
23
24#[must_use]
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub struct Verify {
27    ignore_checksums: bool,
28    ignore_order: bool,
29}
30
31#[allow(clippy::derivable_impls)]
32impl Default for Verify {
33    fn default() -> Self {
34        Self {
35            ignore_checksums: false,
36            ignore_order: false,
37        }
38    }
39}
40
41impl Verify {
42    pub const fn with_ignore_checksums(mut self, ignore_checksums: bool) -> Self {
43        self.ignore_checksums = ignore_checksums;
44        self
45    }
46
47    pub const fn ignore_checksums(&self) -> bool {
48        self.ignore_checksums
49    }
50
51    pub const fn with_ignore_order(mut self, ignore_order: bool) -> Self {
52        self.ignore_order = ignore_order;
53        self
54    }
55
56    pub const fn ignore_order(&self) -> bool {
57        self.ignore_order
58    }
59}
60
61impl ListOutOfOrder for Verify {
62    fn list_out_of_order(
63        &self,
64        defined_migrations: &[ScriptContent],
65        executed_migrations: &IndexMap<NaiveDateTime, Execution>,
66    ) -> Vec<ProblematicMigration> {
67        if self.ignore_order {
68            return Vec::new();
69        }
70        if let Some(&last_applied_key) = executed_migrations.keys().max_by_key(|key| **key) {
71            defined_migrations
72                .iter()
73                .filter_map(|mig| {
74                    if last_applied_key > mig.key && !executed_migrations.contains_key(&mig.key) {
75                        Some(ProblematicMigration {
76                            key: mig.key,
77                            kind: mig.kind,
78                            script_path: mig.path.clone(),
79                            problem: Problem::OutOfOrder {
80                                definition_key: mig.key,
81                                last_applied_key,
82                            },
83                        })
84                    } else {
85                        None
86                    }
87                })
88                .collect()
89        } else {
90            Vec::new()
91        }
92    }
93}
94
95impl ListChangedAfterExecution for Verify {
96    fn list_changed_after_execution(
97        &self,
98        defined_migrations: &[ScriptContent],
99        executed_migrations: &IndexMap<NaiveDateTime, Execution>,
100    ) -> Vec<ProblematicMigration> {
101        if self.ignore_checksums {
102            return Vec::new();
103        }
104        defined_migrations
105            .iter()
106            .filter_map(|mig| {
107                if mig.kind.is_forward() {
108                    executed_migrations.get(&mig.key).and_then(|exec| {
109                        if exec.checksum != mig.checksum {
110                            Some(ProblematicMigration {
111                                key: mig.key,
112                                kind: mig.kind,
113                                script_path: mig.path.clone(),
114                                problem: Problem::ChecksumMismatch {
115                                    definition_checksum: mig.checksum,
116                                    execution_checksum: exec.checksum,
117                                },
118                            })
119                        } else {
120                            None
121                        }
122                    })
123                } else {
124                    None
125                }
126            })
127            .collect()
128    }
129}
130
131pub trait MigrationsToApply {
132    fn list_migrations_to_apply(
133        &self,
134        defined_migrations: &[ScriptContent],
135        executed_migrations: &IndexMap<NaiveDateTime, Execution>,
136    ) -> IndexMap<NaiveDateTime, ApplicableMigration>;
137}
138
139#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
140pub struct Migrate {
141    _seal: PhantomData<()>,
142}
143
144impl MigrationsToApply for Migrate {
145    fn list_migrations_to_apply(
146        &self,
147        defined_migrations: &[ScriptContent],
148        executed_migrations: &IndexMap<NaiveDateTime, Execution>,
149    ) -> IndexMap<NaiveDateTime, ApplicableMigration> {
150        defined_migrations
151            .iter()
152            .filter(|mig| mig.kind.is_forward() && !executed_migrations.contains_key(&mig.key))
153            .map(to_applicable_migration)
154            .collect()
155    }
156}
157
158#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
159pub struct Revert {
160    _seal: PhantomData<()>,
161}
162
163impl MigrationsToApply for Revert {
164    fn list_migrations_to_apply(
165        &self,
166        defined_migrations: &[ScriptContent],
167        executed_migrations: &IndexMap<NaiveDateTime, Execution>,
168    ) -> IndexMap<NaiveDateTime, ApplicableMigration> {
169        defined_migrations
170            .iter()
171            .filter(|mig| mig.kind.is_backward() && executed_migrations.contains_key(&mig.key))
172            .map(to_applicable_migration)
173            .collect()
174    }
175}
176
177fn to_applicable_migration(mig: &ScriptContent) -> (NaiveDateTime, ApplicableMigration) {
178    (
179        mig.key,
180        ApplicableMigration {
181            key: mig.key,
182            kind: mig.kind,
183            script_content: mig.content.clone(),
184            checksum: mig.checksum,
185        },
186    )
187}
188
189#[cfg(test)]
190mod tests;