Skip to main content

sbol_db_sqlite/
pool.rs

1//! SQLite connection pool, schema migrations, and the [`Migrator`] capability.
2
3use std::str::FromStr;
4use std::time::Duration;
5
6use async_trait::async_trait;
7use sbol_db_core::DomainError;
8use sbol_db_storage::{MigrationEntry, Migrator};
9use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions};
10
11pub use sqlx::SqlitePool;
12
13static MIGRATOR: sqlx::migrate::Migrator = sqlx::migrate!("./migrations");
14
15pub(crate) fn db_err<E: std::fmt::Display>(e: E) -> DomainError {
16    DomainError::Database(e.to_string())
17}
18
19/// Open a SQLite pool for `url` (e.g. `sqlite://data/sbol.db` or
20/// `sqlite::memory:`). WAL mode plus a busy timeout let readers and a single
21/// writer coexist without spurious "database is locked" errors; foreign keys
22/// are enabled so the graph→triples cascade fires.
23pub async fn connect(url: &str) -> Result<SqlitePool, DomainError> {
24    let options = SqliteConnectOptions::from_str(url)
25        .map_err(db_err)?
26        .create_if_missing(true)
27        .journal_mode(SqliteJournalMode::Wal)
28        .busy_timeout(Duration::from_secs(5))
29        .foreign_keys(true);
30    SqlitePoolOptions::new()
31        .max_connections(8)
32        .connect_with(options)
33        .await
34        .map_err(db_err)
35}
36
37/// Open `url` and apply every pending migration.
38pub async fn connect_and_migrate(url: &str) -> Result<SqlitePool, DomainError> {
39    let pool = connect(url).await?;
40    run_migrations(&pool).await?;
41    Ok(pool)
42}
43
44pub async fn run_migrations(pool: &SqlitePool) -> Result<(), DomainError> {
45    MIGRATOR.run(pool).await.map_err(db_err)
46}
47
48pub async fn migration_status(pool: &SqlitePool) -> Result<Vec<MigrationEntry>, DomainError> {
49    let known = MIGRATOR
50        .iter()
51        .map(|m| (m.version, m.description.to_string()))
52        .collect::<Vec<_>>();
53    let applied: Vec<i64> =
54        sqlx::query_scalar("SELECT version FROM _sqlx_migrations ORDER BY version")
55            .fetch_all(pool)
56            .await
57            .unwrap_or_default();
58    Ok(known
59        .into_iter()
60        .map(|(version, description)| MigrationEntry {
61            version,
62            description,
63            applied: applied.contains(&version),
64        })
65        .collect())
66}
67
68/// The migration capability for a SQLite backend.
69#[derive(Clone)]
70pub struct SqliteMigrator {
71    pool: SqlitePool,
72}
73
74impl SqliteMigrator {
75    pub fn new(pool: SqlitePool) -> Self {
76        Self { pool }
77    }
78}
79
80#[async_trait]
81impl Migrator for SqliteMigrator {
82    async fn run_migrations(&self) -> Result<(), DomainError> {
83        run_migrations(&self.pool).await
84    }
85
86    async fn migration_status(&self) -> Result<Vec<MigrationEntry>, DomainError> {
87        migration_status(&self.pool).await
88    }
89}