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