Skip to main content

miden_node_db/
manager.rs

1//! A minimal connection manager wrapper
2//!
3//! Only required to setup connection parameters, specifically `WAL`.
4
5use deadpool_sync::InteractError;
6
7use super::{Result, RunQueryDsl, SqliteConnection};
8
9#[derive(thiserror::Error, Debug)]
10pub enum ConnectionManagerError {
11    #[error("failed to apply connection parameter")]
12    ConnectionParamSetup(#[source] diesel::result::Error),
13    #[error("SQLite pool interaction failed: {0}")]
14    InteractError(String),
15    #[error("failed to create a new connection")]
16    ConnectionCreate(#[source] deadpool_diesel::Error),
17    #[error("failed to recycle connection")]
18    PoolRecycle(#[source] deadpool::managed::RecycleError<deadpool_diesel::Error>),
19}
20
21impl ConnectionManagerError {
22    /// Converts from `InteractError`
23    ///
24    /// Note: Required since `InteractError` has at least one enum variant that is _not_ `Send +
25    /// Sync` and hence prevents the `Sync` auto implementation. This does an internal
26    /// conversion to string while maintaining convenience.
27    ///
28    /// Using `MSG` as const so it can be called as `.map_err(DatabaseError::interact::<"Your
29    /// message">)`
30    pub fn interact(msg: &(impl ToString + ?Sized), e: &InteractError) -> Self {
31        let msg = msg.to_string();
32        Self::InteractError(format!("{msg} failed: {e:?}"))
33    }
34}
35
36/// Create a connection manager with per-connection setup
37///
38/// Particularly, `foreign_key` checks are enabled and using a write-append-log for journaling.
39pub struct ConnectionManager {
40    pub(crate) manager: deadpool_diesel::sqlite::Manager,
41}
42
43impl ConnectionManager {
44    pub fn new(database_path: &str) -> Self {
45        let manager = deadpool_diesel::sqlite::Manager::new(
46            database_path.to_owned(),
47            deadpool_diesel::sqlite::Runtime::Tokio1,
48        );
49        Self { manager }
50    }
51}
52
53impl deadpool::managed::Manager for ConnectionManager {
54    type Type = deadpool_sync::SyncWrapper<SqliteConnection>;
55    type Error = ConnectionManagerError;
56
57    async fn create(&self) -> Result<Self::Type, Self::Error> {
58        let conn = self.manager.create().await.map_err(ConnectionManagerError::ConnectionCreate)?;
59
60        conn.interact(configure_connection_on_creation)
61            .await
62            .map_err(|e| ConnectionManagerError::interact("Connection setup", &e))??;
63        Ok(conn)
64    }
65
66    async fn recycle(
67        &self,
68        conn: &mut Self::Type,
69        metrics: &deadpool_diesel::Metrics,
70    ) -> deadpool::managed::RecycleResult<Self::Error> {
71        self.manager.recycle(conn, metrics).await.map_err(|err| {
72            deadpool::managed::RecycleError::Backend(ConnectionManagerError::PoolRecycle(err))
73        })?;
74        Ok(())
75    }
76}
77
78pub fn configure_connection_on_creation(
79    conn: &mut SqliteConnection,
80) -> Result<(), ConnectionManagerError> {
81    // Wait up to 3 seconds for writer locks before erroring.
82    diesel::sql_query("PRAGMA busy_timeout=3000")
83        .execute(conn)
84        .map_err(ConnectionManagerError::ConnectionParamSetup)?;
85
86    // Enable the WAL mode. This allows concurrent reads while the transaction is being written,
87    // this is required for proper synchronization of the servers in-memory and on-disk
88    // representations (see [State::apply_block])
89    diesel::sql_query("PRAGMA journal_mode=WAL")
90        .execute(conn)
91        .map_err(ConnectionManagerError::ConnectionParamSetup)?;
92
93    // Enable foreign key checks.
94    diesel::sql_query("PRAGMA foreign_keys=ON")
95        .execute(conn)
96        .map_err(ConnectionManagerError::ConnectionParamSetup)?;
97
98    // Set busy timeout so concurrent writers wait instead of immediately failing.
99    diesel::sql_query("PRAGMA busy_timeout=5000")
100        .execute(conn)
101        .map_err(ConnectionManagerError::ConnectionParamSetup)?;
102    Ok(())
103}