geekorm_core/migrations/
mod.rs1pub mod validate;
6
7use crate::backends::TableInfo;
8use crate::builder::models::QueryType;
9use crate::error::MigrationError;
10use crate::{Database, GeekConnection, Query, Table, Values};
11
12use self::validate::Validator;
13
14#[derive(Debug)]
18pub enum MigrationState {
19    Initialized,
21    UpToDate,
23    OutOfDate(String),
25}
26
27pub(crate) type DatabaseTables = Vec<(String, Vec<TableInfo>)>;
28
29pub trait Migration
31where
32    Self: Sync + Send,
33{
34    fn version() -> &'static str
36    where
37        Self: Sized;
38    fn create_query() -> &'static str
40    where
41        Self: Sized;
42    fn upgrade_query() -> &'static str
44    where
45        Self: Sized,
46    {
47        ""
48    }
49    fn rollback_query() -> &'static str
51    where
52        Self: Sized,
53    {
54        ""
55    }
56
57    fn previous() -> Option<Box<dyn Migration>>
59    where
60        Self: Sized,
61    {
62        None
63    }
64
65    fn database(&self) -> &Database;
67
68    #[allow(async_fn_in_trait, unused_variables)]
71    async fn validate_database<'a, C>(
72        &self,
73        connection: &'a C,
74        database: &Database,
75    ) -> Result<MigrationState, crate::Error>
76    where
77        Self: Sized,
78        C: GeekConnection<Connection = C> + 'a,
79    {
80        let database_tables = C::table_names(connection).await?;
82
83        if database_tables.is_empty() {
85            return Ok(MigrationState::Initialized);
86        }
87
88        let mut database_table_columns: DatabaseTables = Vec::new();
89        for table in database_tables {
90            let dbcolumns = C::pragma_info(connection, table.as_str()).await?;
91            database_table_columns.push((table, dbcolumns));
92        }
93
94        let mut migrations: Vec<Box<dyn Migration>> = Vec::new();
95        #[cfg(feature = "log")]
96        {
97            log::debug!("Validating database schema");
98        }
99
100        let state = Self::validate(&mut migrations, database, &database_table_columns)?;
101
102        for migration in migrations {
103            #[cfg(feature = "log")]
104            {
105                let v = Self::version();
106                log::info!("Upgrading database to version {}", v);
107            }
108            Self::upgrade(connection).await?;
109        }
110        if matches!(state, MigrationState::OutOfDate(_)) {
111            #[cfg(feature = "log")]
112            {
113                log::info!("Upgrading database to version {}", Self::version());
114            }
115            Self::upgrade(connection).await?;
116        }
117
118        Ok(MigrationState::UpToDate)
119    }
120
121    #[allow(unused_variables)]
123    fn validate(
124        migrations: &mut Vec<Box<dyn Migration>>,
125        migration_database: &Database,
126        live_database: &DatabaseTables,
127    ) -> Result<MigrationState, crate::Error>
128    where
129        Self: Sized,
130    {
131        let mut validator = Validator {
132            errors: Vec::new(),
133            quick: true,
134        };
135        let result =
136            validate::validate_database(live_database, migration_database, &mut validator)?;
137
138        match result {
139            MigrationState::OutOfDate(reason) => {
140                #[cfg(feature = "log")]
141                {
142                    log::info!("Database is out of date: {}", reason);
143                }
144                if let Some(prev) = Self::previous() {
145                    migrations.push(prev);
146                }
147
148                Ok(MigrationState::OutOfDate(reason))
149            }
150            _ => Ok(MigrationState::UpToDate),
151        }
152    }
153
154    #[allow(async_fn_in_trait)]
158    async fn create<'a, C>(connection: &'a C) -> Result<(), crate::Error>
159    where
160        Self: Sized,
161        C: GeekConnection<Connection = C> + 'a,
162    {
163        let query = Self::create_query().to_string();
164
165        C::batch(
166            connection,
167            Query::new(
168                QueryType::Create,
169                query,
170                Values::new(),
171                Values::new(),
172                Vec::new(),
173                Table::default(),
174            ),
175        )
176        .await
177    }
178
179    #[allow(async_fn_in_trait)]
181    async fn upgrade<'a, C>(connection: &'a C) -> Result<(), crate::Error>
182    where
183        Self: Sized,
184        C: GeekConnection<Connection = C> + 'a,
185    {
186        let query = Self::upgrade_query().to_string();
187        if query.is_empty() {
188            #[cfg(feature = "log")]
189            {
190                log::warn!("No upgrade query found");
191            }
192            return Err(crate::Error::MigrationError(MigrationError::UpgradeError(
193                "No upgrade is avalible".to_string(),
194            )));
195        }
196        #[cfg(feature = "log")]
197        {
198            log::debug!("Executing upgrade query: {}", query);
199        }
200        C::batch(
201            connection,
202            Query::new(
203                QueryType::Update,
204                query,
205                Values::new(),
206                Values::new(),
207                Vec::new(),
208                Table::default(),
209            ),
210        )
211        .await
212    }
213
214    #[allow(async_fn_in_trait)]
216    async fn rollback<'a, C>(connection: &'a C) -> Result<(), crate::Error>
217    where
218        Self: Sized,
219        C: GeekConnection<Connection = C> + 'a,
220    {
221        let query = Self::rollback_query().to_string();
222        if query.is_empty() {
223            #[cfg(feature = "log")]
224            {
225                log::debug!("No rollback query found");
226            }
227            return Ok(());
228        }
229        #[cfg(feature = "log")]
230        {
231            log::debug!("Executing rollback query: {}", query);
232        }
233        C::execute(
234            connection,
235            Query::new(
236                QueryType::Update,
237                query,
238                Values::new(),
239                Values::new(),
240                Vec::new(),
241                Table::default(),
242            ),
243        )
244        .await
245    }
246
247    #[allow(async_fn_in_trait, unused_variables)]
249    async fn migrate<'a, C>(connection: &'a C) -> Result<(), crate::Error>
250    where
251        Self: Sized,
252        C: GeekConnection<Connection = C> + 'a,
253    {
254        Ok(())
255    }
256}