mirror of
https://github.com/launchbadge/sqlx.git
synced 2026-03-22 10:04:02 +00:00
cli: add --target-version CLI flags for migrate run/revert (#2538)
* cli: add --target-version CLI flags for migrate run/revert * cli: fix broken test * cli: test harness for `sqlx migrate` along with --target-version tests * cli: Fail if version supplied to run/revert is too old/new After some discussion with my coworkers, we thought about the behavior a bit more: The behavior is now that for a run, if the provided version is too old, the CLI will return with failure rather than being a no-op. This gives feedback to the operator instead of being quiet. It is still valid to up/downgrade to the latest version, this will still be a no-op to allow for idempotency.
This commit is contained in:
@@ -50,7 +50,7 @@ pub async fn reset(
|
||||
|
||||
pub async fn setup(migration_source: &str, connect_opts: &ConnectOpts) -> anyhow::Result<()> {
|
||||
create(connect_opts).await?;
|
||||
migrate::run(migration_source, connect_opts, false, false).await
|
||||
migrate::run(migration_source, connect_opts, false, false, None).await
|
||||
}
|
||||
|
||||
fn ask_to_continue(connect_opts: &ConnectOpts) -> bool {
|
||||
|
||||
@@ -35,13 +35,33 @@ pub async fn run(opt: Opt) -> Result<()> {
|
||||
dry_run,
|
||||
ignore_missing,
|
||||
connect_opts,
|
||||
} => migrate::run(&source, &connect_opts, dry_run, *ignore_missing).await?,
|
||||
target_version,
|
||||
} => {
|
||||
migrate::run(
|
||||
&source,
|
||||
&connect_opts,
|
||||
dry_run,
|
||||
*ignore_missing,
|
||||
target_version,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
MigrateCommand::Revert {
|
||||
source,
|
||||
dry_run,
|
||||
ignore_missing,
|
||||
connect_opts,
|
||||
} => migrate::revert(&source, &connect_opts, dry_run, *ignore_missing).await?,
|
||||
target_version,
|
||||
} => {
|
||||
migrate::revert(
|
||||
&source,
|
||||
&connect_opts,
|
||||
dry_run,
|
||||
*ignore_missing,
|
||||
target_version,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
MigrateCommand::Info {
|
||||
source,
|
||||
connect_opts,
|
||||
|
||||
@@ -267,8 +267,15 @@ pub async fn run(
|
||||
connect_opts: &ConnectOpts,
|
||||
dry_run: bool,
|
||||
ignore_missing: bool,
|
||||
target_version: Option<i64>,
|
||||
) -> anyhow::Result<()> {
|
||||
let migrator = Migrator::new(Path::new(migration_source)).await?;
|
||||
if let Some(target_version) = target_version {
|
||||
if !migrator.iter().any(|m| target_version == m.version) {
|
||||
bail!(MigrateError::VersionNotPresent(target_version));
|
||||
}
|
||||
}
|
||||
|
||||
let mut conn = crate::connect(connect_opts).await?;
|
||||
|
||||
conn.ensure_migrations_table().await?;
|
||||
@@ -281,6 +288,17 @@ pub async fn run(
|
||||
let applied_migrations = conn.list_applied_migrations().await?;
|
||||
validate_applied_migrations(&applied_migrations, &migrator, ignore_missing)?;
|
||||
|
||||
let latest_version = applied_migrations
|
||||
.iter()
|
||||
.max_by(|x, y| x.version.cmp(&y.version))
|
||||
.and_then(|migration| Some(migration.version))
|
||||
.unwrap_or(0);
|
||||
if let Some(target_version) = target_version {
|
||||
if target_version < latest_version {
|
||||
bail!(MigrateError::VersionTooOld(target_version, latest_version));
|
||||
}
|
||||
}
|
||||
|
||||
let applied_migrations: HashMap<_, _> = applied_migrations
|
||||
.into_iter()
|
||||
.map(|m| (m.version, m))
|
||||
@@ -299,12 +317,23 @@ pub async fn run(
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let elapsed = if dry_run {
|
||||
let skip = match target_version {
|
||||
Some(target_version) if migration.version > target_version => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let elapsed = if dry_run || skip {
|
||||
Duration::new(0, 0)
|
||||
} else {
|
||||
conn.apply(migration).await?
|
||||
};
|
||||
let text = if dry_run { "Can apply" } else { "Applied" };
|
||||
let text = if skip {
|
||||
"Skipped"
|
||||
} else if dry_run {
|
||||
"Can apply"
|
||||
} else {
|
||||
"Applied"
|
||||
};
|
||||
|
||||
println!(
|
||||
"{} {}/{} {} {}",
|
||||
@@ -333,8 +362,15 @@ pub async fn revert(
|
||||
connect_opts: &ConnectOpts,
|
||||
dry_run: bool,
|
||||
ignore_missing: bool,
|
||||
target_version: Option<i64>,
|
||||
) -> anyhow::Result<()> {
|
||||
let migrator = Migrator::new(Path::new(migration_source)).await?;
|
||||
if let Some(target_version) = target_version {
|
||||
if target_version != 0 && !migrator.iter().any(|m| target_version == m.version) {
|
||||
bail!(MigrateError::VersionNotPresent(target_version));
|
||||
}
|
||||
}
|
||||
|
||||
let mut conn = crate::connect(&connect_opts).await?;
|
||||
|
||||
conn.ensure_migrations_table().await?;
|
||||
@@ -347,6 +383,17 @@ pub async fn revert(
|
||||
let applied_migrations = conn.list_applied_migrations().await?;
|
||||
validate_applied_migrations(&applied_migrations, &migrator, ignore_missing)?;
|
||||
|
||||
let latest_version = applied_migrations
|
||||
.iter()
|
||||
.max_by(|x, y| x.version.cmp(&y.version))
|
||||
.and_then(|migration| Some(migration.version))
|
||||
.unwrap_or(0);
|
||||
if let Some(target_version) = target_version {
|
||||
if target_version > latest_version {
|
||||
bail!(MigrateError::VersionTooNew(target_version, latest_version));
|
||||
}
|
||||
}
|
||||
|
||||
let applied_migrations: HashMap<_, _> = applied_migrations
|
||||
.into_iter()
|
||||
.map(|m| (m.version, m))
|
||||
@@ -361,12 +408,22 @@ pub async fn revert(
|
||||
}
|
||||
|
||||
if applied_migrations.contains_key(&migration.version) {
|
||||
let elapsed = if dry_run {
|
||||
let skip = match target_version {
|
||||
Some(target_version) if migration.version <= target_version => true,
|
||||
_ => false,
|
||||
};
|
||||
let elapsed = if dry_run || skip {
|
||||
Duration::new(0, 0)
|
||||
} else {
|
||||
conn.revert(migration).await?
|
||||
};
|
||||
let text = if dry_run { "Can apply" } else { "Applied" };
|
||||
let text = if skip {
|
||||
"Skipped"
|
||||
} else if dry_run {
|
||||
"Can apply"
|
||||
} else {
|
||||
"Applied"
|
||||
};
|
||||
|
||||
println!(
|
||||
"{} {}/{} {} {}",
|
||||
@@ -378,8 +435,12 @@ pub async fn revert(
|
||||
);
|
||||
|
||||
is_applied = true;
|
||||
// Only a single migration will be reverted at a time, so we break
|
||||
break;
|
||||
|
||||
// Only a single migration will be reverted at a time if no target
|
||||
// version is supplied, so we break.
|
||||
if let None = target_version {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if !is_applied {
|
||||
|
||||
@@ -159,6 +159,11 @@ pub enum MigrateCommand {
|
||||
|
||||
#[clap(flatten)]
|
||||
connect_opts: ConnectOpts,
|
||||
|
||||
/// Apply migrations up to the specified version. If unspecified, apply all
|
||||
/// pending migrations. If already at the target version, then no-op.
|
||||
#[clap(long)]
|
||||
target_version: Option<i64>,
|
||||
},
|
||||
|
||||
/// Revert the latest migration with a down file.
|
||||
@@ -175,6 +180,12 @@ pub enum MigrateCommand {
|
||||
|
||||
#[clap(flatten)]
|
||||
connect_opts: ConnectOpts,
|
||||
|
||||
/// Revert migrations down to the specified version. If unspecified, revert
|
||||
/// only the last migration. Set to 0 to revert all migrations. If already
|
||||
/// at the target version, then no-op.
|
||||
#[clap(long)]
|
||||
target_version: Option<i64>,
|
||||
},
|
||||
|
||||
/// List all available migrations.
|
||||
|
||||
@@ -361,7 +361,7 @@ mod tests {
|
||||
let sample_metadata = std::fs::read_to_string(sample_metadata_path)?;
|
||||
let metadata: Metadata = sample_metadata.parse()?;
|
||||
|
||||
let action = minimal_project_recompile_action(&metadata)?;
|
||||
let action = minimal_project_recompile_action(&metadata);
|
||||
assert_eq!(
|
||||
action,
|
||||
ProjectRecompileAction {
|
||||
|
||||
Reference in New Issue
Block a user