mirror of
https://github.com/launchbadge/sqlx
synced 2024-11-10 14:34:19 +00:00
Support naming migrations sequentially (#2602)
* Support naming migrations sequentially and inferring naming scheme * Document new options and how naming is inferred * Only account for up migrations when inferring ordering
This commit is contained in:
parent
c70cfaf035
commit
f2bb464bcd
4 changed files with 104 additions and 6 deletions
|
@ -27,7 +27,9 @@ pub async fn run(opt: Opt) -> Result<()> {
|
||||||
source,
|
source,
|
||||||
description,
|
description,
|
||||||
reversible,
|
reversible,
|
||||||
} => migrate::add(&source, &description, reversible).await?,
|
sequential,
|
||||||
|
timestamp,
|
||||||
|
} => migrate::add(&source, &description, reversible, sequential, timestamp).await?,
|
||||||
MigrateCommand::Run {
|
MigrateCommand::Run {
|
||||||
source,
|
source,
|
||||||
dry_run,
|
dry_run,
|
||||||
|
|
|
@ -37,10 +37,75 @@ fn create_file(
|
||||||
Ok(())
|
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(
|
pub async fn add(
|
||||||
migration_source: &str,
|
migration_source: &str,
|
||||||
description: &str,
|
description: &str,
|
||||||
reversible: bool,
|
reversible: bool,
|
||||||
|
sequential: bool,
|
||||||
|
timestamp: bool,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
fs::create_dir_all(migration_source).context("Unable to create migrations directory")?;
|
fs::create_dir_all(migration_source).context("Unable to create migrations directory")?;
|
||||||
|
|
||||||
|
@ -50,15 +115,16 @@ pub async fn add(
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
|
||||||
let migrator = Migrator::new(Path::new(migration_source)).await?;
|
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() {
|
for migration in migrator.iter() {
|
||||||
if migration.migration_type.is_reversible() != reversible {
|
if migration.migration_type.is_reversible() != reversible {
|
||||||
bail!(MigrateError::InvalidMixReversibleAndSimple);
|
bail!(MigrateError::InvalidMixReversibleAndSimple);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let dt = Utc::now();
|
let ordering = MigrationOrdering::infer(sequential, timestamp, &migrator);
|
||||||
let file_prefix = dt.format("%Y%m%d%H%M%S").to_string();
|
let file_prefix = ordering.file_prefix();
|
||||||
|
|
||||||
if reversible {
|
if reversible {
|
||||||
create_file(
|
create_file(
|
||||||
migration_source,
|
migration_source,
|
||||||
|
|
|
@ -109,8 +109,22 @@ pub struct MigrateOpt {
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
pub enum MigrateCommand {
|
pub enum MigrateCommand {
|
||||||
/// Create a new migration with the given description,
|
/// Create a new migration with the given description.
|
||||||
/// and the current time as the version.
|
///
|
||||||
|
/// 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 {
|
Add {
|
||||||
description: String,
|
description: String,
|
||||||
|
|
||||||
|
@ -121,6 +135,14 @@ pub enum MigrateCommand {
|
||||||
/// else creates a single sql file
|
/// else creates a single sql file
|
||||||
#[clap(short)]
|
#[clap(short)]
|
||||||
reversible: bool,
|
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.
|
/// Run all pending migrations.
|
||||||
|
|
|
@ -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 {
|
pub fn is_down_migration(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
MigrationType::Simple => false,
|
MigrationType::Simple => false,
|
||||||
|
|
Loading…
Reference in a new issue