1use crate::db::Db;
2use crate::error::Result;
3use crate::file;
4use crate::migration::{Migration, MigrationWithMeta};
5use std::collections::HashMap;
6use std::fmt::Display;
7use std::path::Path;
8
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum ApplicationState {
11 Pending {
12 definition: Migration,
13 },
14 Applied {
15 definition: Migration,
16 application: MigrationWithMeta,
17 },
18 Changed {
19 definition: Migration,
20 application: MigrationWithMeta,
21 },
22 Removed {
23 application: MigrationWithMeta,
24 },
25}
26
27impl ApplicationState {
28 pub fn is_pending(&self) -> bool {
29 matches!(self, ApplicationState::Pending { .. })
30 }
31
32 pub fn is_applied(&self) -> bool {
33 matches!(self, ApplicationState::Applied { .. })
34 }
35
36 pub fn is_changed(&self) -> bool {
37 matches!(self, ApplicationState::Changed { .. })
38 }
39
40 pub fn is_removed(&self) -> bool {
41 matches!(self, ApplicationState::Removed { .. })
42 }
43
44 pub fn name(&self) -> &str {
45 match self {
46 ApplicationState::Pending { definition } => &definition.name,
47 ApplicationState::Applied {
48 definition,
49 application: _,
50 } => &definition.name,
51 ApplicationState::Changed {
52 definition,
53 application: _,
54 } => &definition.name,
55 ApplicationState::Removed { application } => &application.migration.name,
56 }
57 }
58}
59
60impl Display for ApplicationState {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 match self {
63 ApplicationState::Pending { definition: file } => write!(f, "{} [pending]", file.name),
64 ApplicationState::Applied {
65 definition: file,
66 application: _,
67 } => {
68 write!(f, "{} [applied]", file.name)
69 }
70 ApplicationState::Changed {
71 definition: file,
72 application: _,
73 } => write!(f, "{} ** CHANGED **", file.name),
74 ApplicationState::Removed { application: db } => {
75 write!(f, "{} ** NO FILE **", db.migration.name)
76 }
77 }
78 }
79}
80
81pub fn get_all_migration_state(
82 db: &mut Db,
83 migrate_dir: impl AsRef<Path>,
84) -> Result<Vec<ApplicationState>> {
85 let definitions = file::list(migrate_dir.as_ref())?;
86 let applications = db.list()?;
87 Ok(get_all_migration_state_impl(definitions, applications))
88}
89
90fn get_all_migration_state_impl(
91 definitions: Vec<Migration>,
92 applications: Vec<MigrationWithMeta>,
93) -> Vec<ApplicationState> {
94 let definitions = definitions
95 .into_iter()
96 .map(|m| (m.name.clone(), m))
97 .collect::<HashMap<String, Migration>>();
98 let applications = applications
99 .into_iter()
100 .map(|m| (m.migration.name.clone(), m))
101 .collect::<HashMap<String, MigrationWithMeta>>();
102
103 let mut all_names = definitions
104 .values()
105 .map(|v| v.name.clone())
106 .chain(applications.values().map(|v| v.migration.name.clone()))
107 .collect::<Vec<String>>();
108 all_names.sort();
109 all_names.dedup();
110
111 all_names
112 .iter()
113 .map(|name| {
114 let definition = definitions.get(name);
115 let application = applications.get(name);
116
117 match (definition, application) {
118 (None, Some(application)) => ApplicationState::Removed {
119 application: application.clone(),
120 },
121 (Some(definition), None) => ApplicationState::Pending {
122 definition: definition.clone(),
123 },
124 (Some(definition), Some(application)) => {
125 if application.migration == *definition {
126 ApplicationState::Applied {
127 definition: definition.clone(),
128 application: application.clone(),
129 }
130 } else {
131 ApplicationState::Changed {
132 definition: definition.clone(),
133 application: application.clone(),
134 }
135 }
136 }
137 (None, None) => unreachable!(),
138 }
139 })
140 .collect::<Vec<_>>()
141}
142
143#[cfg(test)]
144mod test {
145 use crate::migration::MigrationMeta;
146 use rand::seq::SliceRandom;
147 use std::time::SystemTime;
148
149 use super::*;
150
151 #[test]
152 fn test_get_all_migration_state_empty() {
153 let result = get_all_migration_state_impl(Vec::new(), Vec::new());
154 assert_eq!(result, Vec::new());
155 }
156
157 #[test]
158 fn test_get_all_migration_state_pending() {
159 let definition = build_migration("migration", "up", "down");
160 let result = get_all_migration_state_impl(vec![definition.clone()], Vec::new());
161 assert_eq!(
162 result,
163 vec![ApplicationState::Pending {
164 definition: definition
165 }]
166 );
167 }
168
169 #[test]
170 fn test_get_all_migration_state_removed() {
171 let application = build_migration_meta("migration", "up", "down");
172 let result = get_all_migration_state_impl(Vec::new(), vec![application.clone()]);
173 assert_eq!(
174 result,
175 vec![ApplicationState::Removed {
176 application: application
177 }]
178 );
179 }
180
181 #[test]
182 fn test_get_all_migration_state_applied() {
183 let definition = build_migration("migration", "up", "down");
184 let application = build_migration_meta("migration", "up", "down");
185 let result =
186 get_all_migration_state_impl(vec![definition.clone()], vec![application.clone()]);
187 assert_eq!(
188 result,
189 vec![ApplicationState::Applied {
190 definition: definition,
191 application: application
192 }]
193 );
194 }
195
196 #[test]
197 fn test_get_all_migration_up_state_changed() {
198 let definition = build_migration("migration", "up-changed", "down");
199 let application = build_migration_meta("migration", "up", "down");
200 let result =
201 get_all_migration_state_impl(vec![definition.clone()], vec![application.clone()]);
202 assert_eq!(
203 result,
204 vec![ApplicationState::Changed {
205 definition: definition,
206 application: application
207 }]
208 );
209 }
210
211 #[test]
212 fn test_get_all_migration_down_state_changed() {
213 let definition = build_migration("migration", "up", "down-changed");
214 let application = build_migration_meta("migration", "up", "down");
215 let result =
216 get_all_migration_state_impl(vec![definition.clone()], vec![application.clone()]);
217 assert_eq!(
218 result,
219 vec![ApplicationState::Changed {
220 definition: definition,
221 application: application
222 }]
223 );
224 }
225
226 #[test]
227 fn test_get_all_migration_state_multiple() {
228 let definition_a = build_migration("1-migration", "1-up", "1-down");
229 let definition_b = build_migration("2-migration", "2-up", "2-down");
230 let definition_c = build_migration("3-migration", "3-up", "3-down");
231
232 let application_a = build_migration_meta("1-migration", "1-up", "1-down");
233 let application_b = build_migration_meta("2-migration", "2-up-changed", "2-down");
234 let application_c = build_migration_meta("4-migration", "4-up", "4-down");
235
236 let mut definitions = vec![
237 definition_a.clone(),
238 definition_b.clone(),
239 definition_c.clone(),
240 ];
241 let mut applications = vec![
242 application_a.clone(),
243 application_b.clone(),
244 application_c.clone(),
245 ];
246
247 let mut rng = rand::thread_rng();
250 definitions.shuffle(&mut rng);
251 applications.shuffle(&mut rng);
252
253 let result = get_all_migration_state_impl(definitions, applications);
254 assert_eq!(
255 result,
256 vec![
257 ApplicationState::Applied {
258 definition: definition_a,
259 application: application_a
260 },
261 ApplicationState::Changed {
262 definition: definition_b,
263 application: application_b
264 },
265 ApplicationState::Pending {
266 definition: definition_c
267 },
268 ApplicationState::Removed {
269 application: application_c
270 },
271 ]
272 );
273 }
274
275 fn build_migration(name: &'static str, up: &'static str, down: &'static str) -> Migration {
276 Migration {
277 up_sql: up.to_string(),
278 down_sql: down.to_string(),
279 name: name.to_string(),
280 }
281 }
282
283 fn build_migration_meta(
284 name: &'static str,
285 up: &'static str,
286 down: &'static str,
287 ) -> MigrationWithMeta {
288 let now = SystemTime::now();
289
290 MigrationWithMeta {
291 meta: MigrationMeta {
292 id: 123,
293 created_at: now,
294 },
295 migration: build_migration(name, up, down),
296 }
297 }
298}