1use r2d2::Pool;
2use std::path::Path;
3
4use crate::error::{HttpError, Result};
5use crate::pool::SqliteConnectionManager;
6
7pub type SqlitePool = Pool<SqliteConnectionManager>;
8
9#[derive(Clone)]
16pub struct Db(pub SqlitePool);
17
18impl Db {
19 pub async fn connect(path: impl AsRef<Path>) -> anyhow::Result<Self> {
21 let path = path.as_ref().to_path_buf();
22 let pool = tokio::task::spawn_blocking(move || -> anyhow::Result<SqlitePool> {
23 let manager = SqliteConnectionManager::file(path);
24 let pool = Pool::builder().max_size(8).build(manager)?;
25 pool.get()?.execute_batch("PRAGMA journal_mode = WAL;")?;
26 Ok(pool)
27 })
28 .await??;
29 Ok(Self(pool))
30 }
31
32 pub async fn connect_memory() -> anyhow::Result<Self> {
35 let manager =
36 SqliteConnectionManager::shared_memory("file:ling_http_mem?mode=memory&cache=shared");
37 let pool = tokio::task::spawn_blocking(move || -> anyhow::Result<SqlitePool> {
38 Ok(Pool::builder().max_size(1).min_idle(Some(1)).build(manager)?)
43 })
44 .await??;
45 Ok(Self(pool))
46 }
47
48 pub fn pool(&self) -> &SqlitePool {
49 &self.0
50 }
51
52 pub async fn with_conn<T, F>(&self, f: F) -> Result<T>
55 where
56 T: Send + 'static,
57 F: FnOnce(&rusqlite::Connection) -> rusqlite::Result<T> + Send + 'static,
58 {
59 let pool = self.0.clone();
60 tokio::task::spawn_blocking(move || {
61 let conn = pool.get().map_err(HttpError::Pool)?;
62 f(&conn).map_err(HttpError::Database)
63 })
64 .await
65 .map_err(|e| HttpError::Internal(anyhow::anyhow!("db task panicked: {e}")))?
66 }
67
68 pub async fn run_migrations(&self, migrations: &'static [(&'static str, &'static str)]) -> anyhow::Result<()> {
71 let pool = self.0.clone();
72 tokio::task::spawn_blocking(move || -> anyhow::Result<()> {
73 let mut conn = pool.get()?;
74 conn.execute_batch(
75 "CREATE TABLE IF NOT EXISTS _migrations (\
76 name TEXT PRIMARY KEY, \
77 applied_at TEXT NOT NULL DEFAULT (datetime('now'))\
78 )",
79 )?;
80 let tx = conn.transaction()?;
81 for (name, sql) in migrations {
82 let already: bool = tx.query_row(
83 "SELECT EXISTS(SELECT 1 FROM _migrations WHERE name = ?1)",
84 [name],
85 |row| row.get(0),
86 )?;
87 if already {
88 continue;
89 }
90 tx.execute_batch(sql)?;
91 tx.execute("INSERT INTO _migrations (name) VALUES (?1)", [name])?;
92 }
93 tx.commit()?;
94 Ok(())
95 })
96 .await?
97 }
98}