Skip to main content

oxisql_core/
migrator.rs

1//! [`Migrator`] trait — abstract interface for applying and rolling back
2//! database schema migrations.
3
4use std::collections::HashSet;
5
6use async_trait::async_trait;
7
8use crate::OxiSqlError;
9
10/// The execution status of a single migration version.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum MigrationStatus {
13    /// The migration has been applied to the target database.
14    Applied,
15    /// The migration has not yet been applied.
16    Pending,
17    /// The migration status could not be determined (e.g. checksum mismatch).
18    Unknown,
19}
20
21/// Metadata describing a single migration version.
22#[derive(Debug, Clone)]
23pub struct MigrationInfo {
24    /// The numeric version identifier (e.g. timestamp or sequential number).
25    pub version: u64,
26    /// A human-readable name for the migration.
27    pub name: String,
28    /// The current status of this migration.
29    pub status: MigrationStatus,
30    /// ISO-8601 timestamp at which the migration was applied, if known.
31    pub applied_at: Option<String>,
32    /// Checksum of the migration SQL, if computed.
33    pub checksum: Option<String>,
34}
35
36/// An async trait for managing database schema migrations.
37///
38/// Implementors are expected to be `Send + Sync` so they can be used safely
39/// across async task boundaries.
40///
41/// # Default implementation
42///
43/// [`Migrator::pending`] has a default body that calls [`Migrator::status`]
44/// and returns any version from `all_versions` that is not yet
45/// [`MigrationStatus::Applied`].  Backends may override this with a more
46/// efficient query.
47#[async_trait]
48pub trait Migrator: Send + Sync {
49    /// Apply a migration identified by `version` by executing `sql`.
50    ///
51    /// The implementation is expected to record the successful application
52    /// (e.g. by inserting a row into a migration history table) so that
53    /// [`status`](Migrator::status) reflects the change.
54    async fn apply(&mut self, version: u64, sql: &str) -> Result<(), OxiSqlError>;
55
56    /// Roll back a previously applied migration by executing `down_sql`.
57    ///
58    /// The implementation should remove or update the corresponding history
59    /// record so that [`status`](Migrator::status) no longer reports this
60    /// version as [`MigrationStatus::Applied`].
61    async fn rollback(&mut self, version: u64, down_sql: &str) -> Result<(), OxiSqlError>;
62
63    /// Return metadata about all known migrations and their current status.
64    async fn status(&self) -> Result<Vec<MigrationInfo>, OxiSqlError>;
65
66    /// Return the subset of `all_versions` that have not yet been applied.
67    ///
68    /// The default implementation calls [`status`](Migrator::status) and
69    /// filters out already-applied versions.  Backends may override this with
70    /// a cheaper single-query implementation.
71    async fn pending(&self, all_versions: &[u64]) -> Result<Vec<u64>, OxiSqlError> {
72        let applied: HashSet<u64> = self
73            .status()
74            .await?
75            .into_iter()
76            .filter(|m| m.status == MigrationStatus::Applied)
77            .map(|m| m.version)
78            .collect();
79        Ok(all_versions
80            .iter()
81            .filter(|v| !applied.contains(v))
82            .copied()
83            .collect())
84    }
85}