Skip to main content

sql_middleware/translation/
mod.rs

1use std::borrow::Cow;
2
3use crate::types::StatementCacheMode;
4
5mod parsers;
6mod scanner;
7#[cfg(test)]
8mod tests;
9mod translate;
10
11/// Target placeholder style for translation.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum PlaceholderStyle {
14    /// PostgreSQL-style placeholders like `$1`.
15    Postgres,
16    /// SQLite-style placeholders like `?1` (also used by Turso).
17    Sqlite,
18}
19
20/// How to resolve translation for a call relative to the pool default.
21///
22/// # Examples
23/// ```rust
24/// use sql_middleware::prelude::*;
25///
26/// let options = QueryOptions::default()
27///     .with_translation(TranslationMode::ForceOn);
28/// # let _ = options;
29/// ```
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum TranslationMode {
32    /// Follow the pool's default setting.
33    PoolDefault,
34    /// Force translation on, regardless of pool default.
35    ForceOn,
36    /// Force translation off, regardless of pool default.
37    ForceOff,
38}
39
40impl TranslationMode {
41    #[must_use]
42    pub fn resolve(self, pool_default: bool) -> bool {
43        match self {
44            TranslationMode::PoolDefault => pool_default,
45            TranslationMode::ForceOn => true,
46            TranslationMode::ForceOff => false,
47        }
48    }
49}
50
51/// How to resolve prepared execution for a call.
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
53pub enum PrepareMode {
54    /// Execute without preparing.
55    #[default]
56    Direct,
57    /// Prepare the statement before execution.
58    Prepared,
59}
60
61/// Per-call options for query/execute paths.
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub struct QueryOptions {
64    pub translation: TranslationMode,
65    pub prepare: PrepareMode,
66    pub statement_cache: Option<StatementCacheMode>,
67}
68
69impl Default for QueryOptions {
70    fn default() -> Self {
71        Self {
72            translation: TranslationMode::PoolDefault,
73            prepare: PrepareMode::default(),
74            statement_cache: None,
75        }
76    }
77}
78
79impl QueryOptions {
80    #[must_use]
81    pub fn with_translation(mut self, translation: TranslationMode) -> Self {
82        self.translation = translation;
83        self
84    }
85
86    #[must_use]
87    pub fn with_prepare(mut self, prepare: PrepareMode) -> Self {
88        self.prepare = prepare;
89        self
90    }
91
92    #[must_use]
93    pub fn with_statement_cache(mut self, statement_cache: StatementCacheMode) -> Self {
94        self.statement_cache = Some(statement_cache);
95        self
96    }
97}
98
99/// Translate placeholders between Postgres-style `$N` and SQLite-style `?N`.
100///
101/// Warning: translation skips quoted strings, comments, and dollar-quoted blocks via a lightweight
102/// state machine; it may still miss edge cases in complex SQL. For dialect-specific SQL (e.g.,
103/// PL/pgSQL bodies), prefer backend-specific SQL instead of relying on translation:
104/// ```rust
105/// # use sql_middleware::prelude::*;
106/// # async fn demo(conn: &mut MiddlewarePoolConnection) -> Result<(), SqlMiddlewareDbError> {
107/// let query = match conn {
108///     MiddlewarePoolConnection::Postgres { .. } => r#"$function$
109/// BEGIN
110///     RETURN ($1 ~ $q$[\t\r\n\v\\]$q$);
111/// END;
112/// $function$"#,
113///     MiddlewarePoolConnection::Sqlite { .. } | MiddlewarePoolConnection::Turso { .. } => {
114///         "SELECT regexp_replace(body, ?1, ?2) FROM rules"
115///     }
116///     MiddlewarePoolConnection::Mssql { .. } => {
117///         "SELECT REPLACE(body, @P1, @P2) FROM rules"
118///     }
119/// };
120/// # let _ = query;
121/// # Ok(())
122/// # }
123/// ```
124/// Returns a borrowed `Cow` when no changes are needed.
125#[must_use]
126pub fn translate_placeholders(sql: &str, target: PlaceholderStyle, enabled: bool) -> Cow<'_, str> {
127    translate::translate_sql(sql, target, enabled)
128}