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}