database_migration/action/
mod.rs1use 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;