From e275cd0b5cee57ac62ac8645fbf59225bef7d3bb Mon Sep 17 00:00:00 2001 From: Max Vorobev Date: Sat, 15 Jul 2023 13:09:05 +0300 Subject: [PATCH 1/3] Support naming migrations sequentially and inferring naming scheme --- sqlx-cli/src/lib.rs | 4 ++- sqlx-cli/src/migrate.rs | 67 +++++++++++++++++++++++++++++++++++++++-- sqlx-cli/src/opt.rs | 10 +++++- 3 files changed, 76 insertions(+), 5 deletions(-) diff --git a/sqlx-cli/src/lib.rs b/sqlx-cli/src/lib.rs index 13786efe90..8dbf95735f 100644 --- a/sqlx-cli/src/lib.rs +++ b/sqlx-cli/src/lib.rs @@ -27,7 +27,9 @@ pub async fn run(opt: Opt) -> Result<()> { source, description, reversible, - } => migrate::add(&source, &description, reversible).await?, + sequential, + timestamp, + } => migrate::add(&source, &description, reversible, sequential, timestamp).await?, MigrateCommand::Run { source, dry_run, diff --git a/sqlx-cli/src/migrate.rs b/sqlx-cli/src/migrate.rs index 7e5602dd92..3b44dedff8 100644 --- a/sqlx-cli/src/migrate.rs +++ b/sqlx-cli/src/migrate.rs @@ -37,10 +37,70 @@ fn create_file( Ok(()) } +enum MigrationOrdering { + Timestamp(String), + Sequential(String), +} + +impl MigrationOrdering { + fn timestamp() -> MigrationOrdering { + Self::Timestamp(Utc::now().format("%Y%m%d%H%M%S").to_string()) + } + + fn sequential(version: i64) -> MigrationOrdering { + Self::Sequential(format!("{:04}", version)) + } + + fn file_prefix(&self) -> &str { + match self { + MigrationOrdering::Timestamp(prefix) => prefix, + MigrationOrdering::Sequential(prefix) => prefix, + } + } + + fn infer(sequential: bool, timestamp: bool, migrator: &Migrator) -> Self { + match (timestamp, sequential) { + (true, true) => panic!("Impossible to specify both timestamp and sequential mode"), + (true, false) => MigrationOrdering::timestamp(), + (false, true) => MigrationOrdering::sequential( + migrator + .iter() + .last() + .map_or(1, |last_migration| last_migration.version + 1), + ), + (false, false) => { + // inferring the naming scheme + let migrations = migrator.iter().rev().take(2).collect::>(); + if let [last, pre_last] = &migrations[..] { + // there are at least two migrations, compare the last twothere's only one existing migration + if last.version - pre_last.version == 1 { + // their version numbers differ by 1, infer sequential + MigrationOrdering::sequential(last.version + 1) + } else { + MigrationOrdering::timestamp() + } + } else if let [last] = &migrations[..] { + // there is only one existing migration + if last.version == 0 || last.version == 1 { + // infer sequential if the version number is 0 or 1 + MigrationOrdering::sequential(last.version + 1) + } else { + MigrationOrdering::timestamp() + } + } else { + MigrationOrdering::timestamp() + } + } + } + } +} + pub async fn add( migration_source: &str, description: &str, reversible: bool, + sequential: bool, + timestamp: bool, ) -> anyhow::Result<()> { fs::create_dir_all(migration_source).context("Unable to create migrations directory")?; @@ -50,15 +110,16 @@ pub async fn add( .unwrap_or(false); let migrator = Migrator::new(Path::new(migration_source)).await?; - // This checks if all existing migrations are of the same type as the reverisble flag passed + // This checks if all existing migrations are of the same type as the reversible flag passed for migration in migrator.iter() { if migration.migration_type.is_reversible() != reversible { bail!(MigrateError::InvalidMixReversibleAndSimple); } } - let dt = Utc::now(); - let file_prefix = dt.format("%Y%m%d%H%M%S").to_string(); + let ordering = MigrationOrdering::infer(sequential, timestamp, &migrator); + let file_prefix = ordering.file_prefix(); + if reversible { create_file( migration_source, diff --git a/sqlx-cli/src/opt.rs b/sqlx-cli/src/opt.rs index fb901d565c..011f6d8441 100644 --- a/sqlx-cli/src/opt.rs +++ b/sqlx-cli/src/opt.rs @@ -110,7 +110,7 @@ pub struct MigrateOpt { #[derive(Parser, Debug)] pub enum MigrateCommand { /// Create a new migration with the given description, - /// and the current time as the version. + /// and (by default) the current time as the version. Add { description: String, @@ -121,6 +121,14 @@ pub enum MigrateCommand { /// else creates a single sql file #[clap(short)] reversible: bool, + + /// If true, migrations are named by timestamp + #[clap(short, long)] + timestamp: bool, + + /// If true, migrations are named sequentially + #[clap(short, long, conflicts_with = "timestamp")] + sequential: bool, }, /// Run all pending migrations. From b58fe54a4e84eb138ec8395fac966afbd169e96c Mon Sep 17 00:00:00 2001 From: Max Vorobev Date: Fri, 21 Jul 2023 22:24:57 +0300 Subject: [PATCH 2/3] Document new options and how naming is inferred --- sqlx-cli/src/opt.rs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/sqlx-cli/src/opt.rs b/sqlx-cli/src/opt.rs index 011f6d8441..a72abc983c 100644 --- a/sqlx-cli/src/opt.rs +++ b/sqlx-cli/src/opt.rs @@ -109,8 +109,22 @@ pub struct MigrateOpt { #[derive(Parser, Debug)] pub enum MigrateCommand { - /// Create a new migration with the given description, - /// and (by default) the current time as the version. + /// Create a new migration with the given description. + /// + /// A version number will be automatically assigned to the migration. + /// + /// For convenience, this command will attempt to detect if sequential versioning is in use, + /// and if so, continue the sequence. + /// + /// Sequential versioning is inferred if: + /// + /// * The version numbers of the last two migrations differ by exactly 1, or: + /// + /// * only one migration exists and its version number is either 0 or 1. + /// + /// Otherwise timestamp versioning is assumed. + /// + /// This behavior can overridden by `--sequential` or `--timestamp`, respectively. Add { description: String, @@ -122,11 +136,11 @@ pub enum MigrateCommand { #[clap(short)] reversible: bool, - /// If true, migrations are named by timestamp + /// If set, use timestamp versioning for the new migration. Conflicts with `--sequential`. #[clap(short, long)] timestamp: bool, - /// If true, migrations are named sequentially + /// If set, use timestamp versioning for the new migration. Conflicts with `--timestamp`. #[clap(short, long, conflicts_with = "timestamp")] sequential: bool, }, From 2dd7f44700977a29b9d035c3a782579c840c4c92 Mon Sep 17 00:00:00 2001 From: Max Vorobev Date: Sat, 22 Jul 2023 18:47:46 +0300 Subject: [PATCH 3/3] Only account for up migrations when inferring ordering --- sqlx-cli/src/migrate.rs | 7 ++++++- sqlx-core/src/migrate/migration_type.rs | 8 ++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/sqlx-cli/src/migrate.rs b/sqlx-cli/src/migrate.rs index 3b44dedff8..5e94f26f41 100644 --- a/sqlx-cli/src/migrate.rs +++ b/sqlx-cli/src/migrate.rs @@ -70,7 +70,12 @@ impl MigrationOrdering { ), (false, false) => { // inferring the naming scheme - let migrations = migrator.iter().rev().take(2).collect::>(); + let migrations = migrator + .iter() + .filter(|migration| migration.migration_type.is_up_migration()) + .rev() + .take(2) + .collect::>(); if let [last, pre_last] = &migrations[..] { // there are at least two migrations, compare the last twothere's only one existing migration if last.version - pre_last.version == 1 { diff --git a/sqlx-core/src/migrate/migration_type.rs b/sqlx-core/src/migrate/migration_type.rs index 87feedfa2b..5fe298ae9f 100644 --- a/sqlx-core/src/migrate/migration_type.rs +++ b/sqlx-core/src/migrate/migration_type.rs @@ -32,6 +32,14 @@ impl MigrationType { } } + pub fn is_up_migration(&self) -> bool { + match self { + MigrationType::Simple => true, + MigrationType::ReversibleUp => true, + MigrationType::ReversibleDown => false, + } + } + pub fn is_down_migration(&self) -> bool { match self { MigrationType::Simple => false,