1use crate::error::{DatabaseError as _, TernResult};
16
17use chrono::{DateTime, Utc};
18use futures_core::{Future, future::BoxFuture};
19use std::time::Instant;
20
21pub use crate::query::Query;
22
23pub trait MigrationContext
25where
26 Self: MigrationSource<Ctx = Self> + Send + Sync + 'static,
27{
28 const HISTORY_TABLE: &str;
34
35 type Exec: Executor;
37
38 fn executor(&mut self) -> &mut Self::Exec;
40
41 fn apply<'migration, 'conn: 'migration, M>(
45 &'conn mut self,
46 migration: &'migration M,
47 ) -> BoxFuture<'migration, TernResult<AppliedMigration>>
48 where
49 M: Migration<Ctx = Self> + Send + Sync + ?Sized,
50 {
51 Box::pin(async move {
52 let start = Instant::now();
53 let query = M::build(migration, self).await?;
54 let executor = self.executor();
55
56 if migration.no_tx() {
57 executor
58 .apply_no_tx(&query)
59 .await
60 .void_tern_migration_result(migration)?;
61 } else {
62 executor
63 .apply_tx(&query)
64 .await
65 .void_tern_migration_result(migration)?;
66 }
67
68 let applied_at = Utc::now();
69 let duration_ms = start.elapsed().as_millis() as i64;
70 let applied =
71 migration.to_applied(duration_ms, applied_at, query.sql());
72 executor
73 .insert_applied_migration(Self::HISTORY_TABLE, &applied)
74 .await?;
75
76 Ok(applied)
77 })
78 }
79
80 fn latest_version(&mut self) -> BoxFuture<'_, TernResult<Option<i64>>> {
82 Box::pin(async move {
83 let latest = self
84 .executor()
85 .get_all_applied(Self::HISTORY_TABLE)
86 .await?
87 .into_iter()
88 .fold(None, |acc, m| match acc {
89 None => Some(m.version),
90 Some(v) if m.version > v => Some(m.version),
91 _ => acc,
92 });
93
94 Ok(latest)
95 })
96 }
97
98 fn previously_applied(
100 &mut self,
101 ) -> BoxFuture<'_, TernResult<Vec<AppliedMigration>>> {
102 Box::pin(self.executor().get_all_applied(Self::HISTORY_TABLE))
103 }
104
105 fn check_history_table(&mut self) -> BoxFuture<'_, TernResult<()>> {
107 Box::pin(
108 self.executor().create_history_if_not_exists(Self::HISTORY_TABLE),
109 )
110 }
111
112 fn drop_history_table(&mut self) -> BoxFuture<'_, TernResult<()>> {
114 Box::pin(self.executor().drop_history(Self::HISTORY_TABLE))
115 }
116
117 fn insert_applied<'migration, 'conn: 'migration>(
119 &'conn mut self,
120 applied: &'migration AppliedMigration,
121 ) -> BoxFuture<'migration, TernResult<()>> {
122 Box::pin(
123 self.executor()
124 .insert_applied_migration(Self::HISTORY_TABLE, applied),
125 )
126 }
127
128 fn upsert_applied<'migration, 'conn: 'migration>(
130 &'conn mut self,
131 applied: &'migration AppliedMigration,
132 ) -> BoxFuture<'migration, TernResult<()>> {
133 Box::pin(
134 self.executor()
135 .upsert_applied_migration(Self::HISTORY_TABLE, applied),
136 )
137 }
138}
139
140pub trait Executor
143where
144 Self: Send + Sync + 'static,
145{
146 type Queries: QueryRepository;
149
150 fn apply_tx(
152 &mut self,
153 query: &Query,
154 ) -> impl Future<Output = TernResult<()>> + Send;
155
156 fn apply_no_tx(
158 &mut self,
159 query: &Query,
160 ) -> impl Future<Output = TernResult<()>> + Send;
161
162 fn create_history_if_not_exists(
164 &mut self,
165 history_table: &str,
166 ) -> impl Future<Output = TernResult<()>> + Send;
167
168 fn drop_history(
170 &mut self,
171 history_table: &str,
172 ) -> impl Future<Output = TernResult<()>> + Send;
173
174 fn get_all_applied(
176 &mut self,
177 history_table: &str,
178 ) -> impl Future<Output = TernResult<Vec<AppliedMigration>>> + Send;
179
180 fn insert_applied_migration(
182 &mut self,
183 history_table: &str,
184 applied: &AppliedMigration,
185 ) -> impl Future<Output = TernResult<()>> + Send;
186
187 fn upsert_applied_migration(
189 &mut self,
190 history_table: &str,
191 applied: &AppliedMigration,
192 ) -> impl Future<Output = TernResult<()>> + Send;
193}
194
195pub trait QueryRepository {
198 fn create_history_if_not_exists_query(history_table: &str) -> Query;
201
202 fn drop_history_query(history_table: &str) -> Query;
204
205 fn insert_into_history_query(
207 history_table: &str,
208 applied: &AppliedMigration,
209 ) -> Query;
210
211 fn select_star_from_history_query(history_table: &str) -> Query;
213
214 fn upsert_history_query(
216 history_table: &str,
217 applied: &AppliedMigration,
218 ) -> Query;
219}
220
221pub trait Migration
223where
224 Self: Send + Sync,
225{
226 type Ctx: MigrationContext;
228
229 fn migration_id(&self) -> MigrationId;
231
232 fn content(&self) -> String;
235
236 fn no_tx(&self) -> bool;
238
239 fn build<'a>(
241 &'a self,
242 ctx: &'a mut Self::Ctx,
243 ) -> BoxFuture<'a, TernResult<Query>>;
244
245 fn version(&self) -> i64 {
247 self.migration_id().version()
248 }
249
250 fn to_applied(
253 &self,
254 duration_ms: i64,
255 applied_at: DateTime<Utc>,
256 content: &str,
257 ) -> AppliedMigration {
258 AppliedMigration::new(
259 self.migration_id(),
260 content,
261 duration_ms,
262 applied_at,
263 )
264 }
265}
266
267pub trait MigrationSource {
270 type Ctx: MigrationContext;
273
274 fn migration_set(
276 &self,
277 last_applied: Option<i64>,
278 ) -> MigrationSet<Self::Ctx>;
279}
280
281pub struct MigrationSet<Ctx: ?Sized> {
284 pub migrations: Vec<Box<dyn Migration<Ctx = Ctx>>>,
285}
286
287impl<Ctx> MigrationSet<Ctx>
288where
289 Ctx: MigrationContext,
290{
291 pub fn new<T>(vs: T) -> MigrationSet<Ctx>
292 where
293 T: Into<Vec<Box<dyn Migration<Ctx = Ctx>>>>,
294 {
295 let mut migrations = vs.into();
296 migrations.sort_by_key(|m| m.version());
297 MigrationSet { migrations }
298 }
299
300 pub fn len(&self) -> usize {
302 self.migrations.len()
303 }
304
305 pub fn versions(&self) -> Vec<i64> {
307 self.migrations.iter().map(|m| m.version()).collect::<Vec<_>>()
308 }
309
310 pub fn migration_ids(&self) -> Vec<MigrationId> {
312 self.migrations.iter().map(|m| m.migration_id()).collect::<Vec<_>>()
313 }
314
315 pub fn max(&self) -> Option<i64> {
317 self.versions().iter().max().copied()
318 }
319
320 pub fn is_empty(&self) -> bool {
322 self.len() == 0
323 }
324}
325
326pub trait QueryBuilder {
332 type Ctx: MigrationContext;
334
335 fn build(
337 &self,
338 ctx: &mut Self::Ctx,
339 ) -> impl Future<Output = TernResult<Query>> + Send;
340}
341
342#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
344pub struct MigrationId {
345 version: i64,
347 description: String,
349}
350
351impl MigrationId {
352 pub fn new(version: i64, description: String) -> Self {
353 Self { version, description }
354 }
355
356 pub fn version(&self) -> i64 {
357 self.version
358 }
359
360 pub fn description(&self) -> String {
361 self.description.clone()
362 }
363}
364
365impl std::fmt::Display for MigrationId {
366 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
367 write!(f, "V{}__{}", self.version, self.description)
368 }
369}
370
371impl From<AppliedMigration> for MigrationId {
372 fn from(value: AppliedMigration) -> Self {
373 Self { version: value.version, description: value.description }
374 }
375}
376
377#[derive(Debug, Clone)]
380#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
381pub struct AppliedMigration {
382 pub version: i64,
384 pub description: String,
386 pub content: String,
388 pub duration_ms: i64,
390 pub applied_at: DateTime<Utc>,
392}
393
394impl AppliedMigration {
395 pub fn new(
396 id: MigrationId,
397 content: &str,
398 duration_ms: i64,
399 applied_at: DateTime<Utc>,
400 ) -> Self {
401 Self {
402 version: id.version,
403 description: id.description,
404 content: content.into(),
405 duration_ms,
406 applied_at,
407 }
408 }
409}