Skip to main content

tern_core/
migration.rs

1//! This module contains types and traits related to the migration files.
2//!
3//! * [`Migration`] is the abstract representation of what is built from a
4//!   migration file.
5//! * [`QueryBuilder`] is the recipe for building the query for a migration.
6//! * [`MigrationSource`] is the ability to produce the set of migrations, a
7//!   [`MigrationSet`], for a particular context in order to be ran in that
8//!   context.
9//! * [`MigrationContext`] is the core type.  It has an associated [`Executor`]
10//!   and it can produce the migrations from source.  Combined, it has the full
11//!   functionality of the migration tool.
12//!
13//! Generally these shouldn't be implemented; use the corresponding derive macro
14//! instead.
15use 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
23/// The context in which a migration run occurs.
24pub trait MigrationContext
25where
26    Self: MigrationSource<Ctx = Self> + Send + Sync + 'static,
27{
28    /// The name of the table in the database that tracks the history of this
29    /// migration set.
30    ///
31    /// It defaults to `_tern_migrations` in the default schema for the
32    /// database driver if using the derive macro for this trait.
33    const HISTORY_TABLE: &str;
34
35    /// The type for executing queries in a migration run.
36    type Exec: Executor;
37
38    /// A reference to the underlying `Executor`.
39    fn executor(&mut self) -> &mut Self::Exec;
40
41    /// For a migration that is capable of building its query in this migration
42    /// context, this builds the query, applies the migration, then updates the
43    /// schema history table after.
44    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    /// Gets the version of the most recently applied migration.
81    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    /// Get all previously applied migrations.
99    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    /// Check that the history table exists and create it if not.
106    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    /// Drop the history table if requested.
113    fn drop_history_table(&mut self) -> BoxFuture<'_, TernResult<()>> {
114        Box::pin(self.executor().drop_history(Self::HISTORY_TABLE))
115    }
116
117    /// Insert an applied migration.
118    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    /// Upsert applied migrations.
129    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
140/// The "executor" type for the database backend ultimately responsible for
141/// issuing migration and schema history queries.
142pub trait Executor
143where
144    Self: Send + Sync + 'static,
145{
146    /// The type of value that can produce queries for the history table of this
147    /// migration set.
148    type Queries: QueryRepository;
149
150    /// Apply the `Query` for the migration in a transaction.
151    fn apply_tx(
152        &mut self,
153        query: &Query,
154    ) -> impl Future<Output = TernResult<()>> + Send;
155
156    /// Apply the `Query` for the migration _not_ in a transaction.
157    fn apply_no_tx(
158        &mut self,
159        query: &Query,
160    ) -> impl Future<Output = TernResult<()>> + Send;
161
162    /// `CREATE IF NOT EXISTS` the history table.
163    fn create_history_if_not_exists(
164        &mut self,
165        history_table: &str,
166    ) -> impl Future<Output = TernResult<()>> + Send;
167
168    /// `DROP` the history table.
169    fn drop_history(
170        &mut self,
171        history_table: &str,
172    ) -> impl Future<Output = TernResult<()>> + Send;
173
174    /// Get the complete history of applied migrations.
175    fn get_all_applied(
176        &mut self,
177        history_table: &str,
178    ) -> impl Future<Output = TernResult<Vec<AppliedMigration>>> + Send;
179
180    /// Insert an applied migration into the history table.
181    fn insert_applied_migration(
182        &mut self,
183        history_table: &str,
184        applied: &AppliedMigration,
185    ) -> impl Future<Output = TernResult<()>> + Send;
186
187    /// Update or insert an applied migration.
188    fn upsert_applied_migration(
189        &mut self,
190        history_table: &str,
191        applied: &AppliedMigration,
192    ) -> impl Future<Output = TernResult<()>> + Send;
193}
194
195/// A type that has a library of "administrative" queries that are needed during
196/// a migration run.
197pub trait QueryRepository {
198    /// The query that creates the schema history table or does nothing if it
199    /// already exists.
200    fn create_history_if_not_exists_query(history_table: &str) -> Query;
201
202    /// The query that drops the history table if requested.
203    fn drop_history_query(history_table: &str) -> Query;
204
205    /// The query to update the schema history table with an applied migration.
206    fn insert_into_history_query(
207        history_table: &str,
208        applied: &AppliedMigration,
209    ) -> Query;
210
211    /// The query to return all rows from the schema history table.
212    fn select_star_from_history_query(history_table: &str) -> Query;
213
214    /// Query to insert or update a record in the history table.
215    fn upsert_history_query(
216        history_table: &str,
217        applied: &AppliedMigration,
218    ) -> Query;
219}
220
221/// A single migration in a migration set.
222pub trait Migration
223where
224    Self: Send + Sync,
225{
226    /// A migration context that is sufficient to build this migration.
227    type Ctx: MigrationContext;
228
229    /// Get the `MigrationId` for this migration.
230    fn migration_id(&self) -> MigrationId;
231
232    /// The raw file content of the migration source file, or when stored as an
233    /// applied migration in the history table, it is the query that was ran.
234    fn content(&self) -> String;
235
236    /// Whether this migration should not be applied in a database transaction.
237    fn no_tx(&self) -> bool;
238
239    /// Produce a future resolving to the migration query when `await`ed.
240    fn build<'a>(
241        &'a self,
242        ctx: &'a mut Self::Ctx,
243    ) -> BoxFuture<'a, TernResult<Query>>;
244
245    /// The migration version.
246    fn version(&self) -> i64 {
247        self.migration_id().version()
248    }
249
250    /// Convert this migration to an [`AppliedMigration`] assuming that it was
251    /// successfully applied.
252    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
267/// A type that is used to collect a [`MigrationSet`] -- migrations that are not
268/// applied yet -- which is used as the input to runner commands.
269pub trait MigrationSource {
270    /// A context that the set of migrations returned by `migration_set` would
271    /// need in order to be applied.
272    type Ctx: MigrationContext;
273
274    /// The set of migrations since `last_applied`.
275    fn migration_set(
276        &self,
277        last_applied: Option<i64>,
278    ) -> MigrationSet<Self::Ctx>;
279}
280
281/// The `Migration`s derived from the files in the source directory that need to
282/// be applied.
283pub 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    /// Number of migrations in the set.
301    pub fn len(&self) -> usize {
302        self.migrations.len()
303    }
304
305    /// Versions present in this migration set.
306    pub fn versions(&self) -> Vec<i64> {
307        self.migrations.iter().map(|m| m.version()).collect::<Vec<_>>()
308    }
309
310    /// The version/name of migrations in this migration set.
311    pub fn migration_ids(&self) -> Vec<MigrationId> {
312        self.migrations.iter().map(|m| m.migration_id()).collect::<Vec<_>>()
313    }
314
315    /// The latest version in the set.
316    pub fn max(&self) -> Option<i64> {
317        self.versions().iter().max().copied()
318    }
319
320    /// The set is empty for the requested operation.
321    pub fn is_empty(&self) -> bool {
322        self.len() == 0
323    }
324}
325
326/// A helper trait for [`Migration`].
327///
328/// With the derive macros, the user's responsibility is to implement this for
329/// a Rust migration, and the proc macro uses it to build an implementation of
330/// [`Migration`].
331pub trait QueryBuilder {
332    /// The context for running the migration this query is for.
333    type Ctx: MigrationContext;
334
335    /// Asynchronously produce the migration query.
336    fn build(
337        &self,
338        ctx: &mut Self::Ctx,
339    ) -> impl Future<Output = TernResult<Query>> + Send;
340}
341
342/// Name/version derived from the migration source filename.
343#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
344pub struct MigrationId {
345    /// Version parsed from the migration filename.
346    version: i64,
347    /// Description parsed from the migration filename.
348    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/// An `AppliedMigration` is the information about a migration that completed
378/// successfully and it is also a row in the schema history table.
379#[derive(Debug, Clone)]
380#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
381pub struct AppliedMigration {
382    /// The migration version.
383    pub version: i64,
384    /// The description of the migration.
385    pub description: String,
386    /// The contents of the migration file at the time it was applied.
387    pub content: String,
388    /// How long the migration took to run in milliseconds.
389    pub duration_ms: i64,
390    /// The timestamp of when the migration was applied.
391    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}