Skip to main content

max / makenotwork

1.9 KB · 52 lines History Blame Raw
1 //! SQLite connection helper.
2 //!
3 //! Migrations stay per-tool: `sqlx::migrate!` resolves its path at compile
4 //! time relative to the calling crate, so each daemon runs its own
5 //! `sqlx::migrate!("./migrations").run(&pool)`. This module owns only the
6 //! connect-with-sane-defaults step, which is identical everywhere.
7
8 use anyhow::Result;
9 use sqlx::SqlitePool;
10 use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions, SqliteSynchronous};
11 use std::path::Path;
12 use std::str::FromStr;
13 use std::time::Duration;
14
15 /// Open (creating if missing) a pooled SQLite connection with sane defaults for
16 /// a concurrent multi-writer daemon. Bento fans builds out across targets, each
17 /// on its own blocking thread writing to this pool, so the default
18 /// rollback-journal mode with `busy_timeout = 0` would surface spurious
19 /// `SQLITE_BUSY` errors under contention. WAL lets readers run alongside a
20 /// writer; `busy_timeout` makes a contending writer wait rather than fail;
21 /// `synchronous = NORMAL` is the standard durable-enough pairing for WAL.
22 pub async fn connect(path: &Path) -> Result<SqlitePool> {
23 let url = format!("sqlite://{}?mode=rwc", path.display());
24 let opts = SqliteConnectOptions::from_str(&url)?
25 .create_if_missing(true)
26 .foreign_keys(true)
27 .journal_mode(SqliteJournalMode::Wal)
28 .synchronous(SqliteSynchronous::Normal)
29 .busy_timeout(Duration::from_secs(5));
30 let pool = SqlitePoolOptions::new()
31 .max_connections(4)
32 .connect_with(opts)
33 .await?;
34 Ok(pool)
35 }
36
37 #[cfg(test)]
38 mod tests {
39 use super::*;
40
41 #[tokio::test]
42 async fn connect_creates_and_enables_foreign_keys() {
43 let dir = tempfile::tempdir().unwrap();
44 let pool = connect(&dir.path().join("t.db")).await.unwrap();
45 let fk: i64 = sqlx::query_scalar("PRAGMA foreign_keys")
46 .fetch_one(&pool)
47 .await
48 .unwrap();
49 assert_eq!(fk, 1);
50 }
51 }
52