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}