#![allow(dead_code)]
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use umbral::migrate::{make_in, run_in};
#[derive(Debug, Clone, sqlx::FromRow, Serialize, Deserialize, umbral::orm::Model)]
#[umbral(table = "lock_race_post")]
pub struct LockRacePost {
pub id: i64,
pub title: String,
}
#[tokio::test]
#[ignore = "needs UMBRAL_TEST_POSTGRES_URL"]
async fn concurrent_migrators_serialize_and_apply_once() {
let Ok(url) = std::env::var("UMBRAL_TEST_POSTGRES_URL") else {
eprintln!("skipping: UMBRAL_TEST_POSTGRES_URL not set");
return;
};
let pool = PgPool::connect(&url).await.expect("connect");
sqlx::query("DROP TABLE IF EXISTS lock_race_post")
.execute(&pool)
.await
.expect("drop table");
let _ = sqlx::query("DELETE FROM umbral_migrations WHERE name LIKE '%lock_race_post%'")
.execute(&pool)
.await;
let mut settings = umbral::Settings::from_env().expect("settings");
settings.database_url = url.clone();
umbral::App::builder()
.settings(settings)
.database("default", pool.clone())
.model::<LockRacePost>()
.build()
.expect("App::build");
let dir = tempfile::tempdir().expect("tempdir");
make_in(dir.path()).await.expect("make_in writes migration");
let d1 = dir.path().to_path_buf();
let d2 = dir.path().to_path_buf();
let (r1, r2) = tokio::join!(run_in(&d1), run_in(&d2));
let applied1 = r1.expect("first migrator must not error");
let applied2 = r2.expect("second migrator must not error — the lock prevents the DDL race");
assert_eq!(
applied1 + applied2,
1,
"the migration must be applied exactly once across both concurrent \
migrators (got {applied1} + {applied2})"
);
let exists: bool = sqlx::query_scalar(
"SELECT EXISTS (SELECT 1 FROM information_schema.tables \
WHERE table_name = 'lock_race_post')",
)
.fetch_one(&pool)
.await
.expect("query information_schema");
assert!(exists, "the table must have been created");
sqlx::query("DROP TABLE IF EXISTS lock_race_post")
.execute(&pool)
.await
.expect("cleanup");
}