From 6189e93c1ba8c4844803d2a7dbc1c725f21ef0b6 Mon Sep 17 00:00:00 2001 From: aoengin Date: Sun, 4 Jan 2026 13:33:38 +0300 Subject: [PATCH 1/4] feat(migration): ensure deterministic migration order by sorting ups before downs --- sqlx-core/src/migrate/migration_type.rs | 8 ++++++++ sqlx-core/src/migrate/migrator.rs | 8 ++++++-- sqlx-core/src/migrate/source.rs | 10 +++++++--- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/sqlx-core/src/migrate/migration_type.rs b/sqlx-core/src/migrate/migration_type.rs index 350ddb3f27..401e6c3555 100644 --- a/sqlx-core/src/migrate/migration_type.rs +++ b/sqlx-core/src/migrate/migration_type.rs @@ -74,6 +74,14 @@ impl MigrationType { } } + /// Ordering helper to sort ups before downs when versions tie. + pub fn direction_order(&self) -> u8 { + match self { + MigrationType::ReversibleDown => 1, + MigrationType::Simple | MigrationType::ReversibleUp => 0, + } + } + #[deprecated = "unused"] pub fn infer(migrator: &Migrator, reversible: bool) -> MigrationType { match migrator.iter().last() { diff --git a/sqlx-core/src/migrate/migrator.rs b/sqlx-core/src/migrate/migrator.rs index 53295c92d0..76eb37218a 100644 --- a/sqlx-core/src/migrate/migrator.rs +++ b/sqlx-core/src/migrate/migrator.rs @@ -87,8 +87,12 @@ impl Migrator { /// let m = Migrator::with_migrations(migrations); /// ``` pub fn with_migrations(mut migrations: Vec) -> Self { - // Ensure that we are sorted by version in ascending order. - migrations.sort_by_key(|m| m.version); + // Ensure deterministic order: version ascending, then up before down when versions match. + migrations.sort_by(|a, b| { + a.version + .cmp(&b.version) + .then_with(|| a.migration_type.direction_order().cmp(&b.migration_type.direction_order())) + }); Self { migrations: Cow::Owned(migrations), ..Self::DEFAULT diff --git a/sqlx-core/src/migrate/source.rs b/sqlx-core/src/migrate/source.rs index 4648e53f1e..f3b1bd057d 100644 --- a/sqlx-core/src/migrate/source.rs +++ b/sqlx-core/src/migrate/source.rs @@ -248,9 +248,13 @@ pub fn resolve_blocking_with_config( )); } - // Ensure that we are sorted by version in ascending order. - migrations.sort_by_key(|(m, _)| m.version); - + // Ensure deterministic order: version ascending, then up before down when versions match. + migrations.sort_by(|(a, _), (b, _)| { + a.version + .cmp(&b.version) + .then_with(|| a.migration_type.direction_order().cmp(&b.migration_type.direction_order())) + }); + Ok(migrations) } From d0bacfaa5012bb191a7e4150ae8dddee46bd9888 Mon Sep 17 00:00:00 2001 From: aoengin Date: Sun, 4 Jan 2026 14:56:45 +0300 Subject: [PATCH 2/4] cargo fmt --- sqlx-core/src/migrate/migrator.rs | 8 +++++--- sqlx-core/src/migrate/source.rs | 10 ++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/sqlx-core/src/migrate/migrator.rs b/sqlx-core/src/migrate/migrator.rs index 76eb37218a..1c09ca880b 100644 --- a/sqlx-core/src/migrate/migrator.rs +++ b/sqlx-core/src/migrate/migrator.rs @@ -89,9 +89,11 @@ impl Migrator { pub fn with_migrations(mut migrations: Vec) -> Self { // Ensure deterministic order: version ascending, then up before down when versions match. migrations.sort_by(|a, b| { - a.version - .cmp(&b.version) - .then_with(|| a.migration_type.direction_order().cmp(&b.migration_type.direction_order())) + a.version.cmp(&b.version).then_with(|| { + a.migration_type + .direction_order() + .cmp(&b.migration_type.direction_order()) + }) }); Self { migrations: Cow::Owned(migrations), diff --git a/sqlx-core/src/migrate/source.rs b/sqlx-core/src/migrate/source.rs index f3b1bd057d..9eb99e18fd 100644 --- a/sqlx-core/src/migrate/source.rs +++ b/sqlx-core/src/migrate/source.rs @@ -250,11 +250,13 @@ pub fn resolve_blocking_with_config( // Ensure deterministic order: version ascending, then up before down when versions match. migrations.sort_by(|(a, _), (b, _)| { - a.version - .cmp(&b.version) - .then_with(|| a.migration_type.direction_order().cmp(&b.migration_type.direction_order())) + a.version.cmp(&b.version).then_with(|| { + a.migration_type + .direction_order() + .cmp(&b.migration_type.direction_order()) + }) }); - + Ok(migrations) } From 4f36184f90d877ddba1c56cdeb2b7239a3849b29 Mon Sep 17 00:00:00 2001 From: aoengin Date: Fri, 1 May 2026 17:22:02 +0300 Subject: [PATCH 3/4] feat(migration): implement ordering for Migration and MigrationType for deterministic sorting --- sqlx-core/src/migrate/migration.rs | 23 +++++++++++++++++++++++ sqlx-core/src/migrate/migration_type.rs | 7 ++++--- sqlx-core/src/migrate/migrator.rs | 9 +-------- sqlx-core/src/migrate/source.rs | 9 +-------- 4 files changed, 29 insertions(+), 19 deletions(-) diff --git a/sqlx-core/src/migrate/migration.rs b/sqlx-core/src/migrate/migration.rs index 79721d244d..a434ac9684 100644 --- a/sqlx-core/src/migrate/migration.rs +++ b/sqlx-core/src/migrate/migration.rs @@ -1,5 +1,6 @@ use sha2::{Digest, Sha384}; use std::borrow::Cow; +use std::cmp::Ordering; use crate::sql_str::SqlStr; @@ -15,6 +16,28 @@ pub struct Migration { pub no_tx: bool, } +impl PartialEq for Migration { + fn eq(&self, other: &Self) -> bool { + self.version == other.version && self.migration_type == other.migration_type + } +} + +impl Eq for Migration {} + +impl PartialOrd for Migration { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Migration { + fn cmp(&self, other: &Self) -> Ordering { + self.version + .cmp(&other.version) + .then_with(|| self.migration_type.cmp(&other.migration_type)) + } +} + impl Migration { pub fn new( version: i64, diff --git a/sqlx-core/src/migrate/migration_type.rs b/sqlx-core/src/migrate/migration_type.rs index 401e6c3555..a72421b141 100644 --- a/sqlx-core/src/migrate/migration_type.rs +++ b/sqlx-core/src/migrate/migration_type.rs @@ -1,7 +1,7 @@ use super::Migrator; /// Migration Type represents the type of migration -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum MigrationType { /// Simple migration are single file migrations with no up / down queries Simple, @@ -77,8 +77,9 @@ impl MigrationType { /// Ordering helper to sort ups before downs when versions tie. pub fn direction_order(&self) -> u8 { match self { - MigrationType::ReversibleDown => 1, - MigrationType::Simple | MigrationType::ReversibleUp => 0, + MigrationType::Simple => 0, + MigrationType::ReversibleUp => 1, + MigrationType::ReversibleDown => 2, } } diff --git a/sqlx-core/src/migrate/migrator.rs b/sqlx-core/src/migrate/migrator.rs index 1c09ca880b..7aa284a60c 100644 --- a/sqlx-core/src/migrate/migrator.rs +++ b/sqlx-core/src/migrate/migrator.rs @@ -87,14 +87,7 @@ impl Migrator { /// let m = Migrator::with_migrations(migrations); /// ``` pub fn with_migrations(mut migrations: Vec) -> Self { - // Ensure deterministic order: version ascending, then up before down when versions match. - migrations.sort_by(|a, b| { - a.version.cmp(&b.version).then_with(|| { - a.migration_type - .direction_order() - .cmp(&b.migration_type.direction_order()) - }) - }); + migrations.sort(); Self { migrations: Cow::Owned(migrations), ..Self::DEFAULT diff --git a/sqlx-core/src/migrate/source.rs b/sqlx-core/src/migrate/source.rs index 9eb99e18fd..10fc7c7b8e 100644 --- a/sqlx-core/src/migrate/source.rs +++ b/sqlx-core/src/migrate/source.rs @@ -248,14 +248,7 @@ pub fn resolve_blocking_with_config( )); } - // Ensure deterministic order: version ascending, then up before down when versions match. - migrations.sort_by(|(a, _), (b, _)| { - a.version.cmp(&b.version).then_with(|| { - a.migration_type - .direction_order() - .cmp(&b.migration_type.direction_order()) - }) - }); + migrations.sort(); Ok(migrations) } From e590e4edf12617476baba9e2057be73110188db6 Mon Sep 17 00:00:00 2001 From: aoengin Date: Fri, 1 May 2026 17:25:45 +0300 Subject: [PATCH 4/4] refactor(migration): remove unused direction_order method from MigrationType --- sqlx-core/src/migrate/migration_type.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/sqlx-core/src/migrate/migration_type.rs b/sqlx-core/src/migrate/migration_type.rs index a72421b141..107cdd246c 100644 --- a/sqlx-core/src/migrate/migration_type.rs +++ b/sqlx-core/src/migrate/migration_type.rs @@ -74,15 +74,6 @@ impl MigrationType { } } - /// Ordering helper to sort ups before downs when versions tie. - pub fn direction_order(&self) -> u8 { - match self { - MigrationType::Simple => 0, - MigrationType::ReversibleUp => 1, - MigrationType::ReversibleDown => 2, - } - } - #[deprecated = "unused"] pub fn infer(migrator: &Migrator, reversible: bool) -> MigrationType { match migrator.iter().last() {