rootasrole_core/database/
migration.rs

1use std::error::Error;
2
3use log::debug;
4use semver::Version;
5
6use crate::version::PACKAGE_VERSION;
7
8type MigrationFn<T> = fn(&Migration<T>, &mut T) -> Result<(), Box<dyn Error>>;
9
10pub struct Migration<T> {
11    pub from: fn() -> Version,
12    pub to: fn() -> Version,
13    pub up: MigrationFn<T>,
14    pub down: MigrationFn<T>,
15}
16
17#[derive(PartialEq, Eq, Debug)]
18pub enum ChangeResult {
19    UpgradeDirect,
20    DowngradeDirect,
21    UpgradeIndirect,
22    DowngradeIndirect,
23    None,
24}
25
26impl<T> Migration<T> {
27    pub fn from(&self) -> Version {
28        (self.from)()
29    }
30    pub fn to(&self) -> Version {
31        (self.to)()
32    }
33    pub fn change(
34        &self,
35        doc: &mut T,
36        from: &Version,
37        to: &Version,
38    ) -> Result<ChangeResult, Box<dyn Error>> {
39        debug!("Checking migration from {} to {} :", self.from(), self.to());
40        debug!(
41            "
42\tself.from() == *from -> {}\tself.from() == *to -> {}
43\tself.to() == *to -> {}\tself.to() == *from -> {}
44\t*from < *to -> {}\tself.to() < *to -> {}\tself.to() > *from -> {}
45\t*from > *to -> {}\tself.from() < *to -> {}\tself.from() > *from -> {}",
46            self.from() == *from,
47            self.to() == *from,
48            self.to() == *to,
49            self.to() == *from,
50            *from < *to,
51            self.to() < *to,
52            self.to() > *from,
53            *from > *to,
54            self.from() < *to,
55            self.from() > *from
56        );
57        if self.from() == *from && self.to() == *to {
58            debug!("Direct Upgrading from {} to {}", self.from(), self.to());
59            (self.up)(self, doc)?;
60            Ok(ChangeResult::UpgradeDirect)
61        } else if self.to() == *from && self.from() == *to {
62            debug!("Direct Downgrading from {} to {}", self.to(), self.from());
63            (self.down)(self, doc)?;
64            Ok(ChangeResult::DowngradeDirect)
65        } else if *from < *to && self.from() == *from && self.to() < *to && self.to() > *from {
66            debug!("Step Upgrading from {} to {}", self.from(), self.to());
67            // 1.0.0 -> 2.0.0 -> 3.0.0
68            (self.up)(self, doc)?;
69            Ok(ChangeResult::UpgradeIndirect)
70        } else if *from > *to && self.to() == *from && self.from() > *to && self.from() < *from {
71            debug!("Step Downgrading from {} to {}", self.to(), self.from());
72            // 3.0.0 -> 2.0.0 -> 1.0.0
73            (self.down)(self, doc)?;
74            Ok(ChangeResult::DowngradeIndirect)
75        } else {
76            Ok(ChangeResult::None)
77        }
78    }
79
80    pub fn migrate_from(
81        from: &Version,
82        to: &Version,
83        doc: &mut T,
84        migrations: &[Self],
85    ) -> Result<bool, Box<dyn Error>> {
86        let mut from = from.clone();
87        let to = to.clone();
88        debug!("===== Migrating from {} to {} =====", from, to);
89        if from != to {
90            let mut migrated = ChangeResult::UpgradeIndirect;
91            while migrated == ChangeResult::UpgradeIndirect
92                || migrated == ChangeResult::DowngradeIndirect
93            {
94                migrated = ChangeResult::None;
95                for migration in migrations {
96                    match migration.change(doc, &from, &to)? {
97                        ChangeResult::UpgradeDirect | ChangeResult::DowngradeDirect => {
98                            return Ok(true);
99                        }
100                        ChangeResult::UpgradeIndirect => {
101                            from = migration.to();
102                            migrated = ChangeResult::UpgradeIndirect;
103                            break;
104                        }
105                        ChangeResult::DowngradeIndirect => {
106                            from = migration.from();
107                            migrated = ChangeResult::DowngradeIndirect;
108                            break;
109                        }
110                        ChangeResult::None => {
111                            migrated = ChangeResult::None;
112                        }
113                    }
114                }
115                if migrated == ChangeResult::None {
116                    return Err(format!("No migration from {} to {} found", from, to).into());
117                }
118            }
119        }
120        Ok(false)
121    }
122
123    /// Migrate the database schema to the current version.
124    /// If the version is already the current version, nothing is done.
125    /// If the version is older, the database is upgraded.
126    /// If the version is newer, the database is downgraded.
127    /// Returns true if the database was migrated, false if it was already at the current version.
128    pub fn migrate(
129        version: &Version,
130        doc: &mut T,
131        migrations: &[Self],
132    ) -> Result<bool, Box<dyn Error>>
133where {
134        Self::migrate_from(
135            version,
136            &Version::parse(PACKAGE_VERSION).unwrap(),
137            doc,
138            migrations,
139        )
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146    use semver::Version;
147
148    #[test]
149    fn test_migration() {
150        let mut doc = 0;
151        let migrations = vec![
152            Migration {
153                from: || Version::parse("1.0.0").unwrap(),
154                to: || Version::parse("2.0.0").unwrap(),
155                up: |_, doc| {
156                    *doc += 1;
157                    Ok(())
158                },
159                down: |_, doc| {
160                    *doc -= 1;
161                    Ok(())
162                },
163            },
164            Migration {
165                from: || Version::parse("2.0.0").unwrap(),
166                to: || Version::parse("3.0.0-alpha.1").unwrap(),
167                up: |_, doc| {
168                    *doc += 1;
169                    Ok(())
170                },
171                down: |_, doc| {
172                    *doc -= 1;
173                    Ok(())
174                },
175            },
176            Migration {
177                from: || Version::parse("3.0.0-alpha.1").unwrap(),
178                to: || Version::parse(PACKAGE_VERSION).unwrap(),
179                up: |_, doc| {
180                    *doc += 1;
181                    Ok(())
182                },
183                down: |_, doc| {
184                    *doc -= 1;
185                    Ok(())
186                },
187            },
188            Migration {
189                from: || Version::parse(PACKAGE_VERSION).unwrap(),
190                to: || Version::parse("4.0.0").unwrap(),
191                up: |_, doc| {
192                    *doc += 1;
193                    Ok(())
194                },
195                down: |_, doc| {
196                    *doc -= 1;
197                    Ok(())
198                },
199            },
200        ];
201        assert_eq!(
202            Migration::migrate(&Version::parse("1.0.0").unwrap(), &mut doc, &migrations).unwrap(),
203            true
204        );
205        assert_eq!(doc, 3);
206        doc = 0;
207        assert_eq!(
208            Migration::migrate(&Version::parse("2.0.0").unwrap(), &mut doc, &migrations).unwrap(),
209            true
210        );
211        assert_eq!(doc, 2);
212        doc = 0;
213        assert_eq!(
214            Migration::migrate(
215                &Version::parse("3.0.0-alpha.1").unwrap(),
216                &mut doc,
217                &migrations
218            )
219            .unwrap(),
220            true
221        );
222        assert_eq!(doc, 1);
223        doc = 0;
224        assert_eq!(
225            Migration::migrate(&Version::parse("4.0.0").unwrap(), &mut doc, &migrations).unwrap(),
226            true
227        );
228        assert_eq!(doc, -1);
229        doc = 0;
230        assert_eq!(
231            Migration::migrate(
232                &Version::parse(PACKAGE_VERSION).unwrap(),
233                &mut doc,
234                &migrations
235            )
236            .unwrap(),
237            false
238        );
239        assert_eq!(doc, 0);
240    }
241}