From 84f21e99ef3a23236a579fab5b2d969fbc7f18d5 Mon Sep 17 00:00:00 2001 From: Ameer Ghani Date: Mon, 31 Jul 2023 15:49:53 -0400 Subject: [PATCH] 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. --- .github/workflows/sqlx.yml | 22 +++ Cargo.lock | 53 ++++++- sqlx-cli/Cargo.toml | 3 + sqlx-cli/src/database.rs | 2 +- sqlx-cli/src/lib.rs | 24 ++- sqlx-cli/src/migrate.rs | 73 ++++++++- sqlx-cli/src/opt.rs | 11 ++ sqlx-cli/src/prepare.rs | 2 +- sqlx-cli/tests/common/mod.rs | 93 +++++++++++ sqlx-cli/tests/migrate.rs | 147 ++++++++++++++++++ .../20230101000000_test1.down.sql | 1 + .../20230101000000_test1.up.sql | 1 + .../20230201000000_test2.down.sql | 1 + .../20230201000000_test2.up.sql | 1 + .../20230301000000_test3.down.sql | 1 + .../20230301000000_test3.up.sql | 1 + .../20230401000000_test4.down.sql | 1 + .../20230401000000_test4.up.sql | 1 + .../20230501000000_test5.down.sql | 1 + .../20230501000000_test5.up.sql | 1 + sqlx-core/src/migrate/error.rs | 9 ++ 21 files changed, 438 insertions(+), 11 deletions(-) create mode 100644 sqlx-cli/tests/common/mod.rs create mode 100644 sqlx-cli/tests/migrate.rs create mode 100644 sqlx-cli/tests/migrations_reversible/20230101000000_test1.down.sql create mode 100644 sqlx-cli/tests/migrations_reversible/20230101000000_test1.up.sql create mode 100644 sqlx-cli/tests/migrations_reversible/20230201000000_test2.down.sql create mode 100644 sqlx-cli/tests/migrations_reversible/20230201000000_test2.up.sql create mode 100644 sqlx-cli/tests/migrations_reversible/20230301000000_test3.down.sql create mode 100644 sqlx-cli/tests/migrations_reversible/20230301000000_test3.up.sql create mode 100644 sqlx-cli/tests/migrations_reversible/20230401000000_test4.down.sql create mode 100644 sqlx-cli/tests/migrations_reversible/20230401000000_test4.up.sql create mode 100644 sqlx-cli/tests/migrations_reversible/20230501000000_test5.down.sql create mode 100644 sqlx-cli/tests/migrations_reversible/20230501000000_test5.up.sql diff --git a/.github/workflows/sqlx.yml b/.github/workflows/sqlx.yml index b32cdea879..d6c021ecfe 100644 --- a/.github/workflows/sqlx.yml +++ b/.github/workflows/sqlx.yml @@ -90,6 +90,28 @@ jobs: --manifest-path sqlx-core/Cargo.toml --features json,_rt-${{ matrix.runtime }},_tls-${{ matrix.tls }} + cli-test: + name: CLI Unit Test + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + + - uses: Swatinem/rust-cache@v1 + with: + key: ${{ runner.os }}-test + + - uses: actions-rs/cargo@v1 + with: + command: test + args: > + --manifest-path sqlx-cli/Cargo.toml + cli: name: CLI Binaries runs-on: ${{ matrix.os }} diff --git a/Cargo.lock b/Cargo.lock index 12ecb647ee..b1cdddedfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -130,6 +130,21 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +[[package]] +name = "assert_cmd" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86d6b683edf8d1119fe420a94f8a7e389239666aa72e65495d91c00462510151" +dependencies = [ + "anstyle", + "bstr", + "doc-comment", + "predicates 3.0.3", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + [[package]] name = "async-attributes" version = "1.1.2" @@ -495,6 +510,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ffdb39cb703212f3c11973452c2861b972f757b021158f3516ba10f2fa8b2c1" dependencies = [ "memchr", + "once_cell", + "regex-automata", "serde", ] @@ -1096,6 +1113,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "dotenvy" version = "0.15.6" @@ -1931,7 +1954,7 @@ dependencies = [ "fragile", "lazy_static", "mockall_derive", - "predicates", + "predicates 2.1.5", "predicates-tree", ] @@ -2326,6 +2349,18 @@ dependencies = [ "regex", ] +[[package]] +name = "predicates" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09963355b9f467184c04017ced4a2ba2d75cbcb4e7462690d388233253d4b1a9" +dependencies = [ + "anstyle", + "difflib", + "itertools", + "predicates-core", +] + [[package]] name = "predicates-core" version = "1.0.6" @@ -2563,6 +2598,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + [[package]] name = "regex-syntax" version = "0.6.27" @@ -3072,6 +3113,7 @@ name = "sqlx-cli" version = "0.7.1" dependencies = [ "anyhow", + "assert_cmd", "async-trait", "backoff", "cargo_metadata", @@ -3979,6 +4021,15 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "waker-fn" version = "1.1.0" diff --git a/sqlx-cli/Cargo.toml b/sqlx-cli/Cargo.toml index 14dc9c0bb3..5f8597c467 100644 --- a/sqlx-cli/Cargo.toml +++ b/sqlx-cli/Cargo.toml @@ -66,3 +66,6 @@ sqlite = ["sqlx/sqlite"] openssl-vendored = ["openssl/vendored"] completions = ["dep:clap_complete"] + +[dev-dependencies] +assert_cmd = "2.0.11" diff --git a/sqlx-cli/src/database.rs b/sqlx-cli/src/database.rs index 0a5461a97b..5df9af5f7c 100644 --- a/sqlx-cli/src/database.rs +++ b/sqlx-cli/src/database.rs @@ -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 { diff --git a/sqlx-cli/src/lib.rs b/sqlx-cli/src/lib.rs index 8dbf95735f..aec2392f75 100644 --- a/sqlx-cli/src/lib.rs +++ b/sqlx-cli/src/lib.rs @@ -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, diff --git a/sqlx-cli/src/migrate.rs b/sqlx-cli/src/migrate.rs index 5e94f26f41..51a756d3c7 100644 --- a/sqlx-cli/src/migrate.rs +++ b/sqlx-cli/src/migrate.rs @@ -267,8 +267,15 @@ pub async fn run( connect_opts: &ConnectOpts, dry_run: bool, ignore_missing: bool, + target_version: Option, ) -> 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, ) -> 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 { diff --git a/sqlx-cli/src/opt.rs b/sqlx-cli/src/opt.rs index a72abc983c..7d5158507c 100644 --- a/sqlx-cli/src/opt.rs +++ b/sqlx-cli/src/opt.rs @@ -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, }, /// 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, }, /// List all available migrations. diff --git a/sqlx-cli/src/prepare.rs b/sqlx-cli/src/prepare.rs index 5ecaabcb5f..21fd9aca20 100644 --- a/sqlx-cli/src/prepare.rs +++ b/sqlx-cli/src/prepare.rs @@ -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 { diff --git a/sqlx-cli/tests/common/mod.rs b/sqlx-cli/tests/common/mod.rs new file mode 100644 index 0000000000..43c0dbc1e1 --- /dev/null +++ b/sqlx-cli/tests/common/mod.rs @@ -0,0 +1,93 @@ +use assert_cmd::{assert::Assert, Command}; + +use sqlx::{migrate::Migrate, Connection, SqliteConnection}; +use std::{ + env::temp_dir, + fs::remove_file, + path::{Path, PathBuf}, +}; + +pub struct TestDatabase { + file_path: PathBuf, + migrations: String, +} + +impl TestDatabase { + pub fn new(name: &str, migrations: &str) -> Self { + let migrations_path = Path::new("tests").join(migrations); + let file_path = Path::new(&temp_dir()).join(format!("test-{}.db", name)); + let ret = Self { + file_path, + migrations: String::from(migrations_path.to_str().unwrap()), + }; + Command::cargo_bin("cargo-sqlx") + .unwrap() + .args([ + "sqlx", + "database", + "create", + "--database-url", + &ret.connection_string(), + ]) + .assert() + .success(); + ret + } + + pub fn connection_string(&self) -> String { + format!("sqlite://{}", self.file_path.display()) + } + + pub fn run_migration(&self, revert: bool, version: Option, dry_run: bool) -> Assert { + let ver = match version { + Some(v) => v.to_string(), + None => String::from(""), + }; + Command::cargo_bin("cargo-sqlx") + .unwrap() + .args( + [ + vec![ + "sqlx", + "migrate", + match revert { + true => "revert", + false => "run", + }, + "--database-url", + &self.connection_string(), + "--source", + &self.migrations, + ], + match version { + Some(_) => vec!["--target-version", &ver], + None => vec![], + }, + match dry_run { + true => vec!["--dry-run"], + false => vec![], + }, + ] + .concat(), + ) + .assert() + } + + pub async fn applied_migrations(&self) -> Vec { + let mut conn = SqliteConnection::connect(&self.connection_string()) + .await + .unwrap(); + conn.list_applied_migrations() + .await + .unwrap() + .iter() + .map(|m| m.version) + .collect() + } +} + +impl Drop for TestDatabase { + fn drop(&mut self) { + remove_file(&self.file_path).unwrap(); + } +} diff --git a/sqlx-cli/tests/migrate.rs b/sqlx-cli/tests/migrate.rs new file mode 100644 index 0000000000..0ea9d4620d --- /dev/null +++ b/sqlx-cli/tests/migrate.rs @@ -0,0 +1,147 @@ +mod common; + +use common::TestDatabase; + +#[tokio::test] +async fn run_reversible_migrations() { + let all_migrations: Vec = vec![ + 20230101000000, + 20230201000000, + 20230301000000, + 20230401000000, + 20230501000000, + ]; + // Without --target-version specified.k + { + let db = TestDatabase::new("migrate_run_reversible_latest", "migrations_reversible"); + db.run_migration(false, None, false).success(); + assert_eq!(db.applied_migrations().await, all_migrations); + } + // With --target-version specified. + { + let db = TestDatabase::new( + "migrate_run_reversible_latest_explicit", + "migrations_reversible", + ); + + // Move to latest, explicitly specified. + db.run_migration(false, 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) + .success(); + assert_eq!(db.applied_migrations().await, all_migrations); + + // Upgrade to an old version. + db.run_migration(false, Some(20230301000000), false) + .failure(); + assert_eq!(db.applied_migrations().await, all_migrations); + } + // With --target-version, incrementally upgrade. + { + let db = TestDatabase::new( + "migrate_run_reversible_incremental", + "migrations_reversible", + ); + + // First version + db.run_migration(false, Some(20230101000000), false) + .success(); + assert_eq!(db.applied_migrations().await, vec![20230101000000]); + + // Dry run upgrade to latest. + db.run_migration(false, None, true).success(); + assert_eq!(db.applied_migrations().await, vec![20230101000000]); + + // Dry run upgrade + 2 + db.run_migration(false, Some(20230301000000), true) + .success(); + assert_eq!(db.applied_migrations().await, vec![20230101000000]); + + // Upgrade to non-existent version. + db.run_migration(false, Some(20230901000000999), false) + .failure(); + assert_eq!(db.applied_migrations().await, vec![20230101000000]); + + // Upgrade + 1 + db.run_migration(false, Some(20230201000000), false) + .success(); + assert_eq!( + db.applied_migrations().await, + vec![20230101000000, 20230201000000] + ); + + // Upgrade + 2 + db.run_migration(false, Some(20230401000000), false) + .success(); + assert_eq!(db.applied_migrations().await, all_migrations[..4]); + } +} + +#[tokio::test] +async fn revert_migrations() { + let all_migrations: Vec = vec![ + 20230101000000, + 20230201000000, + 20230301000000, + 20230401000000, + 20230501000000, + ]; + + // Without --target-version + { + let db = TestDatabase::new("migrate_revert_incremental", "migrations_reversible"); + db.run_migration(false, None, false).success(); + + // Dry-run + db.run_migration(true, None, true).success(); + assert_eq!(db.applied_migrations().await, all_migrations); + + // Downgrade one + db.run_migration(true, None, false).success(); + assert_eq!(db.applied_migrations().await, all_migrations[..4]); + + // Downgrade one + db.run_migration(true, 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(); + + // Dry-run downgrade to version 3. + db.run_migration(true, Some(20230301000000), true).success(); + assert_eq!(db.applied_migrations().await, all_migrations); + + // Downgrade to version 3. + db.run_migration(true, 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) + .success(); + assert_eq!(db.applied_migrations().await, all_migrations[..3]); + + // Try downgrading to a newer version. + db.run_migration(true, 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(); + assert_eq!(db.applied_migrations().await, all_migrations[..3]); + + // Ensure we can still upgrade + db.run_migration(false, Some(20230401000000), false) + .success(); + assert_eq!(db.applied_migrations().await, all_migrations[..4]); + + // Downgrade to zero. + db.run_migration(true, Some(0), false).success(); + assert_eq!(db.applied_migrations().await, vec![] as Vec); + } +} diff --git a/sqlx-cli/tests/migrations_reversible/20230101000000_test1.down.sql b/sqlx-cli/tests/migrations_reversible/20230101000000_test1.down.sql new file mode 100644 index 0000000000..c7719eeac2 --- /dev/null +++ b/sqlx-cli/tests/migrations_reversible/20230101000000_test1.down.sql @@ -0,0 +1 @@ +DROP TABLE test1; diff --git a/sqlx-cli/tests/migrations_reversible/20230101000000_test1.up.sql b/sqlx-cli/tests/migrations_reversible/20230101000000_test1.up.sql new file mode 100644 index 0000000000..e779776230 --- /dev/null +++ b/sqlx-cli/tests/migrations_reversible/20230101000000_test1.up.sql @@ -0,0 +1 @@ +CREATE TABLE test1(x INTEGER PRIMARY KEY); diff --git a/sqlx-cli/tests/migrations_reversible/20230201000000_test2.down.sql b/sqlx-cli/tests/migrations_reversible/20230201000000_test2.down.sql new file mode 100644 index 0000000000..815195d940 --- /dev/null +++ b/sqlx-cli/tests/migrations_reversible/20230201000000_test2.down.sql @@ -0,0 +1 @@ +DROP TABLE test2; diff --git a/sqlx-cli/tests/migrations_reversible/20230201000000_test2.up.sql b/sqlx-cli/tests/migrations_reversible/20230201000000_test2.up.sql new file mode 100644 index 0000000000..e6538a3b72 --- /dev/null +++ b/sqlx-cli/tests/migrations_reversible/20230201000000_test2.up.sql @@ -0,0 +1 @@ +CREATE TABLE test2(x INTEGER PRIMARY KEY); diff --git a/sqlx-cli/tests/migrations_reversible/20230301000000_test3.down.sql b/sqlx-cli/tests/migrations_reversible/20230301000000_test3.down.sql new file mode 100644 index 0000000000..7a77c5a573 --- /dev/null +++ b/sqlx-cli/tests/migrations_reversible/20230301000000_test3.down.sql @@ -0,0 +1 @@ +DROP TABLE test3; diff --git a/sqlx-cli/tests/migrations_reversible/20230301000000_test3.up.sql b/sqlx-cli/tests/migrations_reversible/20230301000000_test3.up.sql new file mode 100644 index 0000000000..322e48de82 --- /dev/null +++ b/sqlx-cli/tests/migrations_reversible/20230301000000_test3.up.sql @@ -0,0 +1 @@ +CREATE TABLE test3(x INTEGER PRIMARY KEY); diff --git a/sqlx-cli/tests/migrations_reversible/20230401000000_test4.down.sql b/sqlx-cli/tests/migrations_reversible/20230401000000_test4.down.sql new file mode 100644 index 0000000000..3e0b8a29bd --- /dev/null +++ b/sqlx-cli/tests/migrations_reversible/20230401000000_test4.down.sql @@ -0,0 +1 @@ +DROP TABLE test4; diff --git a/sqlx-cli/tests/migrations_reversible/20230401000000_test4.up.sql b/sqlx-cli/tests/migrations_reversible/20230401000000_test4.up.sql new file mode 100644 index 0000000000..825436733d --- /dev/null +++ b/sqlx-cli/tests/migrations_reversible/20230401000000_test4.up.sql @@ -0,0 +1 @@ +CREATE TABLE test4(x INTEGER PRIMARY KEY); diff --git a/sqlx-cli/tests/migrations_reversible/20230501000000_test5.down.sql b/sqlx-cli/tests/migrations_reversible/20230501000000_test5.down.sql new file mode 100644 index 0000000000..2959c8d6bc --- /dev/null +++ b/sqlx-cli/tests/migrations_reversible/20230501000000_test5.down.sql @@ -0,0 +1 @@ +DROP TABLE test5; diff --git a/sqlx-cli/tests/migrations_reversible/20230501000000_test5.up.sql b/sqlx-cli/tests/migrations_reversible/20230501000000_test5.up.sql new file mode 100644 index 0000000000..fa8ec16406 --- /dev/null +++ b/sqlx-cli/tests/migrations_reversible/20230501000000_test5.up.sql @@ -0,0 +1 @@ +CREATE TABLE test5(x INTEGER PRIMARY KEY); diff --git a/sqlx-core/src/migrate/error.rs b/sqlx-core/src/migrate/error.rs index 36dbe81133..d76f97387f 100644 --- a/sqlx-core/src/migrate/error.rs +++ b/sqlx-core/src/migrate/error.rs @@ -15,6 +15,15 @@ pub enum MigrateError { #[error("migration {0} was previously applied but has been modified")] VersionMismatch(i64), + #[error("migration {0} is not present in the migration source")] + VersionNotPresent(i64), + + #[error("migration {0} is older than the latest applied migration {1}")] + VersionTooOld(i64, i64), + + #[error("migration {0} is newer than the latest applied migration {1}")] + VersionTooNew(i64, i64), + #[error("cannot mix reversible migrations with simple migrations. All migrations should be reversible or simple migrations")] InvalidMixReversibleAndSimple,