winbrew_database/
connection.rs

1use anyhow::{Context, Result};
2use r2d2::{ManageConnection, Pool};
3use rusqlite::{Connection, OpenFlags};
4use std::path::{Path, PathBuf};
5use std::time::Duration;
6
7#[derive(Clone, Debug)]
8pub struct SqliteConnectionManager {
9    path: PathBuf,
10    read_only: bool,
11}
12
13impl ManageConnection for SqliteConnectionManager {
14    type Connection = Connection;
15    type Error = rusqlite::Error;
16
17    fn connect(&self) -> std::result::Result<Self::Connection, Self::Error> {
18        open_connection(&self.path, self.read_only)
19    }
20
21    fn is_valid(&self, conn: &mut Self::Connection) -> std::result::Result<(), Self::Error> {
22        conn.execute_batch("SELECT 1;")
23    }
24
25    fn has_broken(&self, _conn: &mut Self::Connection) -> bool {
26        false
27    }
28}
29
30pub(crate) fn open_connection(
31    path: &Path,
32    read_only: bool,
33) -> std::result::Result<Connection, rusqlite::Error> {
34    let conn = if read_only {
35        if !path.exists() {
36            return Err(rusqlite::Error::SqliteFailure(
37                rusqlite::ffi::Error::new(rusqlite::ffi::SQLITE_CANTOPEN),
38                Some(format!("catalog database not found: {}", path.display())),
39            ));
40        }
41
42        Connection::open_with_flags(path, OpenFlags::SQLITE_OPEN_READ_ONLY)?
43    } else {
44        Connection::open(path)?
45    };
46    conn.busy_timeout(Duration::from_secs(5))?;
47
48    if read_only {
49        conn.execute_batch("PRAGMA query_only=ON; PRAGMA foreign_keys=ON;")?;
50    } else {
51        conn.execute_batch(
52            "PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL; PRAGMA foreign_keys=ON;",
53        )?;
54    }
55
56    Ok(conn)
57}
58
59pub(crate) fn build_pool(
60    path: PathBuf,
61    read_only: bool,
62    max_size: u32,
63    migrate: Option<fn(&Connection) -> Result<()>>,
64) -> Result<Pool<SqliteConnectionManager>> {
65    let pool = Pool::builder()
66        .max_size(max_size)
67        .build(SqliteConnectionManager { path, read_only })
68        .context("failed to initialize SQLite connection pool")?;
69
70    if let Some(migrate) = migrate {
71        let conn = pool
72            .get()
73            .context("failed to get database connection for migrations")?;
74        migrate(&conn)?;
75    }
76
77    Ok(pool)
78}