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}