Skip to main content

sql_middleware/sqlite/
prepared.rs

1use std::sync::Arc;
2
3use crate::adapters::params::convert_params;
4use crate::middleware::{ConversionMode, CustomDbRow, ResultSet, RowValues, SqlMiddlewareDbError};
5use crate::types::StatementCacheMode;
6
7use super::connection::{SqliteConnection, run_blocking};
8use super::params::{Params, SqliteParamsBuf};
9use super::query::sqlite_extract_value_sync;
10
11/// Handle to a prepared `SQLite` statement tied to a connection.
12pub struct SqlitePreparedStatement<'conn> {
13    connection: &'conn mut SqliteConnection,
14    query: Arc<String>,
15    statement_cache_mode: StatementCacheMode,
16}
17
18impl<'conn> SqlitePreparedStatement<'conn> {
19    pub(crate) fn new(
20        connection: &'conn mut SqliteConnection,
21        query: Arc<String>,
22        statement_cache_mode: StatementCacheMode,
23    ) -> Self {
24        Self {
25            connection,
26            query,
27            statement_cache_mode,
28        }
29    }
30
31    /// Start configuring a prepared SELECT execution.
32    #[must_use]
33    pub fn select(&mut self) -> SqlitePreparedSelect<'_, 'conn, '_> {
34        SqlitePreparedSelect {
35            statement: self,
36            params: SqlitePreparedParams::None,
37        }
38    }
39
40    /// Start configuring a prepared DML execution.
41    #[must_use]
42    pub fn execute(&mut self) -> SqlitePreparedExecute<'_, 'conn, '_> {
43        SqlitePreparedExecute {
44            statement: self,
45            params: SqlitePreparedParams::None,
46        }
47    }
48
49    /// Execute the prepared statement as a query and materialise the rows into a [`ResultSet`].
50    ///
51    /// # Errors
52    /// Returns [`SqlMiddlewareDbError`] if execution fails or result conversion encounters an issue.
53    pub(crate) async fn query(
54        &mut self,
55        params: &[RowValues],
56    ) -> Result<ResultSet, SqlMiddlewareDbError> {
57        let params_owned = convert_params::<Params>(params, ConversionMode::Query)?.0;
58        self.connection
59            .execute_select(
60                self.query.as_ref(),
61                &params_owned,
62                super::query::build_result_set,
63                self.statement_cache_mode,
64            )
65            .await
66    }
67
68    /// Execute the prepared statement using a reusable SQLite parameter buffer.
69    ///
70    /// # Errors
71    /// Returns [`SqlMiddlewareDbError`] if execution fails or result conversion encounters an issue.
72    pub(crate) async fn query_params(
73        &mut self,
74        params: &SqliteParamsBuf,
75    ) -> Result<ResultSet, SqlMiddlewareDbError> {
76        self.connection
77            .execute_select(
78                self.query.as_ref(),
79                params.as_values(),
80                super::query::build_result_set,
81                self.statement_cache_mode,
82            )
83            .await
84    }
85
86    /// Execute the prepared statement and return the first row, if present.
87    ///
88    /// This avoids building a full [`ResultSet`] when callers only need one row.
89    ///
90    /// # Errors
91    /// Returns [`SqlMiddlewareDbError`] if execution or row conversion fails.
92    pub(crate) async fn query_optional(
93        &mut self,
94        params: &[RowValues],
95    ) -> Result<Option<CustomDbRow>, SqlMiddlewareDbError> {
96        let params_owned = convert_params::<Params>(params, ConversionMode::Query)?.0;
97        self.query_optional_values(params_owned).await
98    }
99
100    /// Execute the prepared statement with a reusable parameter buffer and return the first row,
101    /// if present.
102    ///
103    /// # Errors
104    /// Returns [`SqlMiddlewareDbError`] if execution or row conversion fails.
105    pub(crate) async fn query_optional_params(
106        &mut self,
107        params: &SqliteParamsBuf,
108    ) -> Result<Option<CustomDbRow>, SqlMiddlewareDbError> {
109        self.query_optional_values(params.as_values().to_vec())
110            .await
111    }
112
113    async fn query_optional_values(
114        &mut self,
115        params_owned: Vec<rusqlite::types::Value>,
116    ) -> Result<Option<CustomDbRow>, SqlMiddlewareDbError> {
117        let sql = Arc::clone(&self.query);
118        let statement_cache_mode = self.statement_cache_mode;
119        run_blocking(
120            self.connection.conn_handle(),
121            move |guard| match statement_cache_mode {
122                StatementCacheMode::Cached => {
123                    let mut stmt = guard
124                        .prepare_cached(sql.as_ref())
125                        .map_err(SqlMiddlewareDbError::SqliteError)?;
126                    query_optional_with_statement(&mut stmt, &params_owned)
127                }
128                StatementCacheMode::Uncached => {
129                    let mut stmt = guard
130                        .prepare(sql.as_ref())
131                        .map_err(SqlMiddlewareDbError::SqliteError)?;
132                    query_optional_with_statement(&mut stmt, &params_owned)
133                }
134            },
135        )
136        .await
137    }
138
139    /// Execute the prepared statement and return the first row.
140    ///
141    /// # Errors
142    /// Returns [`SqlMiddlewareDbError`] if execution fails, row conversion fails, or no row is
143    /// returned.
144    pub(crate) async fn query_one(
145        &mut self,
146        params: &[RowValues],
147    ) -> Result<CustomDbRow, SqlMiddlewareDbError> {
148        self.query_optional(params)
149            .await?
150            .ok_or_else(|| SqlMiddlewareDbError::SqliteError(rusqlite::Error::QueryReturnedNoRows))
151    }
152
153    /// Execute the prepared statement with a reusable parameter buffer and return the first row.
154    ///
155    /// # Errors
156    /// Returns [`SqlMiddlewareDbError`] if execution fails, row conversion fails, or no row is
157    /// returned.
158    pub(crate) async fn query_one_params(
159        &mut self,
160        params: &SqliteParamsBuf,
161    ) -> Result<CustomDbRow, SqlMiddlewareDbError> {
162        self.query_optional_params(params)
163            .await?
164            .ok_or_else(|| SqlMiddlewareDbError::SqliteError(rusqlite::Error::QueryReturnedNoRows))
165    }
166
167    /// Execute the prepared statement and map the first row inside the SQLite worker.
168    ///
169    /// Use this for hot paths that only need one row and can decode directly from
170    /// `rusqlite::Row`, avoiding `ResultSet` materialisation.
171    ///
172    /// # Errors
173    /// Returns [`SqlMiddlewareDbError`] if execution fails, the mapper fails, or no row is
174    /// returned.
175    pub(crate) async fn query_map_one<T, F>(
176        &mut self,
177        params: &[RowValues],
178        mapper: F,
179    ) -> Result<T, SqlMiddlewareDbError>
180    where
181        T: Send + 'static,
182        F: FnOnce(&rusqlite::Row<'_>) -> Result<T, SqlMiddlewareDbError> + Send + 'static,
183    {
184        self.query_map_optional(params, mapper)
185            .await?
186            .ok_or_else(|| SqlMiddlewareDbError::SqliteError(rusqlite::Error::QueryReturnedNoRows))
187    }
188
189    /// Execute the prepared statement with a reusable parameter buffer and map the first row
190    /// inside the SQLite worker.
191    ///
192    /// # Errors
193    /// Returns [`SqlMiddlewareDbError`] if execution fails, the mapper fails, or no row is
194    /// returned.
195    pub(crate) async fn query_map_one_params<T, F>(
196        &mut self,
197        params: &SqliteParamsBuf,
198        mapper: F,
199    ) -> Result<T, SqlMiddlewareDbError>
200    where
201        T: Send + 'static,
202        F: FnOnce(&rusqlite::Row<'_>) -> Result<T, SqlMiddlewareDbError> + Send + 'static,
203    {
204        self.query_map_optional_params(params, mapper)
205            .await?
206            .ok_or_else(|| SqlMiddlewareDbError::SqliteError(rusqlite::Error::QueryReturnedNoRows))
207    }
208
209    /// Execute the prepared statement and map the first row inside the SQLite worker, returning
210    /// `None` when the query has no rows.
211    ///
212    /// # Errors
213    /// Returns [`SqlMiddlewareDbError`] if execution or the mapper fails.
214    pub(crate) async fn query_map_optional<T, F>(
215        &mut self,
216        params: &[RowValues],
217        mapper: F,
218    ) -> Result<Option<T>, SqlMiddlewareDbError>
219    where
220        T: Send + 'static,
221        F: FnOnce(&rusqlite::Row<'_>) -> Result<T, SqlMiddlewareDbError> + Send + 'static,
222    {
223        let params_owned = convert_params::<Params>(params, ConversionMode::Query)?.0;
224        self.query_map_optional_values(params_owned, mapper).await
225    }
226
227    /// Execute the prepared statement with a reusable parameter buffer and map the first row
228    /// inside the SQLite worker, returning `None` when the query has no rows.
229    ///
230    /// # Errors
231    /// Returns [`SqlMiddlewareDbError`] if execution or the mapper fails.
232    pub(crate) async fn query_map_optional_params<T, F>(
233        &mut self,
234        params: &SqliteParamsBuf,
235        mapper: F,
236    ) -> Result<Option<T>, SqlMiddlewareDbError>
237    where
238        T: Send + 'static,
239        F: FnOnce(&rusqlite::Row<'_>) -> Result<T, SqlMiddlewareDbError> + Send + 'static,
240    {
241        self.query_map_optional_values(params.as_values().to_vec(), mapper)
242            .await
243    }
244
245    async fn query_map_optional_values<T, F>(
246        &mut self,
247        params_owned: Vec<rusqlite::types::Value>,
248        mapper: F,
249    ) -> Result<Option<T>, SqlMiddlewareDbError>
250    where
251        T: Send + 'static,
252        F: FnOnce(&rusqlite::Row<'_>) -> Result<T, SqlMiddlewareDbError> + Send + 'static,
253    {
254        let sql = Arc::clone(&self.query);
255        let statement_cache_mode = self.statement_cache_mode;
256        run_blocking(
257            self.connection.conn_handle(),
258            move |guard| match statement_cache_mode {
259                StatementCacheMode::Cached => {
260                    let mut stmt = guard
261                        .prepare_cached(sql.as_ref())
262                        .map_err(SqlMiddlewareDbError::SqliteError)?;
263                    query_map_optional_with_statement(&mut stmt, &params_owned, mapper)
264                }
265                StatementCacheMode::Uncached => {
266                    let mut stmt = guard
267                        .prepare(sql.as_ref())
268                        .map_err(SqlMiddlewareDbError::SqliteError)?;
269                    query_map_optional_with_statement(&mut stmt, &params_owned, mapper)
270                }
271            },
272        )
273        .await
274    }
275
276    /// Execute the prepared statement as a DML (INSERT/UPDATE/DELETE) returning rows affected.
277    ///
278    /// # Errors
279    /// Returns [`SqlMiddlewareDbError`] if execution fails or if the result cannot be converted into the expected row count.
280    pub(crate) async fn execute_values(
281        &mut self,
282        params: &[RowValues],
283    ) -> Result<usize, SqlMiddlewareDbError> {
284        let params_owned = convert_params::<Params>(params, ConversionMode::Execute)?.0;
285        self.connection
286            .execute_dml(
287                self.query.as_ref(),
288                &params_owned,
289                self.statement_cache_mode,
290            )
291            .await
292    }
293
294    /// Execute the prepared statement as DML using a reusable SQLite parameter buffer.
295    ///
296    /// # Errors
297    /// Returns [`SqlMiddlewareDbError`] if execution fails or if the result cannot be converted into the expected row count.
298    pub(crate) async fn execute_params(
299        &mut self,
300        params: &SqliteParamsBuf,
301    ) -> Result<usize, SqlMiddlewareDbError> {
302        self.connection
303            .execute_dml(
304                self.query.as_ref(),
305                params.as_values(),
306                self.statement_cache_mode,
307            )
308            .await
309    }
310
311    /// Access the raw SQL string of the prepared statement.
312    #[must_use]
313    pub fn sql(&self) -> &str {
314        self.query.as_str()
315    }
316}
317
318/// Builder for executing a prepared SQLite DML statement.
319pub struct SqlitePreparedExecute<'stmt, 'conn, 'params> {
320    statement: &'stmt mut SqlitePreparedStatement<'conn>,
321    params: SqlitePreparedParams<'params>,
322}
323
324impl<'stmt, 'conn, 'params> SqlitePreparedExecute<'stmt, 'conn, 'params> {
325    /// Use middleware `RowValues` parameters.
326    #[must_use]
327    pub fn params<'next>(
328        self,
329        params: &'next [RowValues],
330    ) -> SqlitePreparedExecute<'stmt, 'conn, 'next> {
331        SqlitePreparedExecute {
332            statement: self.statement,
333            params: SqlitePreparedParams::RowValues(params),
334        }
335    }
336
337    /// Use a reusable SQLite parameter buffer.
338    #[must_use]
339    pub fn params_buf<'next>(
340        self,
341        params: &'next SqliteParamsBuf,
342    ) -> SqlitePreparedExecute<'stmt, 'conn, 'next> {
343        SqlitePreparedExecute {
344            statement: self.statement,
345            params: SqlitePreparedParams::Buffer(params),
346        }
347    }
348
349    /// Execute the DML statement and return affected rows.
350    ///
351    /// # Errors
352    /// Returns [`SqlMiddlewareDbError`] if execution fails or the row count cannot be converted.
353    pub async fn run(self) -> Result<usize, SqlMiddlewareDbError> {
354        match self.params {
355            SqlitePreparedParams::None => self.statement.execute_values(&[]).await,
356            SqlitePreparedParams::RowValues(params) => self.statement.execute_values(params).await,
357            SqlitePreparedParams::Buffer(params) => self.statement.execute_params(params).await,
358        }
359    }
360}
361
362enum SqlitePreparedParams<'params> {
363    None,
364    RowValues(&'params [RowValues]),
365    Buffer(&'params SqliteParamsBuf),
366}
367
368/// Builder for executing a prepared SQLite SELECT.
369pub struct SqlitePreparedSelect<'stmt, 'conn, 'params> {
370    statement: &'stmt mut SqlitePreparedStatement<'conn>,
371    params: SqlitePreparedParams<'params>,
372}
373
374impl<'stmt, 'conn, 'params> SqlitePreparedSelect<'stmt, 'conn, 'params> {
375    /// Use middleware `RowValues` parameters.
376    #[must_use]
377    pub fn params<'next>(
378        self,
379        params: &'next [RowValues],
380    ) -> SqlitePreparedSelect<'stmt, 'conn, 'next> {
381        SqlitePreparedSelect {
382            statement: self.statement,
383            params: SqlitePreparedParams::RowValues(params),
384        }
385    }
386
387    /// Use a reusable SQLite parameter buffer.
388    #[must_use]
389    pub fn params_buf<'next>(
390        self,
391        params: &'next SqliteParamsBuf,
392    ) -> SqlitePreparedSelect<'stmt, 'conn, 'next> {
393        SqlitePreparedSelect {
394            statement: self.statement,
395            params: SqlitePreparedParams::Buffer(params),
396        }
397    }
398
399    /// Execute and return all rows as a `ResultSet`.
400    ///
401    /// # Errors
402    /// Returns [`SqlMiddlewareDbError`] if execution fails or result conversion encounters an issue.
403    pub async fn all(self) -> Result<ResultSet, SqlMiddlewareDbError> {
404        match self.params {
405            SqlitePreparedParams::None => self.statement.query(&[]).await,
406            SqlitePreparedParams::RowValues(params) => self.statement.query(params).await,
407            SqlitePreparedParams::Buffer(params) => self.statement.query_params(params).await,
408        }
409    }
410
411    /// Execute and return the first row, if present.
412    ///
413    /// # Errors
414    /// Returns [`SqlMiddlewareDbError`] if execution or row conversion fails.
415    pub async fn optional(self) -> Result<Option<CustomDbRow>, SqlMiddlewareDbError> {
416        match self.params {
417            SqlitePreparedParams::None => self.statement.query_optional(&[]).await,
418            SqlitePreparedParams::RowValues(params) => self.statement.query_optional(params).await,
419            SqlitePreparedParams::Buffer(params) => {
420                self.statement.query_optional_params(params).await
421            }
422        }
423    }
424
425    /// Execute and return exactly one row.
426    ///
427    /// # Errors
428    /// Returns [`SqlMiddlewareDbError`] if execution fails, row conversion fails, or no row is returned.
429    pub async fn one(self) -> Result<CustomDbRow, SqlMiddlewareDbError> {
430        match self.params {
431            SqlitePreparedParams::None => self.statement.query_one(&[]).await,
432            SqlitePreparedParams::RowValues(params) => self.statement.query_one(params).await,
433            SqlitePreparedParams::Buffer(params) => self.statement.query_one_params(params).await,
434        }
435    }
436
437    /// Execute and map exactly one native SQLite row.
438    ///
439    /// # Errors
440    /// Returns [`SqlMiddlewareDbError`] if execution fails, the mapper fails, or no row is returned.
441    pub async fn map_one<T, F>(self, mapper: F) -> Result<T, SqlMiddlewareDbError>
442    where
443        T: Send + 'static,
444        F: FnOnce(&rusqlite::Row<'_>) -> Result<T, SqlMiddlewareDbError> + Send + 'static,
445    {
446        match self.params {
447            SqlitePreparedParams::None => self.statement.query_map_one(&[], mapper).await,
448            SqlitePreparedParams::RowValues(params) => {
449                self.statement.query_map_one(params, mapper).await
450            }
451            SqlitePreparedParams::Buffer(params) => {
452                self.statement.query_map_one_params(params, mapper).await
453            }
454        }
455    }
456
457    /// Execute and map the first native SQLite row, if present.
458    ///
459    /// # Errors
460    /// Returns [`SqlMiddlewareDbError`] if execution or the mapper fails.
461    pub async fn map_optional<T, F>(self, mapper: F) -> Result<Option<T>, SqlMiddlewareDbError>
462    where
463        T: Send + 'static,
464        F: FnOnce(&rusqlite::Row<'_>) -> Result<T, SqlMiddlewareDbError> + Send + 'static,
465    {
466        match self.params {
467            SqlitePreparedParams::None => self.statement.query_map_optional(&[], mapper).await,
468            SqlitePreparedParams::RowValues(params) => {
469                self.statement.query_map_optional(params, mapper).await
470            }
471            SqlitePreparedParams::Buffer(params) => {
472                self.statement
473                    .query_map_optional_params(params, mapper)
474                    .await
475            }
476        }
477    }
478}
479
480fn query_optional_with_statement(
481    stmt: &mut rusqlite::Statement<'_>,
482    params: &[rusqlite::types::Value],
483) -> Result<Option<CustomDbRow>, SqlMiddlewareDbError> {
484    let column_names = Arc::new(
485        stmt.column_names()
486            .into_iter()
487            .map(str::to_string)
488            .collect::<Vec<_>>(),
489    );
490    let col_count = column_names.len();
491    let param_refs = params
492        .iter()
493        .map(|value| value as &dyn rusqlite::ToSql)
494        .collect::<Vec<_>>();
495    let mut rows = stmt.query(&param_refs[..])?;
496
497    rows.next()?
498        .map(|row| row_to_custom_db_row(row, Arc::clone(&column_names), col_count))
499        .transpose()
500}
501
502fn query_map_optional_with_statement<T, F>(
503    stmt: &mut rusqlite::Statement<'_>,
504    params: &[rusqlite::types::Value],
505    mapper: F,
506) -> Result<Option<T>, SqlMiddlewareDbError>
507where
508    F: FnOnce(&rusqlite::Row<'_>) -> Result<T, SqlMiddlewareDbError>,
509{
510    let param_refs = params
511        .iter()
512        .map(|value| value as &dyn rusqlite::ToSql)
513        .collect::<Vec<_>>();
514    let mut rows = stmt.query(&param_refs[..])?;
515
516    rows.next()?.map(mapper).transpose()
517}
518
519fn row_to_custom_db_row(
520    row: &rusqlite::Row<'_>,
521    column_names: Arc<Vec<String>>,
522    col_count: usize,
523) -> Result<CustomDbRow, SqlMiddlewareDbError> {
524    let mut row_values = Vec::with_capacity(col_count);
525    for idx in 0..col_count {
526        row_values.push(sqlite_extract_value_sync(row, idx)?);
527    }
528    Ok(CustomDbRow::new(column_names, row_values))
529}