From 4d6fc175e0821e88f9a3ccfcb95505a1076e2111 Mon Sep 17 00:00:00 2001 From: Marco Neumann Date: Thu, 21 Jul 2022 10:44:50 +0200 Subject: [PATCH 01/10] fix: ensure migration progress is not lost for PG Fixes #1966. --- sqlx-core/src/postgres/migrate.rs | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/sqlx-core/src/postgres/migrate.rs b/sqlx-core/src/postgres/migrate.rs index 71084e72f7..cdd6096f2f 100644 --- a/sqlx-core/src/postgres/migrate.rs +++ b/sqlx-core/src/postgres/migrate.rs @@ -222,23 +222,44 @@ CREATE TABLE IF NOT EXISTS _sqlx_migrations ( let mut tx = self.begin().await?; let start = Instant::now(); + // Use a single transaction for the actual migration script and the essential bookeeping so we never + // execute migrations twice. See https://github.com/launchbadge/sqlx/issues/1966. + // The `execution_time` however can only be measured for the whole transaction. This value _only_ exists for + // data lineage and debugging reasons, so it is not super important if it is lost. So we initialize it to -1 + // and update it once the actual transaction completed. let _ = tx.execute(&*migration.sql).await?; - tx.commit().await?; - - let elapsed = start.elapsed(); - // language=SQL let _ = query( r#" INSERT INTO _sqlx_migrations ( version, description, success, checksum, execution_time ) - VALUES ( $1, $2, TRUE, $3, $4 ) + VALUES ( $1, $2, TRUE, $3, -1 ) "#, ) .bind(migration.version) .bind(&*migration.description) .bind(&*migration.checksum) + .execute(&mut tx) + .await?; + + tx.commit().await?; + + // Update `elapsed_time`. + // NOTE: The process may disconnect/die at this point, so the elapsed time value might be lost. We accept + // this small risk since this value is not super important. + + let elapsed = start.elapsed(); + + // language=SQL + let _ = query( + r#" + UPDATE _sqlx_migrations + SET execution_time = $1 + WHERE version = $2 + "#, + ) .bind(elapsed.as_nanos() as i64) + .bind(migration.version) .execute(self) .await?; From 17b99c5b8f28e05b2bd0018f20660fbcb7a45816 Mon Sep 17 00:00:00 2001 From: Marco Neumann Date: Thu, 21 Jul 2022 10:50:33 +0200 Subject: [PATCH 02/10] fix: ensure migration progress is not lost for sqlite This is similar to #1966. --- sqlx-core/src/sqlite/migrate.rs | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/sqlx-core/src/sqlite/migrate.rs b/sqlx-core/src/sqlite/migrate.rs index d8f18e57e1..66c8edd67f 100644 --- a/sqlx-core/src/sqlite/migrate.rs +++ b/sqlx-core/src/sqlite/migrate.rs @@ -173,23 +173,44 @@ CREATE TABLE IF NOT EXISTS _sqlx_migrations ( let mut tx = self.begin().await?; let start = Instant::now(); + // Use a single transaction for the actual migration script and the essential bookeeping so we never + // execute migrations twice. See https://github.com/launchbadge/sqlx/issues/1966. + // The `execution_time` however can only be measured for the whole transaction. This value _only_ exists for + // data lineage and debugging reasons, so it is not super important if it is lost. So we initialize it to -1 + // and update it once the actual transaction completed. let _ = tx.execute(&*migration.sql).await?; - tx.commit().await?; - - let elapsed = start.elapsed(); - // language=SQL let _ = query( r#" INSERT INTO _sqlx_migrations ( version, description, success, checksum, execution_time ) - VALUES ( ?1, ?2, TRUE, ?3, ?4 ) + VALUES ( ?1, ?2, TRUE, ?3, -1 ) "#, ) .bind(migration.version) .bind(&*migration.description) .bind(&*migration.checksum) + .execute(&mut tx) + .await?; + + tx.commit().await?; + + // Update `elapsed_time`. + // NOTE: The process may disconnect/die at this point, so the elapsed time value might be lost. We accept + // this small risk since this value is not super important. + + let elapsed = start.elapsed(); + + // language=SQL + let _ = query( + r#" + UPDATE _sqlx_migrations + SET execution_time = ?1 + WHERE version = ?2 + "#, + ) .bind(elapsed.as_nanos() as i64) + .bind(migration.version) .execute(self) .await?; From 8138065a70dcabf4e2c17ba5b54a8e9e9337bdb7 Mon Sep 17 00:00:00 2001 From: Marco Neumann Date: Thu, 21 Jul 2022 10:59:19 +0200 Subject: [PATCH 03/10] fix: ensure reverse migration progress is not lost for PG See #1966. --- sqlx-core/src/postgres/migrate.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/sqlx-core/src/postgres/migrate.rs b/sqlx-core/src/postgres/migrate.rs index cdd6096f2f..183b193b51 100644 --- a/sqlx-core/src/postgres/migrate.rs +++ b/sqlx-core/src/postgres/migrate.rs @@ -272,21 +272,23 @@ CREATE TABLE IF NOT EXISTS _sqlx_migrations ( migration: &'m Migration, ) -> BoxFuture<'m, Result> { Box::pin(async move { + // Use a single transaction for the actual migration script and the essential bookeeping so we never + // execute migrations twice. See https://github.com/launchbadge/sqlx/issues/1966. let mut tx = self.begin().await?; let start = Instant::now(); let _ = tx.execute(&*migration.sql).await?; - tx.commit().await?; - - let elapsed = start.elapsed(); - // language=SQL let _ = query(r#"DELETE FROM _sqlx_migrations WHERE version = $1"#) .bind(migration.version) - .execute(self) + .execute(&mut tx) .await?; + tx.commit().await?; + + let elapsed = start.elapsed(); + Ok(elapsed) }) } From 7c94461dbd8c50d18925961e7034a55f495bff83 Mon Sep 17 00:00:00 2001 From: Marco Neumann Date: Thu, 21 Jul 2022 11:00:49 +0200 Subject: [PATCH 04/10] fix: ensure reverse migration progress is not lost for sqlite See #1966. --- sqlx-core/src/sqlite/migrate.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/sqlx-core/src/sqlite/migrate.rs b/sqlx-core/src/sqlite/migrate.rs index 66c8edd67f..c07f6c8164 100644 --- a/sqlx-core/src/sqlite/migrate.rs +++ b/sqlx-core/src/sqlite/migrate.rs @@ -223,21 +223,23 @@ CREATE TABLE IF NOT EXISTS _sqlx_migrations ( migration: &'m Migration, ) -> BoxFuture<'m, Result> { Box::pin(async move { + // Use a single transaction for the actual migration script and the essential bookeeping so we never + // execute migrations twice. See https://github.com/launchbadge/sqlx/issues/1966. let mut tx = self.begin().await?; let start = Instant::now(); let _ = tx.execute(&*migration.sql).await?; - tx.commit().await?; - - let elapsed = start.elapsed(); - // language=SQL let _ = query(r#"DELETE FROM _sqlx_migrations WHERE version = ?1"#) .bind(migration.version) - .execute(self) + .execute(&mut tx) .await?; + tx.commit().await?; + + let elapsed = start.elapsed(); + Ok(elapsed) }) } From d1bffe7a4787dd62fcaf66f4721b6352c20b3abb Mon Sep 17 00:00:00 2001 From: Marco Neumann Date: Thu, 21 Jul 2022 11:05:34 +0200 Subject: [PATCH 05/10] fix: ensure migration progress is not lost for mysql This is similar to #1966. --- sqlx-core/src/mysql/migrate.rs | 38 ++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/sqlx-core/src/mysql/migrate.rs b/sqlx-core/src/mysql/migrate.rs index 800d7408dc..56cc25ca70 100644 --- a/sqlx-core/src/mysql/migrate.rs +++ b/sqlx-core/src/mysql/migrate.rs @@ -1,4 +1,4 @@ -use crate::connection::ConnectOptions; +use crate::connection::{ConnectOptions, Connection}; use crate::error::Error; use crate::executor::Executor; use crate::migrate::MigrateError; @@ -209,29 +209,49 @@ CREATE TABLE IF NOT EXISTS _sqlx_migrations ( migration: &'m Migration, ) -> BoxFuture<'m, Result> { Box::pin(async move { + // Use a single transaction for the actual migration script and the essential bookeeping so we never + // execute migrations twice. See https://github.com/launchbadge/sqlx/issues/1966. + // The `execution_time` however can only be measured for the whole transaction. This value _only_ exists for + // data lineage and debugging reasons, so it is not super important if it is lost. So we initialize it to -1 + // and update it once the actual transaction completed. + let mut tx = self.begin().await?; let start = Instant::now(); - let res = self.execute(&*migration.sql).await; - - let elapsed = start.elapsed(); - // language=MySQL let _ = query( r#" INSERT INTO _sqlx_migrations ( version, description, success, checksum, execution_time ) - VALUES ( ?, ?, ?, ?, ? ) + VALUES ( ?, ?, TRUE, ?, -1 ) "#, ) .bind(migration.version) .bind(&*migration.description) - .bind(res.is_ok()) .bind(&*migration.checksum) + .execute(&mut tx) + .await?; + + let _ = tx.execute(&*migration.sql).await?; + + tx.commit().await?; + + // Update `elapsed_time`. + // NOTE: The process may disconnect/die at this point, so the elapsed time value might be lost. We accept + // this small risk since this value is not super important. + + let elapsed = start.elapsed(); + + let _ = query( + r#" + UPDATE _sqlx_migrations + SET execution_time = ? + WHERE version = ? + "#, + ) .bind(elapsed.as_nanos() as i64) + .bind(migration.version) .execute(self) .await?; - res?; - Ok(elapsed) }) } From aebacfd8d10849bf35445e52f3be8404c947b944 Mon Sep 17 00:00:00 2001 From: Marco Neumann Date: Thu, 21 Jul 2022 11:07:27 +0200 Subject: [PATCH 06/10] fix: ensure reverse migration progress is not lost for mysql See #1966. --- sqlx-core/src/mysql/migrate.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/sqlx-core/src/mysql/migrate.rs b/sqlx-core/src/mysql/migrate.rs index 56cc25ca70..e9a966a3fc 100644 --- a/sqlx-core/src/mysql/migrate.rs +++ b/sqlx-core/src/mysql/migrate.rs @@ -261,18 +261,23 @@ CREATE TABLE IF NOT EXISTS _sqlx_migrations ( migration: &'m Migration, ) -> BoxFuture<'m, Result> { Box::pin(async move { + // Use a single transaction for the actual migration script and the essential bookeeping so we never + // execute migrations twice. See https://github.com/launchbadge/sqlx/issues/1966. + let mut tx = self.begin().await?; let start = Instant::now(); - self.execute(&*migration.sql).await?; - - let elapsed = start.elapsed(); + tx.execute(&*migration.sql).await?; // language=SQL let _ = query(r#"DELETE FROM _sqlx_migrations WHERE version = ?"#) .bind(migration.version) - .execute(self) + .execute(&mut tx) .await?; + tx.commit().await?; + + let elapsed = start.elapsed(); + Ok(elapsed) }) } From 90302497e7a397d13202e5e823a0f94d3faeb488 Mon Sep 17 00:00:00 2001 From: Marco Neumann Date: Thu, 21 Jul 2022 14:25:25 +0200 Subject: [PATCH 07/10] test: check migration type as well --- sqlx-core/src/migrate/migration_type.rs | 2 +- tests/migrate/macro.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/sqlx-core/src/migrate/migration_type.rs b/sqlx-core/src/migrate/migration_type.rs index edabdbed5d..87feedfa2b 100644 --- a/sqlx-core/src/migrate/migration_type.rs +++ b/sqlx-core/src/migrate/migration_type.rs @@ -1,5 +1,5 @@ /// Migration Type represents the type of migration -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub enum MigrationType { /// Simple migration are single file migrations with no up / down queries Simple, diff --git a/tests/migrate/macro.rs b/tests/migrate/macro.rs index 7215046bef..720734fae8 100644 --- a/tests/migrate/macro.rs +++ b/tests/migrate/macro.rs @@ -12,6 +12,7 @@ async fn same_output() -> anyhow::Result<()> { for (e, r) in EMBEDDED.iter().zip(runtime.iter()) { assert_eq!(e.version, r.version); assert_eq!(e.description, r.description); + assert_eq!(e.migration_type, r.migration_type); assert_eq!(e.sql, r.sql); assert_eq!(e.checksum, r.checksum); } From 682ceea021c7580c6328f33e8df7daa2a44d0622 Mon Sep 17 00:00:00 2001 From: Marco Neumann Date: Thu, 28 Jul 2022 12:03:52 +0200 Subject: [PATCH 08/10] test: extend migrations testing --- Cargo.toml | 15 ++++ sqlx-test/src/lib.rs | 29 +++++++ tests/migrate/macro.rs | 20 +++-- .../migrations/20200723212833_tweet.sql | 6 -- .../migrations/20200723212841_accounts.sql | 5 -- .../20220721124650_add_table.down.sql | 1 + .../20220721124650_add_table.up.sql | 7 ++ .../20220721125033_modify_column.down.sql | 2 + .../20220721125033_modify_column.up.sql | 2 + .../20220721115250_add_test_table.sql | 7 ++ .../20220721115524_convert_type.sql | 34 ++++++++ tests/mysql/migrate.rs | 85 +++++++++++++++++++ .../20220721124650_add_table.down.sql | 1 + .../20220721124650_add_table.up.sql | 7 ++ .../20220721125033_modify_column.down.sql | 2 + .../20220721125033_modify_column.up.sql | 2 + .../20220721115250_add_test_table.sql | 7 ++ .../20220721115524_convert_type.sql | 34 ++++++++ tests/postgres/migrate.rs | 85 +++++++++++++++++++ .../20220721124650_add_table.down.sql | 1 + .../20220721124650_add_table.up.sql | 7 ++ .../20220721125033_modify_column.down.sql | 2 + .../20220721125033_modify_column.up.sql | 2 + .../20220721115250_add_test_table.sql | 7 ++ .../20220721115524_convert_type.sql | 34 ++++++++ tests/sqlite/migrate.rs | 85 +++++++++++++++++++ .../20220721124650_add_table.down.sql | 1 + .../20220721124650_add_table.up.sql | 7 ++ .../20220721125033_modify_column.down.sql | 2 + .../20220721125033_modify_column.up.sql | 2 + .../20220721115250_add_test_table.sql | 7 ++ .../20220721115524_convert_type.sql | 30 +++++++ 32 files changed, 521 insertions(+), 17 deletions(-) delete mode 100644 tests/migrate/migrations/20200723212833_tweet.sql delete mode 100644 tests/migrate/migrations/20200723212841_accounts.sql create mode 100644 tests/migrate/migrations_reversible/20220721124650_add_table.down.sql create mode 100644 tests/migrate/migrations_reversible/20220721124650_add_table.up.sql create mode 100644 tests/migrate/migrations_reversible/20220721125033_modify_column.down.sql create mode 100644 tests/migrate/migrations_reversible/20220721125033_modify_column.up.sql create mode 100644 tests/migrate/migrations_simple/20220721115250_add_test_table.sql create mode 100644 tests/migrate/migrations_simple/20220721115524_convert_type.sql create mode 100644 tests/mysql/migrate.rs create mode 100644 tests/mysql/migrations_reversible/20220721124650_add_table.down.sql create mode 100644 tests/mysql/migrations_reversible/20220721124650_add_table.up.sql create mode 100644 tests/mysql/migrations_reversible/20220721125033_modify_column.down.sql create mode 100644 tests/mysql/migrations_reversible/20220721125033_modify_column.up.sql create mode 100644 tests/mysql/migrations_simple/20220721115250_add_test_table.sql create mode 100644 tests/mysql/migrations_simple/20220721115524_convert_type.sql create mode 100644 tests/postgres/migrate.rs create mode 100644 tests/postgres/migrations_reversible/20220721124650_add_table.down.sql create mode 100644 tests/postgres/migrations_reversible/20220721124650_add_table.up.sql create mode 100644 tests/postgres/migrations_reversible/20220721125033_modify_column.down.sql create mode 100644 tests/postgres/migrations_reversible/20220721125033_modify_column.up.sql create mode 100644 tests/postgres/migrations_simple/20220721115250_add_test_table.sql create mode 100644 tests/postgres/migrations_simple/20220721115524_convert_type.sql create mode 100644 tests/sqlite/migrate.rs create mode 100644 tests/sqlite/migrations_reversible/20220721124650_add_table.down.sql create mode 100644 tests/sqlite/migrations_reversible/20220721124650_add_table.up.sql create mode 100644 tests/sqlite/migrations_reversible/20220721125033_modify_column.down.sql create mode 100644 tests/sqlite/migrations_reversible/20220721125033_modify_column.up.sql create mode 100644 tests/sqlite/migrations_simple/20220721115250_add_test_table.sql create mode 100644 tests/sqlite/migrations_simple/20220721115524_convert_type.sql diff --git a/Cargo.toml b/Cargo.toml index d01f3307eb..23e64a3789 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -203,6 +203,11 @@ name = "sqlite-test-attr" path = "tests/sqlite/test-attr.rs" required-features = ["sqlite", "macros", "migrate"] +[[test]] +name = "sqlite-migrate" +path = "tests/sqlite/migrate.rs" +required-features = ["sqlite", "macros", "migrate"] + # # MySQL # @@ -232,6 +237,11 @@ name = "mysql-test-attr" path = "tests/mysql/test-attr.rs" required-features = ["mysql", "macros", "migrate"] +[[test]] +name = "mysql-migrate" +path = "tests/mysql/migrate.rs" +required-features = ["mysql", "macros", "migrate"] + # # PostgreSQL # @@ -266,6 +276,11 @@ name = "postgres-test-attr" path = "tests/postgres/test-attr.rs" required-features = ["postgres", "macros", "migrate"] +[[test]] +name = "postgres-migrate" +path = "tests/postgres/migrate.rs" +required-features = ["postgres", "macros", "migrate"] + # # Microsoft SQL Server (MSSQL) # diff --git a/sqlx-test/src/lib.rs b/sqlx-test/src/lib.rs index 5ba0f6323f..cb23c81ae6 100644 --- a/sqlx-test/src/lib.rs +++ b/sqlx-test/src/lib.rs @@ -1,6 +1,7 @@ use sqlx::pool::PoolOptions; use sqlx::{Connection, Database, Pool}; use std::env; +use std::sync::atomic::{AtomicBool, Ordering}; pub fn setup_if_needed() { let _ = dotenvy::dotenv(); @@ -223,3 +224,31 @@ macro_rules! Postgres_query_for_test_prepared_type { "SELECT ({0} is not distinct from $1)::int4, {0}, $2" }; } + +/// Global lock that prevents multiple tests in this module to be executed at the same time. +static GLOBAL_LOCK: AtomicBool = AtomicBool::new(false); + +/// Simple lock guard that should not be used in production but that is `Send` (i.e. can easily be used with tokio). +/// +/// This may be helpful for tests that modify the database state, e.g. migrations. +pub struct SimpleLockGuard; + +impl SimpleLockGuard { + /// Acquire global lock. + pub fn acquire() -> Self { + loop { + let was_locked = GLOBAL_LOCK.fetch_or(true, Ordering::SeqCst); + if !was_locked { + break; + } + std::thread::sleep(std::time::Duration::from_millis(10)); + } + Self + } +} + +impl Drop for SimpleLockGuard { + fn drop(&mut self) { + GLOBAL_LOCK.store(false, Ordering::SeqCst); + } +} diff --git a/tests/migrate/macro.rs b/tests/migrate/macro.rs index 720734fae8..da7f901996 100644 --- a/tests/migrate/macro.rs +++ b/tests/migrate/macro.rs @@ -1,21 +1,29 @@ use sqlx::migrate::Migrator; use std::path::Path; -static EMBEDDED: Migrator = sqlx::migrate!("tests/migrate/migrations"); +static EMBEDDED_SIMPLE: Migrator = sqlx::migrate!("tests/migrate/migrations_simple"); +static EMBEDDED_REVERSIBLE: Migrator = sqlx::migrate!("tests/migrate/migrations_reversible"); #[sqlx_macros::test] async fn same_output() -> anyhow::Result<()> { - let runtime = Migrator::new(Path::new("tests/migrate/migrations")).await?; + let runtime_simple = Migrator::new(Path::new("tests/migrate/migrations_simple")).await?; + let runtime_reversible = + Migrator::new(Path::new("tests/migrate/migrations_reversible")).await?; - assert_eq!(runtime.migrations.len(), EMBEDDED.migrations.len()); + assert_same(&EMBEDDED_SIMPLE, &runtime_simple); + assert_same(&EMBEDDED_REVERSIBLE, &runtime_reversible); - for (e, r) in EMBEDDED.iter().zip(runtime.iter()) { + Ok(()) +} + +fn assert_same(embedded: &Migrator, runtime: &Migrator) { + assert_eq!(runtime.migrations.len(), embedded.migrations.len()); + + for (e, r) in embedded.iter().zip(runtime.iter()) { assert_eq!(e.version, r.version); assert_eq!(e.description, r.description); assert_eq!(e.migration_type, r.migration_type); assert_eq!(e.sql, r.sql); assert_eq!(e.checksum, r.checksum); } - - Ok(()) } diff --git a/tests/migrate/migrations/20200723212833_tweet.sql b/tests/migrate/migrations/20200723212833_tweet.sql deleted file mode 100644 index 45c09606c4..0000000000 --- a/tests/migrate/migrations/20200723212833_tweet.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE tweet ( - id BIGINT NOT NULL PRIMARY KEY, - text TEXT NOT NULL, - is_sent BOOLEAN NOT NULL DEFAULT TRUE, - owner_id BIGINT -); diff --git a/tests/migrate/migrations/20200723212841_accounts.sql b/tests/migrate/migrations/20200723212841_accounts.sql deleted file mode 100644 index f2c0a73929..0000000000 --- a/tests/migrate/migrations/20200723212841_accounts.sql +++ /dev/null @@ -1,5 +0,0 @@ -CREATE TABLE accounts ( - id INTEGER NOT NULL PRIMARY KEY, - name TEXT NOT NULL, - is_active BOOLEAN -); diff --git a/tests/migrate/migrations_reversible/20220721124650_add_table.down.sql b/tests/migrate/migrations_reversible/20220721124650_add_table.down.sql new file mode 100644 index 0000000000..5505859725 --- /dev/null +++ b/tests/migrate/migrations_reversible/20220721124650_add_table.down.sql @@ -0,0 +1 @@ +DROP TABLE migrations_reversible_test; diff --git a/tests/migrate/migrations_reversible/20220721124650_add_table.up.sql b/tests/migrate/migrations_reversible/20220721124650_add_table.up.sql new file mode 100644 index 0000000000..9dfc757954 --- /dev/null +++ b/tests/migrate/migrations_reversible/20220721124650_add_table.up.sql @@ -0,0 +1,7 @@ +CREATE TABLE migrations_reversible_test ( + some_id BIGINT NOT NULL PRIMARY KEY, + some_payload BIGINT NOT NUll +); + +INSERT INTO migrations_reversible_test (some_id, some_payload) +VALUES (1, 100); diff --git a/tests/migrate/migrations_reversible/20220721125033_modify_column.down.sql b/tests/migrate/migrations_reversible/20220721125033_modify_column.down.sql new file mode 100644 index 0000000000..3f71737b8c --- /dev/null +++ b/tests/migrate/migrations_reversible/20220721125033_modify_column.down.sql @@ -0,0 +1,2 @@ +UPDATE migrations_reversible_test +SET some_payload = some_payload - 1; diff --git a/tests/migrate/migrations_reversible/20220721125033_modify_column.up.sql b/tests/migrate/migrations_reversible/20220721125033_modify_column.up.sql new file mode 100644 index 0000000000..bbb176cf41 --- /dev/null +++ b/tests/migrate/migrations_reversible/20220721125033_modify_column.up.sql @@ -0,0 +1,2 @@ +UPDATE migrations_reversible_test +SET some_payload = some_payload + 1; diff --git a/tests/migrate/migrations_simple/20220721115250_add_test_table.sql b/tests/migrate/migrations_simple/20220721115250_add_test_table.sql new file mode 100644 index 0000000000..d5ba291914 --- /dev/null +++ b/tests/migrate/migrations_simple/20220721115250_add_test_table.sql @@ -0,0 +1,7 @@ +CREATE TABLE migrations_simple_test ( + some_id BIGINT NOT NULL PRIMARY KEY, + some_payload BIGINT NOT NUll +); + +INSERT INTO migrations_simple_test (some_id, some_payload) +VALUES (1, 100); diff --git a/tests/migrate/migrations_simple/20220721115524_convert_type.sql b/tests/migrate/migrations_simple/20220721115524_convert_type.sql new file mode 100644 index 0000000000..8f6b04d6b0 --- /dev/null +++ b/tests/migrate/migrations_simple/20220721115524_convert_type.sql @@ -0,0 +1,34 @@ +-- Perform a tricky conversion of the payload. +-- +-- This script will only succeed once and will fail if executed twice. + +-- set up temporary target column +ALTER TABLE migrations_simple_test +ADD some_payload_tmp TEXT; + +-- perform conversion +-- This will fail if `some_payload` is already a string column due to the addition. +-- We add a suffix after the addition to ensure that the SQL database does not silently cast the string back to an +-- integer. +UPDATE migrations_simple_test +SET some_payload_tmp = CONCAT(CAST((some_payload + 10) AS TEXT), '_suffix'); + +-- remove original column including the content +ALTER TABLE migrations_simple_test +DROP COLUMN some_payload; + +-- prepare new payload column (nullable, so we can copy over the data) +ALTER TABLE migrations_simple_test +ADD some_payload TEXT; + +-- copy new values +UPDATE migrations_simple_test +SET some_payload = some_payload_tmp; + +-- "freeze" column +ALTER TABLE migrations_simple_test +ALTER COLUMN some_payload SET NOT NULL; + +-- clean up +ALTER TABLE migrations_simple_test +DROP COLUMN some_payload_tmp; diff --git a/tests/mysql/migrate.rs b/tests/mysql/migrate.rs new file mode 100644 index 0000000000..53963dd701 --- /dev/null +++ b/tests/mysql/migrate.rs @@ -0,0 +1,85 @@ +use sqlx::migrate::Migrator; +use sqlx::mysql::{MySql, MySqlConnection}; +use sqlx::Executor; +use sqlx::Row; +use std::path::Path; + +use sqlx_test::{new, SimpleLockGuard}; + +#[sqlx_macros::test] +async fn simple() -> anyhow::Result<()> { + let _guard = SimpleLockGuard::acquire(); + + let mut conn = new::().await?; + clean_up(&mut conn).await?; + + let migrator = Migrator::new(Path::new("tests/mysql/migrations_simple")).await?; + + // run migration + migrator.run(&mut conn).await?; + + // check outcome + let res: String = conn + .fetch_one("SELECT some_payload FROM migrations_simple_test") + .await? + .get(0); + assert_eq!(res, "110_suffix"); + + // running it a 2nd time should still work + migrator.run(&mut conn).await?; + + Ok(()) +} + +#[sqlx_macros::test] +async fn reversible() -> anyhow::Result<()> { + let _guard = SimpleLockGuard::acquire(); + + let mut conn = new::().await?; + clean_up(&mut conn).await?; + + let migrator = Migrator::new(Path::new("tests/mysql/migrations_reversible")).await?; + + // run migration + migrator.run(&mut conn).await?; + + // check outcome + let res: i64 = conn + .fetch_one("SELECT some_payload FROM migrations_reversible_test") + .await? + .get(0); + assert_eq!(res, 101); + + // roll back nothing (last version) + migrator.undo(&mut conn, 20220721125033).await?; + + // check outcome + let res: i64 = conn + .fetch_one("SELECT some_payload FROM migrations_reversible_test") + .await? + .get(0); + assert_eq!(res, 101); + + // roll back one version + migrator.undo(&mut conn, 20220721124650).await?; + + // check outcome + let res: i64 = conn + .fetch_one("SELECT some_payload FROM migrations_reversible_test") + .await? + .get(0); + assert_eq!(res, 100); + + Ok(()) +} + +/// Ensure that we have a clean initial state. +async fn clean_up(conn: &mut MySqlConnection) -> anyhow::Result<()> { + conn.execute("DROP TABLE migrations_simple_test").await.ok(); + conn.execute("DROP TABLE migrations_reversible_test") + .await + .ok(); + conn.execute("DROP TABLE _sqlx_migrations").await.ok(); + + Ok(()) +} diff --git a/tests/mysql/migrations_reversible/20220721124650_add_table.down.sql b/tests/mysql/migrations_reversible/20220721124650_add_table.down.sql new file mode 100644 index 0000000000..5505859725 --- /dev/null +++ b/tests/mysql/migrations_reversible/20220721124650_add_table.down.sql @@ -0,0 +1 @@ +DROP TABLE migrations_reversible_test; diff --git a/tests/mysql/migrations_reversible/20220721124650_add_table.up.sql b/tests/mysql/migrations_reversible/20220721124650_add_table.up.sql new file mode 100644 index 0000000000..9dfc757954 --- /dev/null +++ b/tests/mysql/migrations_reversible/20220721124650_add_table.up.sql @@ -0,0 +1,7 @@ +CREATE TABLE migrations_reversible_test ( + some_id BIGINT NOT NULL PRIMARY KEY, + some_payload BIGINT NOT NUll +); + +INSERT INTO migrations_reversible_test (some_id, some_payload) +VALUES (1, 100); diff --git a/tests/mysql/migrations_reversible/20220721125033_modify_column.down.sql b/tests/mysql/migrations_reversible/20220721125033_modify_column.down.sql new file mode 100644 index 0000000000..3f71737b8c --- /dev/null +++ b/tests/mysql/migrations_reversible/20220721125033_modify_column.down.sql @@ -0,0 +1,2 @@ +UPDATE migrations_reversible_test +SET some_payload = some_payload - 1; diff --git a/tests/mysql/migrations_reversible/20220721125033_modify_column.up.sql b/tests/mysql/migrations_reversible/20220721125033_modify_column.up.sql new file mode 100644 index 0000000000..bbb176cf41 --- /dev/null +++ b/tests/mysql/migrations_reversible/20220721125033_modify_column.up.sql @@ -0,0 +1,2 @@ +UPDATE migrations_reversible_test +SET some_payload = some_payload + 1; diff --git a/tests/mysql/migrations_simple/20220721115250_add_test_table.sql b/tests/mysql/migrations_simple/20220721115250_add_test_table.sql new file mode 100644 index 0000000000..d5ba291914 --- /dev/null +++ b/tests/mysql/migrations_simple/20220721115250_add_test_table.sql @@ -0,0 +1,7 @@ +CREATE TABLE migrations_simple_test ( + some_id BIGINT NOT NULL PRIMARY KEY, + some_payload BIGINT NOT NUll +); + +INSERT INTO migrations_simple_test (some_id, some_payload) +VALUES (1, 100); diff --git a/tests/mysql/migrations_simple/20220721115524_convert_type.sql b/tests/mysql/migrations_simple/20220721115524_convert_type.sql new file mode 100644 index 0000000000..03c9732d0b --- /dev/null +++ b/tests/mysql/migrations_simple/20220721115524_convert_type.sql @@ -0,0 +1,34 @@ +-- Perform a tricky conversion of the payload. +-- +-- This script will only succeed once and will fail if executed twice. + +-- set up temporary target column +ALTER TABLE migrations_simple_test +ADD some_payload_tmp TEXT; + +-- perform conversion +-- This will fail if `some_payload` is already a string column due to the addition. +-- We add a suffix after the addition to ensure that the SQL database does not silently cast the string back to an +-- integer. +UPDATE migrations_simple_test +SET some_payload_tmp = CONCAT(CAST((some_payload + 10) AS CHAR(3)), '_suffix'); + +-- remove original column including the content +ALTER TABLE migrations_simple_test +DROP COLUMN some_payload; + +-- prepare new payload column (nullable, so we can copy over the data) +ALTER TABLE migrations_simple_test +ADD some_payload TEXT; + +-- copy new values +UPDATE migrations_simple_test +SET some_payload = some_payload_tmp; + +-- "freeze" column +ALTER TABLE migrations_simple_test +MODIFY some_payload TEXT NOT NULL; + +-- clean up +ALTER TABLE migrations_simple_test +DROP COLUMN some_payload_tmp; diff --git a/tests/postgres/migrate.rs b/tests/postgres/migrate.rs new file mode 100644 index 0000000000..f02e452941 --- /dev/null +++ b/tests/postgres/migrate.rs @@ -0,0 +1,85 @@ +use sqlx::migrate::Migrator; +use sqlx::postgres::{PgConnection, Postgres}; +use sqlx::Executor; +use sqlx::Row; +use std::path::Path; + +use sqlx_test::{new, SimpleLockGuard}; + +#[sqlx_macros::test] +async fn simple() -> anyhow::Result<()> { + let _guard = SimpleLockGuard::acquire(); + + let mut conn = new::().await?; + clean_up(&mut conn).await?; + + let migrator = Migrator::new(Path::new("tests/postgres/migrations_simple")).await?; + + // run migration + migrator.run(&mut conn).await?; + + // check outcome + let res: String = conn + .fetch_one("SELECT some_payload FROM migrations_simple_test") + .await? + .get(0); + assert_eq!(res, "110_suffix"); + + // running it a 2nd time should still work + migrator.run(&mut conn).await?; + + Ok(()) +} + +#[sqlx_macros::test] +async fn reversible() -> anyhow::Result<()> { + let _guard = SimpleLockGuard::acquire(); + + let mut conn = new::().await?; + clean_up(&mut conn).await?; + + let migrator = Migrator::new(Path::new("tests/postgres/migrations_reversible")).await?; + + // run migration + migrator.run(&mut conn).await?; + + // check outcome + let res: i64 = conn + .fetch_one("SELECT some_payload FROM migrations_reversible_test") + .await? + .get(0); + assert_eq!(res, 101); + + // roll back nothing (last version) + migrator.undo(&mut conn, 20220721125033).await?; + + // check outcome + let res: i64 = conn + .fetch_one("SELECT some_payload FROM migrations_reversible_test") + .await? + .get(0); + assert_eq!(res, 101); + + // roll back one version + migrator.undo(&mut conn, 20220721124650).await?; + + // check outcome + let res: i64 = conn + .fetch_one("SELECT some_payload FROM migrations_reversible_test") + .await? + .get(0); + assert_eq!(res, 100); + + Ok(()) +} + +/// Ensure that we have a clean initial state. +async fn clean_up(conn: &mut PgConnection) -> anyhow::Result<()> { + conn.execute("DROP TABLE migrations_simple_test").await.ok(); + conn.execute("DROP TABLE migrations_reversible_test") + .await + .ok(); + conn.execute("DROP TABLE _sqlx_migrations").await.ok(); + + Ok(()) +} diff --git a/tests/postgres/migrations_reversible/20220721124650_add_table.down.sql b/tests/postgres/migrations_reversible/20220721124650_add_table.down.sql new file mode 100644 index 0000000000..5505859725 --- /dev/null +++ b/tests/postgres/migrations_reversible/20220721124650_add_table.down.sql @@ -0,0 +1 @@ +DROP TABLE migrations_reversible_test; diff --git a/tests/postgres/migrations_reversible/20220721124650_add_table.up.sql b/tests/postgres/migrations_reversible/20220721124650_add_table.up.sql new file mode 100644 index 0000000000..9dfc757954 --- /dev/null +++ b/tests/postgres/migrations_reversible/20220721124650_add_table.up.sql @@ -0,0 +1,7 @@ +CREATE TABLE migrations_reversible_test ( + some_id BIGINT NOT NULL PRIMARY KEY, + some_payload BIGINT NOT NUll +); + +INSERT INTO migrations_reversible_test (some_id, some_payload) +VALUES (1, 100); diff --git a/tests/postgres/migrations_reversible/20220721125033_modify_column.down.sql b/tests/postgres/migrations_reversible/20220721125033_modify_column.down.sql new file mode 100644 index 0000000000..3f71737b8c --- /dev/null +++ b/tests/postgres/migrations_reversible/20220721125033_modify_column.down.sql @@ -0,0 +1,2 @@ +UPDATE migrations_reversible_test +SET some_payload = some_payload - 1; diff --git a/tests/postgres/migrations_reversible/20220721125033_modify_column.up.sql b/tests/postgres/migrations_reversible/20220721125033_modify_column.up.sql new file mode 100644 index 0000000000..bbb176cf41 --- /dev/null +++ b/tests/postgres/migrations_reversible/20220721125033_modify_column.up.sql @@ -0,0 +1,2 @@ +UPDATE migrations_reversible_test +SET some_payload = some_payload + 1; diff --git a/tests/postgres/migrations_simple/20220721115250_add_test_table.sql b/tests/postgres/migrations_simple/20220721115250_add_test_table.sql new file mode 100644 index 0000000000..d5ba291914 --- /dev/null +++ b/tests/postgres/migrations_simple/20220721115250_add_test_table.sql @@ -0,0 +1,7 @@ +CREATE TABLE migrations_simple_test ( + some_id BIGINT NOT NULL PRIMARY KEY, + some_payload BIGINT NOT NUll +); + +INSERT INTO migrations_simple_test (some_id, some_payload) +VALUES (1, 100); diff --git a/tests/postgres/migrations_simple/20220721115524_convert_type.sql b/tests/postgres/migrations_simple/20220721115524_convert_type.sql new file mode 100644 index 0000000000..8f6b04d6b0 --- /dev/null +++ b/tests/postgres/migrations_simple/20220721115524_convert_type.sql @@ -0,0 +1,34 @@ +-- Perform a tricky conversion of the payload. +-- +-- This script will only succeed once and will fail if executed twice. + +-- set up temporary target column +ALTER TABLE migrations_simple_test +ADD some_payload_tmp TEXT; + +-- perform conversion +-- This will fail if `some_payload` is already a string column due to the addition. +-- We add a suffix after the addition to ensure that the SQL database does not silently cast the string back to an +-- integer. +UPDATE migrations_simple_test +SET some_payload_tmp = CONCAT(CAST((some_payload + 10) AS TEXT), '_suffix'); + +-- remove original column including the content +ALTER TABLE migrations_simple_test +DROP COLUMN some_payload; + +-- prepare new payload column (nullable, so we can copy over the data) +ALTER TABLE migrations_simple_test +ADD some_payload TEXT; + +-- copy new values +UPDATE migrations_simple_test +SET some_payload = some_payload_tmp; + +-- "freeze" column +ALTER TABLE migrations_simple_test +ALTER COLUMN some_payload SET NOT NULL; + +-- clean up +ALTER TABLE migrations_simple_test +DROP COLUMN some_payload_tmp; diff --git a/tests/sqlite/migrate.rs b/tests/sqlite/migrate.rs new file mode 100644 index 0000000000..a2d464d07a --- /dev/null +++ b/tests/sqlite/migrate.rs @@ -0,0 +1,85 @@ +use sqlx::migrate::Migrator; +use sqlx::sqlite::{Sqlite, SqliteConnection}; +use sqlx::Executor; +use sqlx::Row; +use std::path::Path; + +use sqlx_test::{new, SimpleLockGuard}; + +#[sqlx_macros::test] +async fn simple() -> anyhow::Result<()> { + let _guard = SimpleLockGuard::acquire(); + + let mut conn = new::().await?; + clean_up(&mut conn).await?; + + let migrator = Migrator::new(Path::new("tests/sqlite/migrations_simple")).await?; + + // run migration + migrator.run(&mut conn).await?; + + // check outcome + let res: String = conn + .fetch_one("SELECT some_payload FROM migrations_simple_test") + .await? + .get(0); + assert_eq!(res, "110_suffix"); + + // running it a 2nd time should still work + migrator.run(&mut conn).await?; + + Ok(()) +} + +#[sqlx_macros::test] +async fn reversible() -> anyhow::Result<()> { + let _guard = SimpleLockGuard::acquire(); + + let mut conn = new::().await?; + clean_up(&mut conn).await?; + + let migrator = Migrator::new(Path::new("tests/sqlite/migrations_reversible")).await?; + + // run migration + migrator.run(&mut conn).await?; + + // check outcome + let res: i64 = conn + .fetch_one("SELECT some_payload FROM migrations_reversible_test") + .await? + .get(0); + assert_eq!(res, 101); + + // roll back nothing (last version) + migrator.undo(&mut conn, 20220721125033).await?; + + // check outcome + let res: i64 = conn + .fetch_one("SELECT some_payload FROM migrations_reversible_test") + .await? + .get(0); + assert_eq!(res, 101); + + // roll back one version + migrator.undo(&mut conn, 20220721124650).await?; + + // check outcome + let res: i64 = conn + .fetch_one("SELECT some_payload FROM migrations_reversible_test") + .await? + .get(0); + assert_eq!(res, 100); + + Ok(()) +} + +/// Ensure that we have a clean initial state. +async fn clean_up(conn: &mut SqliteConnection) -> anyhow::Result<()> { + conn.execute("DROP TABLE migrations_simple_test").await.ok(); + conn.execute("DROP TABLE migrations_reversible_test") + .await + .ok(); + conn.execute("DROP TABLE _sqlx_migrations").await.ok(); + + Ok(()) +} diff --git a/tests/sqlite/migrations_reversible/20220721124650_add_table.down.sql b/tests/sqlite/migrations_reversible/20220721124650_add_table.down.sql new file mode 100644 index 0000000000..5505859725 --- /dev/null +++ b/tests/sqlite/migrations_reversible/20220721124650_add_table.down.sql @@ -0,0 +1 @@ +DROP TABLE migrations_reversible_test; diff --git a/tests/sqlite/migrations_reversible/20220721124650_add_table.up.sql b/tests/sqlite/migrations_reversible/20220721124650_add_table.up.sql new file mode 100644 index 0000000000..9dfc757954 --- /dev/null +++ b/tests/sqlite/migrations_reversible/20220721124650_add_table.up.sql @@ -0,0 +1,7 @@ +CREATE TABLE migrations_reversible_test ( + some_id BIGINT NOT NULL PRIMARY KEY, + some_payload BIGINT NOT NUll +); + +INSERT INTO migrations_reversible_test (some_id, some_payload) +VALUES (1, 100); diff --git a/tests/sqlite/migrations_reversible/20220721125033_modify_column.down.sql b/tests/sqlite/migrations_reversible/20220721125033_modify_column.down.sql new file mode 100644 index 0000000000..3f71737b8c --- /dev/null +++ b/tests/sqlite/migrations_reversible/20220721125033_modify_column.down.sql @@ -0,0 +1,2 @@ +UPDATE migrations_reversible_test +SET some_payload = some_payload - 1; diff --git a/tests/sqlite/migrations_reversible/20220721125033_modify_column.up.sql b/tests/sqlite/migrations_reversible/20220721125033_modify_column.up.sql new file mode 100644 index 0000000000..bbb176cf41 --- /dev/null +++ b/tests/sqlite/migrations_reversible/20220721125033_modify_column.up.sql @@ -0,0 +1,2 @@ +UPDATE migrations_reversible_test +SET some_payload = some_payload + 1; diff --git a/tests/sqlite/migrations_simple/20220721115250_add_test_table.sql b/tests/sqlite/migrations_simple/20220721115250_add_test_table.sql new file mode 100644 index 0000000000..d5ba291914 --- /dev/null +++ b/tests/sqlite/migrations_simple/20220721115250_add_test_table.sql @@ -0,0 +1,7 @@ +CREATE TABLE migrations_simple_test ( + some_id BIGINT NOT NULL PRIMARY KEY, + some_payload BIGINT NOT NUll +); + +INSERT INTO migrations_simple_test (some_id, some_payload) +VALUES (1, 100); diff --git a/tests/sqlite/migrations_simple/20220721115524_convert_type.sql b/tests/sqlite/migrations_simple/20220721115524_convert_type.sql new file mode 100644 index 0000000000..25de20b6a5 --- /dev/null +++ b/tests/sqlite/migrations_simple/20220721115524_convert_type.sql @@ -0,0 +1,30 @@ +-- Perform a tricky conversion of the payload. +-- +-- This script will only succeed once and will fail if executed twice. + +-- set up temporary target column +ALTER TABLE migrations_simple_test +ADD some_payload_tmp TEXT; + +-- perform conversion +-- This will fail if `some_payload` is already a string column due to the addition. +-- We add a suffix after the addition to ensure that the SQL database does not silently cast the string back to an +-- integer. +UPDATE migrations_simple_test +SET some_payload_tmp = CAST((some_payload + 10) AS TEXT) || '_suffix'; + +-- remove original column including the content +ALTER TABLE migrations_simple_test +DROP COLUMN some_payload; + +-- prepare new payload column (nullable, so we can copy over the data) +ALTER TABLE migrations_simple_test +ADD some_payload TEXT; + +-- copy new values +UPDATE migrations_simple_test +SET some_payload = some_payload_tmp; + +-- clean up +ALTER TABLE migrations_simple_test +DROP COLUMN some_payload_tmp; From 9b0c27f1b2d939f064606b2abb552309e90e4b62 Mon Sep 17 00:00:00 2001 From: Marco Neumann Date: Tue, 2 Aug 2022 14:13:17 +0200 Subject: [PATCH 09/10] fix: work around MySQL implicit commits --- sqlx-core/src/mysql/migrate.rs | 38 +++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/sqlx-core/src/mysql/migrate.rs b/sqlx-core/src/mysql/migrate.rs index e9a966a3fc..d17fbfb59c 100644 --- a/sqlx-core/src/mysql/migrate.rs +++ b/sqlx-core/src/mysql/migrate.rs @@ -217,11 +217,17 @@ CREATE TABLE IF NOT EXISTS _sqlx_migrations ( let mut tx = self.begin().await?; let start = Instant::now(); + // For MySQL we cannot really isolate migrations due to implicit commits caused by table modification, see + // https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html + // + // To somewhat try to detect this, we first insert the migration into the migration table with + // `success=FALSE` and later modify the flag. + // // language=MySQL let _ = query( r#" INSERT INTO _sqlx_migrations ( version, description, success, checksum, execution_time ) - VALUES ( ?, ?, TRUE, ?, -1 ) + VALUES ( ?, ?, FALSE, ?, -1 ) "#, ) .bind(migration.version) @@ -232,6 +238,18 @@ CREATE TABLE IF NOT EXISTS _sqlx_migrations ( let _ = tx.execute(&*migration.sql).await?; + // language=MySQL + let _ = query( + r#" + UPDATE _sqlx_migrations + SET success = TRUE + WHERE version = ? + "#, + ) + .bind(migration.version) + .execute(&mut tx) + .await?; + tx.commit().await?; // Update `elapsed_time`. @@ -266,6 +284,24 @@ CREATE TABLE IF NOT EXISTS _sqlx_migrations ( let mut tx = self.begin().await?; let start = Instant::now(); + // For MySQL we cannot really isolate migrations due to implicit commits caused by table modification, see + // https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html + // + // To somewhat try to detect this, we first insert the migration into the migration table with + // `success=FALSE` and later remove the migration altogether. + // + // language=MySQL + let _ = query( + r#" + UPDATE _sqlx_migrations + SET success = FALSE + WHERE version = ? + "#, + ) + .bind(migration.version) + .execute(&mut tx) + .await?; + tx.execute(&*migration.sql).await?; // language=SQL From fd24e8fbf5ce5993d75c5d59e82f1a8495799579 Mon Sep 17 00:00:00 2001 From: Marco Neumann Date: Wed, 3 Aug 2022 12:46:30 +0200 Subject: [PATCH 10/10] refactor: simplify migration testing --- sqlx-test/src/lib.rs | 29 ----------------------------- tests/mysql/migrate.rs | 17 +++++------------ tests/postgres/migrate.rs | 17 +++++------------ tests/sqlite/migrate.rs | 17 +++++------------ 4 files changed, 15 insertions(+), 65 deletions(-) diff --git a/sqlx-test/src/lib.rs b/sqlx-test/src/lib.rs index cb23c81ae6..5ba0f6323f 100644 --- a/sqlx-test/src/lib.rs +++ b/sqlx-test/src/lib.rs @@ -1,7 +1,6 @@ use sqlx::pool::PoolOptions; use sqlx::{Connection, Database, Pool}; use std::env; -use std::sync::atomic::{AtomicBool, Ordering}; pub fn setup_if_needed() { let _ = dotenvy::dotenv(); @@ -224,31 +223,3 @@ macro_rules! Postgres_query_for_test_prepared_type { "SELECT ({0} is not distinct from $1)::int4, {0}, $2" }; } - -/// Global lock that prevents multiple tests in this module to be executed at the same time. -static GLOBAL_LOCK: AtomicBool = AtomicBool::new(false); - -/// Simple lock guard that should not be used in production but that is `Send` (i.e. can easily be used with tokio). -/// -/// This may be helpful for tests that modify the database state, e.g. migrations. -pub struct SimpleLockGuard; - -impl SimpleLockGuard { - /// Acquire global lock. - pub fn acquire() -> Self { - loop { - let was_locked = GLOBAL_LOCK.fetch_or(true, Ordering::SeqCst); - if !was_locked { - break; - } - std::thread::sleep(std::time::Duration::from_millis(10)); - } - Self - } -} - -impl Drop for SimpleLockGuard { - fn drop(&mut self) { - GLOBAL_LOCK.store(false, Ordering::SeqCst); - } -} diff --git a/tests/mysql/migrate.rs b/tests/mysql/migrate.rs index 53963dd701..97caa38005 100644 --- a/tests/mysql/migrate.rs +++ b/tests/mysql/migrate.rs @@ -1,16 +1,12 @@ use sqlx::migrate::Migrator; use sqlx::mysql::{MySql, MySqlConnection}; +use sqlx::pool::PoolConnection; use sqlx::Executor; use sqlx::Row; use std::path::Path; -use sqlx_test::{new, SimpleLockGuard}; - -#[sqlx_macros::test] -async fn simple() -> anyhow::Result<()> { - let _guard = SimpleLockGuard::acquire(); - - let mut conn = new::().await?; +#[sqlx::test(migrations = false)] +async fn simple(mut conn: PoolConnection) -> anyhow::Result<()> { clean_up(&mut conn).await?; let migrator = Migrator::new(Path::new("tests/mysql/migrations_simple")).await?; @@ -31,11 +27,8 @@ async fn simple() -> anyhow::Result<()> { Ok(()) } -#[sqlx_macros::test] -async fn reversible() -> anyhow::Result<()> { - let _guard = SimpleLockGuard::acquire(); - - let mut conn = new::().await?; +#[sqlx::test(migrations = false)] +async fn reversible(mut conn: PoolConnection) -> anyhow::Result<()> { clean_up(&mut conn).await?; let migrator = Migrator::new(Path::new("tests/mysql/migrations_reversible")).await?; diff --git a/tests/postgres/migrate.rs b/tests/postgres/migrate.rs index f02e452941..ff25096831 100644 --- a/tests/postgres/migrate.rs +++ b/tests/postgres/migrate.rs @@ -1,16 +1,12 @@ use sqlx::migrate::Migrator; +use sqlx::pool::PoolConnection; use sqlx::postgres::{PgConnection, Postgres}; use sqlx::Executor; use sqlx::Row; use std::path::Path; -use sqlx_test::{new, SimpleLockGuard}; - -#[sqlx_macros::test] -async fn simple() -> anyhow::Result<()> { - let _guard = SimpleLockGuard::acquire(); - - let mut conn = new::().await?; +#[sqlx::test(migrations = false)] +async fn simple(mut conn: PoolConnection) -> anyhow::Result<()> { clean_up(&mut conn).await?; let migrator = Migrator::new(Path::new("tests/postgres/migrations_simple")).await?; @@ -31,11 +27,8 @@ async fn simple() -> anyhow::Result<()> { Ok(()) } -#[sqlx_macros::test] -async fn reversible() -> anyhow::Result<()> { - let _guard = SimpleLockGuard::acquire(); - - let mut conn = new::().await?; +#[sqlx::test(migrations = false)] +async fn reversible(mut conn: PoolConnection) -> anyhow::Result<()> { clean_up(&mut conn).await?; let migrator = Migrator::new(Path::new("tests/postgres/migrations_reversible")).await?; diff --git a/tests/sqlite/migrate.rs b/tests/sqlite/migrate.rs index a2d464d07a..19e8690f9a 100644 --- a/tests/sqlite/migrate.rs +++ b/tests/sqlite/migrate.rs @@ -1,16 +1,12 @@ use sqlx::migrate::Migrator; +use sqlx::pool::PoolConnection; use sqlx::sqlite::{Sqlite, SqliteConnection}; use sqlx::Executor; use sqlx::Row; use std::path::Path; -use sqlx_test::{new, SimpleLockGuard}; - -#[sqlx_macros::test] -async fn simple() -> anyhow::Result<()> { - let _guard = SimpleLockGuard::acquire(); - - let mut conn = new::().await?; +#[sqlx::test(migrations = false)] +async fn simple(mut conn: PoolConnection) -> anyhow::Result<()> { clean_up(&mut conn).await?; let migrator = Migrator::new(Path::new("tests/sqlite/migrations_simple")).await?; @@ -31,11 +27,8 @@ async fn simple() -> anyhow::Result<()> { Ok(()) } -#[sqlx_macros::test] -async fn reversible() -> anyhow::Result<()> { - let _guard = SimpleLockGuard::acquire(); - - let mut conn = new::().await?; +#[sqlx::test(migrations = false)] +async fn reversible(mut conn: PoolConnection) -> anyhow::Result<()> { clean_up(&mut conn).await?; let migrator = Migrator::new(Path::new("tests/sqlite/migrations_reversible")).await?;