Skip to main content

sql_middleware/pool/connection/
sqlite.rs

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