Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support naming migrations sequentially #2602

Merged
merged 3 commits into from Jul 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion sqlx-cli/src/lib.rs
Expand Up @@ -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,
Expand Down
72 changes: 69 additions & 3 deletions sqlx-cli/src/migrate.rs
Expand Up @@ -37,10 +37,75 @@ 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()
.filter(|migration| migration.migration_type.is_up_migration())
.rev()
.take(2)
.collect::<Vec<_>>();
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")?;

Expand All @@ -50,15 +115,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,
Expand Down
26 changes: 24 additions & 2 deletions sqlx-cli/src/opt.rs
Expand Up @@ -109,8 +109,22 @@ pub struct MigrateOpt {

#[derive(Parser, Debug)]
pub enum MigrateCommand {
/// Create a new migration with the given description,
/// and 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,

Expand All @@ -121,6 +135,14 @@ pub enum MigrateCommand {
/// else creates a single sql file
#[clap(short)]
reversible: bool,

/// If set, use timestamp versioning for the new migration. Conflicts with `--sequential`.
#[clap(short, long)]
timestamp: bool,

/// If set, use timestamp versioning for the new migration. Conflicts with `--timestamp`.
#[clap(short, long, conflicts_with = "timestamp")]
sequential: bool,
},

/// Run all pending migrations.
Expand Down
8 changes: 8 additions & 0 deletions sqlx-core/src/migrate/migration_type.rs
Expand Up @@ -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,
Expand Down