1use std::path::Path;
9
10use anyhow::{Context, Result};
11use sqlx::AnyPool;
12use sqlx::any::{AnyPoolOptions, install_default_drivers};
13use sqlx::migrate::{Migration, Migrator};
14
15#[derive(Debug, Default)]
17pub struct Status {
18 pub applied: Vec<String>,
19 pub pending: Vec<String>,
20}
21
22fn runtime() -> Result<tokio::runtime::Runtime> {
23 tokio::runtime::Builder::new_current_thread()
24 .enable_all()
25 .build()
26 .context("building the async runtime")
27}
28
29async fn connect(database_url: &str) -> Result<AnyPool> {
30 install_default_drivers();
33 AnyPoolOptions::new()
34 .max_connections(1)
35 .connect(database_url)
36 .await
37 .context("connecting to the database")
38}
39
40async fn applied_versions(pool: &AnyPool) -> Vec<i64> {
43 sqlx::query_scalar::<_, i64>("SELECT version FROM _sqlx_migrations ORDER BY version")
44 .fetch_all(pool)
45 .await
46 .unwrap_or_default()
47}
48
49fn label(migration: &Migration) -> String {
50 format!("{}_{}", migration.version, migration.description)
51}
52
53pub fn run(database_url: &str, dir: &Path) -> Result<Vec<String>> {
55 runtime()?.block_on(async {
56 let migrator = Migrator::new(dir).await.context("loading migrations")?;
57 let pool = connect(database_url).await?;
58 let before = applied_versions(&pool).await;
59 migrator.run(&pool).await.context("applying migrations")?;
60 let newly = migrator
61 .iter()
62 .filter(|m| !before.contains(&m.version))
63 .map(label)
64 .collect();
65 Ok(newly)
66 })
67}
68
69pub fn status(database_url: &str, dir: &Path) -> Result<Status> {
71 runtime()?.block_on(async {
72 let migrator = Migrator::new(dir).await.context("loading migrations")?;
73 let pool = connect(database_url).await?;
74 let applied = applied_versions(&pool).await;
75
76 let mut status = Status::default();
77 for migration in migrator.iter() {
78 if applied.contains(&migration.version) {
79 status.applied.push(label(migration));
80 } else {
81 status.pending.push(label(migration));
82 }
83 }
84 Ok(status)
85 })
86}