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.