Skip to main content

MigrationEngine

Struct MigrationEngine 

Source
pub struct MigrationEngine { /* private fields */ }
Expand description

Migration engine bound to an open connection.

Implementations§

Source§

impl MigrationEngine

Source

pub fn new( conn: Box<dyn Connection>, migrations_dir: PathBuf, dialect: Dialect, ) -> Self

Source

pub fn ensure_migration_table(&mut self) -> Result<(), SqlError>

Ensure the ferrule_migrations tracking table exists.

The DDL is dialect-specific because the canonical column types, the timestamp default, and the “create if absent” idiom differ across backends:

  • SQLite / PostgresTEXT keys are valid and CREATE TABLE IF NOT EXISTS is supported.
  • MySQLTEXT cannot be a PRIMARY KEY without a prefix length, so the keyed columns use VARCHAR.
  • MSSQLCREATE TABLE IF NOT EXISTS is not valid T-SQL, TEXT cannot key a table, and the TIMESTAMP type is a rowversion (not a datetime); we guard creation with IF OBJECT_ID(...) IS NULL and use DATETIME2.
  • Oracle — has no TEXT type and IF NOT EXISTS is unsupported pre-23c, so creation runs inside a PL/SQL block that swallows ORA-00955 (name already used).
Source

pub fn pending_migrations(&mut self) -> Result<Vec<MigrationFile>, SqlError>

Return the list of migrations that have not yet been applied, sorted lexicographically by version.

Source

pub fn apply_up(&mut self, file: &MigrationFile) -> Result<(), SqlError>

Apply a single migration (.up.sql).

The migration script and the ferrule_migrations tracking-row INSERT are committed as a single unit on backends with transactional DDL (SQLite, Postgres, MSSQL): both succeed or both roll back, so a mid-script failure can never leave the migration recorded-but-partial or applied-but-untracked. On MySQL and Oracle, DDL implicitly commits, so the two steps run best-effort and a failure in the middle can leave the schema partially applied — see apply_atomic for the per-dialect details.

Source

pub fn apply_down(&mut self, file: &MigrationFile) -> Result<(), SqlError>

Rollback a single migration (.down.sql).

The rollback script and the ferrule_migrations tracking-row DELETE are committed together on backends with transactional DDL (SQLite, Postgres, MSSQL): the row is removed only if the entire down script succeeds, so a mid-script failure can never leave the schema half-rolled-back while the row still marks the migration applied. On MySQL and Oracle, DDL implicitly commits, so the two steps run best-effort — see apply_atomic.

Source

pub fn last_applied( &mut self, n: usize, ) -> Result<Vec<AppliedMigration>, SqlError>

Read the last N applied versions from the tracking table, ordered by most-recent first.

The ordering uses version DESC as a deterministic tiebreak after applied_at DESC: applied_at has second granularity, and migrate up can apply a whole batch inside a single second, so without the tiebreak down could roll back an arbitrary member of that batch rather than the genuinely newest one.

The row-limit clause is dialect-specific: SQLite, Postgres, and MySQL accept LIMIT n; MSSQL uses SELECT TOP n; Oracle (12c+) uses FETCH FIRST n ROWS ONLY.

Source

pub fn all_applied(&mut self) -> Result<Vec<AppliedMigration>, SqlError>

Read every applied migration from the tracking table, ordered most-recent first (applied_at DESC, version DESC).

Unlike MigrationEngine::last_applied this applies no row cap, so migrate history and migrate verify see the full set rather than a silently truncated window. The ordering is identical to last_applied and needs no dialect-specific limit clause, so the same query runs on all backends.

Source

pub fn verify_checksum(&mut self, version: &str) -> Result<(), SqlError>

Verify that a single migration’s .up.sql file on disk still matches the checksum recorded in the database. Returns Ok(()) if clean, Err on drift.

The on-disk file is resolved by reusing MigrationEngine::scan_dir (Direction::Up) and matching the derived version exactly, the same way apply_up and pending_migrations identify files. An earlier name.starts_with(version) prefix match could bind the wrong file (e.g. version 2026 matching 20260602_x.up.sql), reporting spurious drift or masking real drift.

Source

pub fn applied_versions(&mut self) -> Result<HashSet<String>, SqlError>

Source

pub fn scan_dir( &self, direction: Direction, ) -> Result<Vec<MigrationFile>, SqlError>

Scan the migrations directory for files in the given direction, returning them sorted lexicographically by version.

The version is the substring before the first _ in the file stem (20260602120000_add_users.up.sql -> 20260602120000). Because two distinct files can collapse onto the same version under that rule, this detects the collision up front and returns a SqlError::QueryFailed naming the conflicting files. Surfacing it here — before any apply_up runs — prevents a duplicate version from being discovered only when the second tracking-row INSERT hits the primary-key constraint mid-run, which would leave one migration’s DDL applied but untracked.

Source

pub fn on_disk_checksums(&self) -> Result<HashMap<String, String>, SqlError>

Build a version -> file-checksum map by scanning the up migrations directory exactly once.

The checksum is the SHA-256 hex digest of the .up.sql file’s contents, identical to what MigrationEngine::apply_up records in the tracking table. This lets verify compare every applied migration against on-disk content using the checksums it already has from the applied-list query — one directory scan and zero extra SELECTs, instead of re-reading the directory and re-querying the database once per applied migration.

Source

pub fn verify_applied( &self, applied: &[AppliedMigration], ) -> Result<Vec<ChecksumDrift>, SqlError>

Verify that every supplied applied migration still matches its .up.sql file on disk, using checksums already held in hand.

applied is the list returned by MigrationEngine::last_applied (or any caller-built list of applied versions + recorded checksums). The directory is scanned once via MigrationEngine::on_disk_checksums; no per-migration queries are issued. The returned vector lists every drifted migration with a human-readable reason — empty means all clean.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more