Skip to main content

sql_middleware/query_builder/
mod.rs

1use std::borrow::Cow;
2
3use crate::executor::QueryTarget;
4use crate::pool::MiddlewarePoolConnection;
5use crate::translation::{PrepareMode, QueryOptions, TranslationMode, translate_placeholders};
6use crate::types::{RowValues, StatementCacheMode};
7
8mod dml;
9mod select;
10
11/// Fluent builder for query execution with optional placeholder translation.
12pub struct QueryBuilder<'conn, 'q> {
13    pub(crate) target: QueryTarget<'conn>,
14    pub(crate) sql: Cow<'q, str>,
15    pub(crate) params: Cow<'q, [RowValues]>,
16    pub(crate) options: QueryOptions,
17}
18
19impl<'conn, 'q> QueryBuilder<'conn, 'q> {
20    pub(crate) fn new(conn: &'conn mut MiddlewarePoolConnection, sql: &'q str) -> Self {
21        Self {
22            target: conn.into(),
23            sql: Cow::Borrowed(sql),
24            params: Cow::Borrowed(&[]),
25            options: QueryOptions::default(),
26        }
27    }
28
29    pub(crate) fn new_target(target: QueryTarget<'conn>, sql: &'q str) -> Self {
30        Self {
31            target,
32            sql: Cow::Borrowed(sql),
33            params: Cow::Borrowed(&[]),
34            options: QueryOptions::default(),
35        }
36    }
37
38    /// Provide parameters for this statement.
39    #[must_use]
40    pub fn params(mut self, params: &'q [RowValues]) -> Self {
41        self.params = Cow::Borrowed(params);
42        self
43    }
44
45    /// Override translation using `QueryOptions`.
46    #[must_use]
47    pub fn options(mut self, options: QueryOptions) -> Self {
48        self.options = options;
49        self
50    }
51
52    /// Override translation mode directly.
53    ///
54    /// Warning: translation skips placeholders inside quoted strings, comments, and dollar-quoted
55    /// blocks via a lightweight state machine; it may miss edge cases in complex SQL (e.g.,
56    /// PL/pgSQL bodies). Prefer backend-specific SQL instead of relying on translation:
57    /// ```rust
58    /// # use sql_middleware::prelude::*;
59    /// # async fn demo(conn: &mut MiddlewarePoolConnection) -> Result<(), SqlMiddlewareDbError> {
60    /// let query = match conn {
61    ///     MiddlewarePoolConnection::Postgres { .. } => r#"$function$
62    /// BEGIN
63    ///     RETURN ($1 ~ $q$[\t\r\n\v\\]$q$);
64    /// END;
65    /// $function$"#,
66    ///     MiddlewarePoolConnection::Sqlite { .. } | MiddlewarePoolConnection::Turso { .. } => {
67    ///         "SELECT regexp_replace(body, ?1, ?2) FROM rules"
68    ///     }
69    ///     MiddlewarePoolConnection::Mssql { .. } => {
70    ///         "SELECT REPLACE(body, @P1, @P2) FROM rules"
71    ///     }
72    /// };
73    /// # let _ = query;
74    /// # Ok(())
75    /// # }
76    /// ```
77    #[must_use]
78    pub fn translation(mut self, translation: TranslationMode) -> Self {
79        self.options.translation = translation;
80        self
81    }
82
83    /// Hint to prepare the statement before execution.
84    #[must_use]
85    pub fn prepare(mut self) -> Self {
86        self.options.prepare = PrepareMode::Prepared;
87        self
88    }
89
90    /// Override the statement cache mode for SQLite/Turso execution paths.
91    #[must_use]
92    pub fn statement_cache(mut self, statement_cache: StatementCacheMode) -> Self {
93        self.options.statement_cache = Some(statement_cache);
94        self
95    }
96}
97
98pub(super) fn translate_query_for_target<'a>(
99    target: &QueryTarget<'_>,
100    query: &'a str,
101    params: &[RowValues],
102    options: QueryOptions,
103) -> Cow<'a, str> {
104    if params.is_empty() {
105        return Cow::Borrowed(query);
106    }
107
108    let Some(style) = target.translation_target() else {
109        return Cow::Borrowed(query);
110    };
111
112    let enabled = options.translation.resolve(target.translation_default());
113    translate_placeholders(query, style, enabled)
114}