sql_middleware/
query_builder.rs

1use std::borrow::Cow;
2
3use crate::error::SqlMiddlewareDbError;
4use crate::executor::{execute_dml_dispatch, execute_select_dispatch, translate_query};
5use crate::pool::MiddlewarePoolConnection;
6use crate::results::ResultSet;
7use crate::translation::{QueryOptions, TranslationMode};
8use crate::types::RowValues;
9
10/// Fluent builder for query execution with optional placeholder translation.
11pub struct QueryBuilder<'conn, 'q> {
12    conn: &'conn mut MiddlewarePoolConnection,
13    sql: Cow<'q, str>,
14    params: Cow<'q, [RowValues]>,
15    options: QueryOptions,
16}
17
18impl<'conn, 'q> QueryBuilder<'conn, 'q> {
19    pub(crate) fn new(conn: &'conn mut MiddlewarePoolConnection, sql: &'q str) -> Self {
20        Self {
21            conn,
22            sql: Cow::Borrowed(sql),
23            params: Cow::Borrowed(&[]),
24            options: QueryOptions::default(),
25        }
26    }
27
28    /// Provide parameters for this statement.
29    #[must_use]
30    pub fn params(mut self, params: &'q [RowValues]) -> Self {
31        self.params = Cow::Borrowed(params);
32        self
33    }
34
35    /// Override translation using `QueryOptions`.
36    #[must_use]
37    pub fn options(mut self, options: QueryOptions) -> Self {
38        self.options = options;
39        self
40    }
41
42    /// Override translation mode directly.
43    ///
44    /// Warning: translation skips placeholders inside quoted strings, comments, and dollar-quoted
45    /// blocks via a lightweight state machine; it may miss edge cases in complex SQL (e.g.,
46    /// PL/pgSQL bodies). Prefer backend-specific SQL instead of relying on translation:
47    /// ```rust
48    /// # use sql_middleware::prelude::*;
49    /// # async fn demo(conn: &mut MiddlewarePoolConnection) -> Result<(), SqlMiddlewareDbError> {
50    /// let query = match conn {
51    ///     MiddlewarePoolConnection::Postgres { .. } => r#"$function$
52    /// BEGIN
53    ///     RETURN ($1 ~ $q$[\t\r\n\v\\]$q$);
54    /// END;
55    /// $function$"#,
56    ///     MiddlewarePoolConnection::Sqlite { .. } | MiddlewarePoolConnection::Turso { .. } => {
57    ///         include_str!("../sql/functions/sqlite/03_sp_get_scores.sql")
58    ///     }
59    /// };
60    /// # let _ = query;
61    /// # Ok(())
62    /// # }
63    /// ```
64    #[must_use]
65    pub fn translation(mut self, translation: TranslationMode) -> Self {
66        self.options.translation = translation;
67        self
68    }
69
70    /// Execute a SELECT and return the result set.
71    ///
72    /// # Errors
73    /// Returns an error if placeholder translation fails or the backend query execution fails.
74    pub async fn select(self) -> Result<ResultSet, SqlMiddlewareDbError> {
75        let translated = translate_query(
76            self.conn,
77            self.sql.as_ref(),
78            self.params.as_ref(),
79            self.options,
80        );
81        execute_select_dispatch(self.conn, translated.as_ref(), self.params.as_ref()).await
82    }
83
84    /// Execute a DML statement and return rows affected.
85    ///
86    /// # Errors
87    /// Returns an error if placeholder translation fails or the backend DML execution fails.
88    pub async fn dml(self) -> Result<usize, SqlMiddlewareDbError> {
89        let translated = translate_query(
90            self.conn,
91            self.sql.as_ref(),
92            self.params.as_ref(),
93            self.options,
94        );
95        execute_dml_dispatch(self.conn, translated.as_ref(), self.params.as_ref()).await
96    }
97
98    /// Execute a batch (ignores params by design).
99    ///
100    /// # Errors
101    /// Returns an error if the backend rejects the batch or the connection fails.
102    pub async fn batch(self) -> Result<(), SqlMiddlewareDbError> {
103        self.conn.execute_batch(&self.sql).await
104    }
105}