Skip to content

Add the possibility to skip migrations #3846

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sqlx-cli/src/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,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, None).await
migrate::run(migration_source, connect_opts, false, false, None, false).await
}

async fn ask_to_continue_drop(db_url: String) -> bool {
Expand Down
18 changes: 18 additions & 0 deletions sqlx-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,24 @@ async fn do_run(opt: Opt) -> Result<()> {
dry_run,
*ignore_missing,
target_version,
false,
)
.await?
}
MigrateCommand::Skip {
source,
dry_run,
ignore_missing,
connect_opts,
target_version,
} => {
migrate::run(
&source,
&connect_opts,
dry_run,
*ignore_missing,
target_version,
true,
)
.await?
}
Expand Down
11 changes: 7 additions & 4 deletions sqlx-cli/src/migrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ pub async fn run(
dry_run: bool,
ignore_missing: bool,
target_version: Option<i64>,
skip: bool,
) -> anyhow::Result<()> {
let migrator = Migrator::new(Path::new(migration_source)).await?;
if let Some(target_version) = target_version {
Expand Down Expand Up @@ -326,18 +327,20 @@ pub async fn run(
}
}
None => {
let skip =
let exceeds_target =
target_version.is_some_and(|target_version| migration.version > target_version);

let elapsed = if dry_run || skip {
let elapsed = if dry_run || exceeds_target {
Duration::new(0, 0)
} else {
conn.apply(migration).await?
conn.apply(migration, skip).await?
};
let text = if skip {
let text = if exceeds_target {
"Skipped"
} else if dry_run {
"Can apply"
} else if skip {
"Skipped on request"
} else {
"Applied"
};
Expand Down
21 changes: 21 additions & 0 deletions sqlx-cli/src/opt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,27 @@ pub enum MigrateCommand {
target_version: Option<i64>,
},

/// Skip all pending migrations.
Skip {
#[clap(flatten)]
source: Source,

/// List all the migrations to be skipped without marking them as applied.
#[clap(long)]
dry_run: bool,

#[clap(flatten)]
ignore_missing: IgnoreMissing,

#[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.
Revert {
#[clap(flatten)]
Expand Down
28 changes: 23 additions & 5 deletions sqlx-cli/tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,22 @@ pub struct TestDatabase {
migrations: String,
}

pub enum MigrateCommand {
Run,
Revert,
Skip,
}

impl AsRef<str> for MigrateCommand {
fn as_ref(&self) -> &str {
match self {
MigrateCommand::Run => "run",
MigrateCommand::Revert => "revert",
MigrateCommand::Skip => "skip",
}
}
}

impl TestDatabase {
pub fn new(name: &str, migrations: &str) -> Self {
let migrations_path = Path::new("tests").join(migrations);
Expand All @@ -38,7 +54,12 @@ impl TestDatabase {
format!("sqlite://{}", self.file_path.display())
}

pub fn run_migration(&self, revert: bool, version: Option<i64>, dry_run: bool) -> Assert {
pub fn run_migration(
&self,
command: MigrateCommand,
version: Option<i64>,
dry_run: bool,
) -> Assert {
let ver = match version {
Some(v) => v.to_string(),
None => String::from(""),
Expand All @@ -50,10 +71,7 @@ impl TestDatabase {
vec![
"sqlx",
"migrate",
match revert {
true => "revert",
false => "run",
},
command.as_ref(),
"--database-url",
&self.connection_string(),
"--source",
Expand Down
132 changes: 108 additions & 24 deletions sqlx-cli/tests/migrate.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
mod common;

use common::TestDatabase;
use common::{MigrateCommand, TestDatabase};

#[tokio::test]
async fn run_reversible_migrations() {
Expand All @@ -11,10 +11,10 @@ async fn run_reversible_migrations() {
20230401000000,
20230501000000,
];
// Without --target-version specified.k
// Without --target-version specified.
{
let db = TestDatabase::new("migrate_run_reversible_latest", "migrations_reversible");
db.run_migration(false, None, false).success();
db.run_migration(MigrateCommand::Run, None, false).success();
assert_eq!(db.applied_migrations().await, all_migrations);
}
// With --target-version specified.
Expand All @@ -25,17 +25,17 @@ async fn run_reversible_migrations() {
);

// Move to latest, explicitly specified.
db.run_migration(false, Some(20230501000000), false)
db.run_migration(MigrateCommand::Run, Some(20230501000000), false)
.success();
assert_eq!(db.applied_migrations().await, all_migrations);

// Move to latest when we're already at the latest.
db.run_migration(false, Some(20230501000000), false)
db.run_migration(MigrateCommand::Run, Some(20230501000000), false)
.success();
assert_eq!(db.applied_migrations().await, all_migrations);

// Upgrade to an old version.
db.run_migration(false, Some(20230301000000), false)
db.run_migration(MigrateCommand::Run, Some(20230301000000), false)
.failure();
assert_eq!(db.applied_migrations().await, all_migrations);
}
Expand All @@ -47,34 +47,34 @@ async fn run_reversible_migrations() {
);

// First version
db.run_migration(false, Some(20230101000000), false)
db.run_migration(MigrateCommand::Run, Some(20230101000000), false)
.success();
assert_eq!(db.applied_migrations().await, vec![20230101000000]);

// Dry run upgrade to latest.
db.run_migration(false, None, true).success();
db.run_migration(MigrateCommand::Run, None, true).success();
assert_eq!(db.applied_migrations().await, vec![20230101000000]);

// Dry run upgrade + 2
db.run_migration(false, Some(20230301000000), true)
db.run_migration(MigrateCommand::Run, Some(20230301000000), true)
.success();
assert_eq!(db.applied_migrations().await, vec![20230101000000]);

// Upgrade to non-existent version.
db.run_migration(false, Some(20230901000000999), false)
db.run_migration(MigrateCommand::Run, Some(20230901000000999), false)
.failure();
assert_eq!(db.applied_migrations().await, vec![20230101000000]);

// Upgrade + 1
db.run_migration(false, Some(20230201000000), false)
db.run_migration(MigrateCommand::Run, Some(20230201000000), false)
.success();
assert_eq!(
db.applied_migrations().await,
vec![20230101000000, 20230201000000]
);

// Upgrade + 2
db.run_migration(false, Some(20230401000000), false)
db.run_migration(MigrateCommand::Run, Some(20230401000000), false)
.success();
assert_eq!(db.applied_migrations().await, all_migrations[..4]);
}
Expand All @@ -93,55 +93,139 @@ async fn revert_migrations() {
// Without --target-version
{
let db = TestDatabase::new("migrate_revert_incremental", "migrations_reversible");
db.run_migration(false, None, false).success();
db.run_migration(MigrateCommand::Run, None, false).success();

// Dry-run
db.run_migration(true, None, true).success();
db.run_migration(MigrateCommand::Revert, None, true)
.success();
assert_eq!(db.applied_migrations().await, all_migrations);

// Downgrade one
db.run_migration(true, None, false).success();
db.run_migration(MigrateCommand::Revert, None, false)
.success();
assert_eq!(db.applied_migrations().await, all_migrations[..4]);

// Downgrade one
db.run_migration(true, None, false).success();
db.run_migration(MigrateCommand::Revert, None, false)
.success();
assert_eq!(db.applied_migrations().await, all_migrations[..3]);
}
// With --target-version
{
let db = TestDatabase::new("migrate_revert_incremental", "migrations_reversible");
db.run_migration(false, None, false).success();
db.run_migration(MigrateCommand::Run, None, false).success();

// Dry-run downgrade to version 3.
db.run_migration(true, Some(20230301000000), true).success();
db.run_migration(MigrateCommand::Revert, Some(20230301000000), true)
.success();
assert_eq!(db.applied_migrations().await, all_migrations);

// Downgrade to version 3.
db.run_migration(true, Some(20230301000000), false)
db.run_migration(MigrateCommand::Revert, Some(20230301000000), false)
.success();
assert_eq!(db.applied_migrations().await, all_migrations[..3]);

// Try downgrading to the same version.
db.run_migration(true, Some(20230301000000), false)
db.run_migration(MigrateCommand::Revert, Some(20230301000000), false)
.success();
assert_eq!(db.applied_migrations().await, all_migrations[..3]);

// Try downgrading to a newer version.
db.run_migration(true, Some(20230401000000), false)
db.run_migration(MigrateCommand::Revert, Some(20230401000000), false)
.failure();
assert_eq!(db.applied_migrations().await, all_migrations[..3]);

// Try downgrading to a non-existent version.
db.run_migration(true, Some(9999), false).failure();
db.run_migration(MigrateCommand::Revert, Some(9999), false)
.failure();
assert_eq!(db.applied_migrations().await, all_migrations[..3]);

// Ensure we can still upgrade
db.run_migration(false, Some(20230401000000), false)
db.run_migration(MigrateCommand::Run, Some(20230401000000), false)
.success();
assert_eq!(db.applied_migrations().await, all_migrations[..4]);

// Downgrade to zero.
db.run_migration(true, Some(0), false).success();
db.run_migration(MigrateCommand::Revert, Some(0), false)
.success();
assert_eq!(db.applied_migrations().await, vec![] as Vec<i64>);
}
}
#[tokio::test]
async fn skip_reversible_migrations() {
let all_migrations: Vec<i64> = vec![
20230101000000,
20230201000000,
20230301000000,
20230401000000,
20230501000000,
];
// Without --target-version specified.
{
let db = TestDatabase::new("migrate_skip_reversible_latest", "migrations_reversible");
db.run_migration(MigrateCommand::Skip, None, false)
.success();
assert_eq!(db.applied_migrations().await, all_migrations);
}
// With --target-version specified.
{
let db = TestDatabase::new(
"migrate_skip_reversible_latest_explicit",
"migrations_reversible",
);

// Move to latest, explicitly specified.
db.run_migration(MigrateCommand::Run, Some(20230501000000), false)
.success();
assert_eq!(db.applied_migrations().await, all_migrations);

// Skip to latest when we're already at the latest.
db.run_migration(MigrateCommand::Skip, Some(20230501000000), false)
.success();
assert_eq!(db.applied_migrations().await, all_migrations);

// Upgrade to an old version.
db.run_migration(MigrateCommand::Skip, Some(20230301000000), false)
.failure();
assert_eq!(db.applied_migrations().await, all_migrations);
}
// With --target-version, incrementally upgrade.
{
let db = TestDatabase::new(
"migrate_skip_reversible_incremental",
"migrations_reversible",
);

// Run first version
db.run_migration(MigrateCommand::Run, Some(20230101000000), false)
.success();
assert_eq!(db.applied_migrations().await, vec![20230101000000]);

// Skip and dry run upgrade to latest.
db.run_migration(MigrateCommand::Skip, None, true).success();
assert_eq!(db.applied_migrations().await, vec![20230101000000]);

// Skip and dry run upgrade + 2
db.run_migration(MigrateCommand::Skip, Some(20230301000000), true)
.success();
assert_eq!(db.applied_migrations().await, vec![20230101000000]);

// Skip to to non-existent version.
db.run_migration(MigrateCommand::Skip, Some(20230901000000999), false)
.failure();
assert_eq!(db.applied_migrations().await, vec![20230101000000]);

// Upgrade + 1
db.run_migration(MigrateCommand::Run, Some(20230201000000), false)
.success();
assert_eq!(
db.applied_migrations().await,
vec![20230101000000, 20230201000000]
);

// Skip + 2
db.run_migration(MigrateCommand::Skip, Some(20230401000000), false)
.success();
assert_eq!(db.applied_migrations().await, all_migrations[..4]);
}
}
3 changes: 2 additions & 1 deletion sqlx-core/src/any/migrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ impl Migrate for AnyConnection {
fn apply<'e: 'm, 'm>(
&'e mut self,
migration: &'m Migration,
skip: bool,
) -> BoxFuture<'m, Result<Duration, MigrateError>> {
Box::pin(async { self.get_migrate()?.apply(migration).await })
Box::pin(async move { self.get_migrate()?.apply(migration, skip).await })
}

fn revert<'e: 'm, 'm>(
Expand Down
Loading
Loading