Skip to content

Commit abb3d4d

Browse files
committed
test: extend migrations testing
1 parent 8c2566b commit abb3d4d

32 files changed

+521
-17
lines changed

Cargo.toml

+15
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,11 @@ name = "sqlite-derives"
206206
path = "tests/sqlite/derives.rs"
207207
required-features = ["sqlite", "macros"]
208208

209+
[[test]]
210+
name = "sqlite-migrate"
211+
path = "tests/sqlite/migrate.rs"
212+
required-features = ["sqlite", "macros", "migrate"]
213+
209214
#
210215
# MySQL
211216
#
@@ -230,6 +235,11 @@ name = "mysql-macros"
230235
path = "tests/mysql/macros.rs"
231236
required-features = ["mysql", "macros"]
232237

238+
[[test]]
239+
name = "mysql-migrate"
240+
path = "tests/mysql/migrate.rs"
241+
required-features = ["mysql", "macros", "migrate"]
242+
233243
#
234244
# PostgreSQL
235245
#
@@ -259,6 +269,11 @@ name = "postgres-derives"
259269
path = "tests/postgres/derives.rs"
260270
required-features = ["postgres", "macros"]
261271

272+
[[test]]
273+
name = "postgres-migrate"
274+
path = "tests/postgres/migrate.rs"
275+
required-features = ["postgres", "macros", "migrate"]
276+
262277
#
263278
# Microsoft SQL Server (MSSQL)
264279
#

sqlx-test/src/lib.rs

+29
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use sqlx::pool::PoolOptions;
22
use sqlx::{Connection, Database, Pool};
33
use std::env;
4+
use std::sync::atomic::{AtomicBool, Ordering};
45

56
pub fn setup_if_needed() {
67
let _ = dotenv::dotenv();
@@ -223,3 +224,31 @@ macro_rules! Postgres_query_for_test_prepared_type {
223224
"SELECT ({0} is not distinct from $1)::int4, {0}, $2"
224225
};
225226
}
227+
228+
/// Global lock that prevents multiple tests in this module to be executed at the same time.
229+
static GLOBAL_LOCK: AtomicBool = AtomicBool::new(false);
230+
231+
/// Simple lock guard that should not be used in production but that is `Send` (i.e. can easily be used with tokio).
232+
///
233+
/// This may be helpful for tests that modify the database state, e.g. migrations.
234+
pub struct SimpleLockGuard;
235+
236+
impl SimpleLockGuard {
237+
/// Acquire global lock.
238+
pub fn acquire() -> Self {
239+
loop {
240+
let was_locked = GLOBAL_LOCK.fetch_or(true, Ordering::SeqCst);
241+
if !was_locked {
242+
break;
243+
}
244+
std::thread::sleep(std::time::Duration::from_millis(10));
245+
}
246+
Self
247+
}
248+
}
249+
250+
impl Drop for SimpleLockGuard {
251+
fn drop(&mut self) {
252+
GLOBAL_LOCK.store(false, Ordering::SeqCst);
253+
}
254+
}

tests/migrate/macro.rs

+14-6
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,29 @@
11
use sqlx::migrate::Migrator;
22
use std::path::Path;
33

4-
static EMBEDDED: Migrator = sqlx::migrate!("tests/migrate/migrations");
4+
static EMBEDDED_SIMPLE: Migrator = sqlx::migrate!("tests/migrate/migrations_simple");
5+
static EMBEDDED_REVERSIBLE: Migrator = sqlx::migrate!("tests/migrate/migrations_reversible");
56

67
#[sqlx_macros::test]
78
async fn same_output() -> anyhow::Result<()> {
8-
let runtime = Migrator::new(Path::new("tests/migrate/migrations")).await?;
9+
let runtime_simple = Migrator::new(Path::new("tests/migrate/migrations_simple")).await?;
10+
let runtime_reversible =
11+
Migrator::new(Path::new("tests/migrate/migrations_reversible")).await?;
912

10-
assert_eq!(runtime.migrations.len(), EMBEDDED.migrations.len());
13+
assert_same(&EMBEDDED_SIMPLE, &runtime_simple);
14+
assert_same(&EMBEDDED_REVERSIBLE, &runtime_reversible);
1115

12-
for (e, r) in EMBEDDED.iter().zip(runtime.iter()) {
16+
Ok(())
17+
}
18+
19+
fn assert_same(embedded: &Migrator, runtime: &Migrator) {
20+
assert_eq!(runtime.migrations.len(), embedded.migrations.len());
21+
22+
for (e, r) in embedded.iter().zip(runtime.iter()) {
1323
assert_eq!(e.version, r.version);
1424
assert_eq!(e.description, r.description);
1525
assert_eq!(e.migration_type, r.migration_type);
1626
assert_eq!(e.sql, r.sql);
1727
assert_eq!(e.checksum, r.checksum);
1828
}
19-
20-
Ok(())
2129
}

tests/migrate/migrations/20200723212833_tweet.sql

-6
This file was deleted.

tests/migrate/migrations/20200723212841_accounts.sql

-5
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DROP TABLE migrations_reversible_test;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CREATE TABLE migrations_reversible_test (
2+
some_id BIGINT NOT NULL PRIMARY KEY,
3+
some_payload BIGINT NOT NUll
4+
);
5+
6+
INSERT INTO migrations_reversible_test (some_id, some_payload)
7+
VALUES (1, 100);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
UPDATE migrations_reversible_test
2+
SET some_payload = some_payload - 1;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
UPDATE migrations_reversible_test
2+
SET some_payload = some_payload + 1;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CREATE TABLE migrations_simple_test (
2+
some_id BIGINT NOT NULL PRIMARY KEY,
3+
some_payload BIGINT NOT NUll
4+
);
5+
6+
INSERT INTO migrations_simple_test (some_id, some_payload)
7+
VALUES (1, 100);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
-- Perform a tricky conversion of the payload.
2+
--
3+
-- This script will only succeed once and will fail if executed twice.
4+
5+
-- set up temporary target column
6+
ALTER TABLE migrations_simple_test
7+
ADD some_payload_tmp TEXT;
8+
9+
-- perform conversion
10+
-- This will fail if `some_payload` is already a string column due to the addition.
11+
-- We add a suffix after the addition to ensure that the SQL database does not silently cast the string back to an
12+
-- integer.
13+
UPDATE migrations_simple_test
14+
SET some_payload_tmp = CONCAT(CAST((some_payload + 10) AS TEXT), '_suffix');
15+
16+
-- remove original column including the content
17+
ALTER TABLE migrations_simple_test
18+
DROP COLUMN some_payload;
19+
20+
-- prepare new payload column (nullable, so we can copy over the data)
21+
ALTER TABLE migrations_simple_test
22+
ADD some_payload TEXT;
23+
24+
-- copy new values
25+
UPDATE migrations_simple_test
26+
SET some_payload = some_payload_tmp;
27+
28+
-- "freeze" column
29+
ALTER TABLE migrations_simple_test
30+
ALTER COLUMN some_payload SET NOT NULL;
31+
32+
-- clean up
33+
ALTER TABLE migrations_simple_test
34+
DROP COLUMN some_payload_tmp;

tests/mysql/migrate.rs

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
use sqlx::migrate::Migrator;
2+
use sqlx::mysql::{MySql, MySqlConnection};
3+
use sqlx::Executor;
4+
use sqlx::Row;
5+
use std::path::Path;
6+
7+
use sqlx_test::{new, SimpleLockGuard};
8+
9+
#[sqlx_macros::test]
10+
async fn simple() -> anyhow::Result<()> {
11+
let _guard = SimpleLockGuard::acquire();
12+
13+
let mut conn = new::<MySql>().await?;
14+
clean_up(&mut conn).await?;
15+
16+
let migrator = Migrator::new(Path::new("tests/mysql/migrations_simple")).await?;
17+
18+
// run migration
19+
migrator.run(&mut conn).await?;
20+
21+
// check outcome
22+
let res: String = conn
23+
.fetch_one("SELECT some_payload FROM migrations_simple_test")
24+
.await?
25+
.get(0);
26+
assert_eq!(res, "110_suffix");
27+
28+
// running it a 2nd time should still work
29+
migrator.run(&mut conn).await?;
30+
31+
Ok(())
32+
}
33+
34+
#[sqlx_macros::test]
35+
async fn reversible() -> anyhow::Result<()> {
36+
let _guard = SimpleLockGuard::acquire();
37+
38+
let mut conn = new::<MySql>().await?;
39+
clean_up(&mut conn).await?;
40+
41+
let migrator = Migrator::new(Path::new("tests/mysql/migrations_reversible")).await?;
42+
43+
// run migration
44+
migrator.run(&mut conn).await?;
45+
46+
// check outcome
47+
let res: i64 = conn
48+
.fetch_one("SELECT some_payload FROM migrations_reversible_test")
49+
.await?
50+
.get(0);
51+
assert_eq!(res, 101);
52+
53+
// roll back nothing (last version)
54+
migrator.undo(&mut conn, 20220721125033).await?;
55+
56+
// check outcome
57+
let res: i64 = conn
58+
.fetch_one("SELECT some_payload FROM migrations_reversible_test")
59+
.await?
60+
.get(0);
61+
assert_eq!(res, 101);
62+
63+
// roll back one version
64+
migrator.undo(&mut conn, 20220721124650).await?;
65+
66+
// check outcome
67+
let res: i64 = conn
68+
.fetch_one("SELECT some_payload FROM migrations_reversible_test")
69+
.await?
70+
.get(0);
71+
assert_eq!(res, 100);
72+
73+
Ok(())
74+
}
75+
76+
/// Ensure that we have a clean initial state.
77+
async fn clean_up(conn: &mut MySqlConnection) -> anyhow::Result<()> {
78+
conn.execute("DROP TABLE migrations_simple_test").await.ok();
79+
conn.execute("DROP TABLE migrations_reversible_test")
80+
.await
81+
.ok();
82+
conn.execute("DROP TABLE _sqlx_migrations").await.ok();
83+
84+
Ok(())
85+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DROP TABLE migrations_reversible_test;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CREATE TABLE migrations_reversible_test (
2+
some_id BIGINT NOT NULL PRIMARY KEY,
3+
some_payload BIGINT NOT NUll
4+
);
5+
6+
INSERT INTO migrations_reversible_test (some_id, some_payload)
7+
VALUES (1, 100);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
UPDATE migrations_reversible_test
2+
SET some_payload = some_payload - 1;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
UPDATE migrations_reversible_test
2+
SET some_payload = some_payload + 1;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CREATE TABLE migrations_simple_test (
2+
some_id BIGINT NOT NULL PRIMARY KEY,
3+
some_payload BIGINT NOT NUll
4+
);
5+
6+
INSERT INTO migrations_simple_test (some_id, some_payload)
7+
VALUES (1, 100);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
-- Perform a tricky conversion of the payload.
2+
--
3+
-- This script will only succeed once and will fail if executed twice.
4+
5+
-- set up temporary target column
6+
ALTER TABLE migrations_simple_test
7+
ADD some_payload_tmp TEXT;
8+
9+
-- perform conversion
10+
-- This will fail if `some_payload` is already a string column due to the addition.
11+
-- We add a suffix after the addition to ensure that the SQL database does not silently cast the string back to an
12+
-- integer.
13+
UPDATE migrations_simple_test
14+
SET some_payload_tmp = CONCAT(CAST((some_payload + 10) AS CHAR(3)), '_suffix');
15+
16+
-- remove original column including the content
17+
ALTER TABLE migrations_simple_test
18+
DROP COLUMN some_payload;
19+
20+
-- prepare new payload column (nullable, so we can copy over the data)
21+
ALTER TABLE migrations_simple_test
22+
ADD some_payload TEXT;
23+
24+
-- copy new values
25+
UPDATE migrations_simple_test
26+
SET some_payload = some_payload_tmp;
27+
28+
-- "freeze" column
29+
ALTER TABLE migrations_simple_test
30+
MODIFY some_payload TEXT NOT NULL;
31+
32+
-- clean up
33+
ALTER TABLE migrations_simple_test
34+
DROP COLUMN some_payload_tmp;

0 commit comments

Comments
 (0)