sql_middleware/pool/connection/
sqlite.rs

1use crate::error::SqlMiddlewareDbError;
2use crate::sqlite::config::SqliteManager;
3use crate::sqlite::{SqliteConnection, SqlitePreparedStatement};
4
5use super::MiddlewarePoolConnection;
6
7#[cfg(feature = "sqlite")]
8pub(super) async fn get_connection(
9    pool: &bb8::Pool<SqliteManager>,
10    translate_placeholders: bool,
11) -> Result<MiddlewarePoolConnection, SqlMiddlewareDbError> {
12    let conn = pool.get_owned().await.map_err(|e| {
13        SqlMiddlewareDbError::ConnectionError(format!("sqlite checkout error: {e}"))
14    })?;
15    let worker_conn = SqliteConnection::new(conn);
16    Ok(MiddlewarePoolConnection::Sqlite {
17        conn: Some(worker_conn),
18        translate_placeholders,
19    })
20}
21
22#[cfg(feature = "sqlite")]
23impl MiddlewarePoolConnection {
24    /// Run synchronous `SQLite` work on the underlying worker-owned connection.
25    ///
26    /// Use this when you need to batch multiple statements in one worker hop, reuse `rusqlite`
27    /// features we don't expose (savepoints, pragmas that return rows, custom hooks), or avoid
28    /// re-preparing statements in hot loops. It keeps blocking work off the async runtime while
29    /// letting you drive the raw `rusqlite::Connection`.
30    ///
31    /// The closure runs on a worker thread and must **not** capture non-`Send` state across an
32    /// `await`. Do all work inside the closure and return promptly instead of holding onto the
33    /// connection handle.
34    ///
35    /// # Errors
36    /// Returns [`SqlMiddlewareDbError::Unimplemented`] when the connection is not `SQLite`.
37    ///
38    /// # Examples
39    /// ```rust,no_run
40    /// use sql_middleware::prelude::*;
41    ///
42    /// # async fn demo() -> Result<(), SqlMiddlewareDbError> {
43    /// let cap = ConfigAndPool::new_sqlite("file::memory:?cache=shared".into()).await?;
44    /// let mut conn = cap.get_connection().await?;
45    /// conn.with_blocking_sqlite(|raw| {
46    ///     raw.execute_batch("CREATE TABLE t (id INTEGER, name TEXT);")?;
47    ///     Ok::<_, SqlMiddlewareDbError>(())
48    /// })
49    /// .await?;
50    /// # Ok(()) }
51    /// ```
52    pub async fn with_blocking_sqlite<F, R>(&mut self, func: F) -> Result<R, SqlMiddlewareDbError>
53    where
54        F: FnOnce(&mut rusqlite::Connection) -> Result<R, SqlMiddlewareDbError> + Send + 'static,
55        R: Send + 'static,
56    {
57        let conn = self.sqlite_conn_mut()?;
58        conn.with_connection(func).await
59    }
60
61    /// Prepare a `SQLite` statement and obtain a reusable handle backed by the worker thread.
62    ///
63    /// # Errors
64    /// Returns [`SqlMiddlewareDbError::Unimplemented`] when the underlying connection is not
65    /// `SQLite`, or propagates any preparation error reported by the worker thread.
66    ///
67    /// The returned handle borrows the worker-owned connection. Use it within the async scope that
68    /// created it; do not move it across tasks or hold it across long `await` chains.
69    ///
70    /// # Examples
71    /// ```rust,no_run
72    /// use sql_middleware::prelude::*;
73    ///
74    /// # async fn demo() -> Result<(), SqlMiddlewareDbError> {
75    /// let cap = ConfigAndPool::new_sqlite("file::memory:?cache=shared".into()).await?;
76    /// let mut conn = cap.get_connection().await?;
77    /// conn.execute_batch("CREATE TABLE t (id INTEGER, name TEXT)").await?;
78    ///
79    /// let prepared = conn
80    ///     .prepare_sqlite_statement("INSERT INTO t (id, name) VALUES (?1, ?2)")
81    ///     .await?;
82    /// prepared
83    ///     .execute(&[RowValues::Int(1), RowValues::Text("alice".into())])
84    ///     .await?;
85    /// # Ok(()) }
86    /// ```
87    pub async fn prepare_sqlite_statement(
88        &mut self,
89        query: &str,
90    ) -> Result<SqlitePreparedStatement<'_>, SqlMiddlewareDbError> {
91        let conn = self.sqlite_conn_mut()?;
92        conn.prepare_statement(query).await
93    }
94
95    pub(crate) fn sqlite_conn_mut(
96        &mut self,
97    ) -> Result<&mut SqliteConnection, SqlMiddlewareDbError> {
98        match self {
99            MiddlewarePoolConnection::Sqlite { conn, .. } => conn.as_mut().ok_or_else(|| {
100                SqlMiddlewareDbError::ExecutionError(
101                    "SQLite connection already taken from pool wrapper".into(),
102                )
103            }),
104            _ => Err(SqlMiddlewareDbError::Unimplemented(
105                "SQLite helper called on non-sqlite connection".into(),
106            )),
107        }
108    }
109
110    /// Extract the `SQLite` connection, returning the translation flag alongside it.
111    ///
112    /// # Errors
113    /// Returns `SqlMiddlewareDbError` if the connection is already taken or the enum is not `SQLite`.
114    pub fn into_sqlite(self) -> Result<(SqliteConnection, bool), SqlMiddlewareDbError> {
115        match self {
116            MiddlewarePoolConnection::Sqlite {
117                mut conn,
118                translate_placeholders,
119            } => conn
120                .take()
121                .map(|conn| (conn, translate_placeholders))
122                .ok_or_else(|| {
123                    SqlMiddlewareDbError::ExecutionError(
124                        "SQLite connection already taken from pool wrapper".into(),
125                    )
126                }),
127            _ => Err(SqlMiddlewareDbError::Unimplemented(
128                "into_sqlite is only available for SQLite connections".to_string(),
129            )),
130        }
131    }
132
133    /// Rewrap a `SQLite` connection back into the enum with the original translation flag.
134    #[must_use]
135    pub fn from_sqlite_parts(
136        conn: SqliteConnection,
137        translate_placeholders: bool,
138    ) -> MiddlewarePoolConnection {
139        MiddlewarePoolConnection::Sqlite {
140            conn: Some(conn),
141            translate_placeholders,
142        }
143    }
144}