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}