Skip to main content

gatekeep_sqlx/
fragment.rs

1use std::marker::PhantomData;
2
3use sqlx::{
4    QueryBuilder,
5    types::{
6        Uuid,
7        time::{Date, OffsetDateTime, PrimitiveDateTime, Time},
8    },
9};
10
11mod backend;
12mod driver;
13mod tenant;
14pub use backend::GatekeepSqlxBackend;
15#[cfg(feature = "mysql")]
16pub use backend::MySqlBackend;
17#[cfg(feature = "postgres")]
18pub use backend::PostgresBackend;
19#[cfg(feature = "sqlite")]
20pub use backend::SqliteBackend;
21pub use driver::{
22    SqlxDriver, SqlxDriverError, infer_enabled_driver_from_url, validate_database_url_for_backend,
23};
24pub use tenant::{
25    MAX_TENANT_IDENTIFIER_BYTES, TenantColumn, TenantColumnError, TenantIdentifierPart,
26};
27
28/// Scalar value carried by a lowered SQL fragment.
29#[derive(Clone, Debug, PartialEq, Eq)]
30#[non_exhaustive]
31pub enum SqlxValue {
32    /// Boolean bind value.
33    Bool(bool),
34    /// Signed 16-bit integer bind value.
35    I16(i16),
36    /// Signed 32-bit integer bind value.
37    I32(i32),
38    /// Signed 64-bit integer bind value.
39    I64(i64),
40    /// Text bind value.
41    Text(String),
42    /// Binary bind value.
43    Bytes(Vec<u8>),
44    /// UUID bind value.
45    Uuid(Uuid),
46    /// Date bind value.
47    Date(Date),
48    /// Time bind value.
49    Time(Time),
50    /// Timestamp without time zone bind value.
51    Timestamp(PrimitiveDateTime),
52    /// Timestamp with time zone bind value.
53    TimestampTz(OffsetDateTime),
54}
55
56macro_rules! impl_sqlx_value_from {
57    ($ty:ty, $variant:ident) => {
58        impl From<$ty> for SqlxValue {
59            fn from(value: $ty) -> Self {
60                Self::$variant(value)
61            }
62        }
63    };
64}
65
66impl_sqlx_value_from!(bool, Bool);
67impl_sqlx_value_from!(i16, I16);
68impl_sqlx_value_from!(i32, I32);
69impl_sqlx_value_from!(i64, I64);
70impl_sqlx_value_from!(String, Text);
71impl_sqlx_value_from!(Vec<u8>, Bytes);
72impl_sqlx_value_from!(Uuid, Uuid);
73impl_sqlx_value_from!(Date, Date);
74impl_sqlx_value_from!(Time, Time);
75impl_sqlx_value_from!(PrimitiveDateTime, Timestamp);
76impl_sqlx_value_from!(OffsetDateTime, TimestampTz);
77
78impl From<&str> for SqlxValue {
79    fn from(value: &str) -> Self {
80        Self::Text(value.to_owned())
81    }
82}
83
84impl From<&[u8]> for SqlxValue {
85    fn from(value: &[u8]) -> Self {
86        Self::Bytes(value.to_vec())
87    }
88}
89
90#[derive(Clone, Debug, PartialEq, Eq)]
91enum SqlPart {
92    Text(String),
93    Bind(SqlxValue),
94}
95
96/// Trusted SQL plus ordered bind values for one `SQLx` backend.
97#[derive(Debug, PartialEq, Eq)]
98pub struct SqlxFragment<B> {
99    parts: Vec<SqlPart>,
100    backend: PhantomData<fn() -> B>,
101}
102
103impl<B> Clone for SqlxFragment<B> {
104    fn clone(&self) -> Self {
105        Self {
106            parts: self.parts.clone(),
107            backend: PhantomData,
108        }
109    }
110}
111
112impl<B> Default for SqlxFragment<B> {
113    fn default() -> Self {
114        Self {
115            parts: Vec::new(),
116            backend: PhantomData,
117        }
118    }
119}
120
121impl<B> SqlxFragment<B> {
122    /// Builds a fragment from SQL owned by the application.
123    ///
124    /// Callers must not pass user-supplied text here. Dynamic values belong in
125    /// bind fragments built with [`Self::bind`].
126    #[must_use]
127    pub fn trusted(sql: impl Into<String>) -> Self {
128        let sql = sql.into();
129        if sql.is_empty() {
130            Self::default()
131        } else {
132            Self {
133                parts: vec![SqlPart::Text(sql)],
134                backend: PhantomData,
135            }
136        }
137    }
138
139    /// Builds a bind fragment from a supported `SQLx` scalar value.
140    #[must_use]
141    pub fn bind(value: impl Into<SqlxValue>) -> Self {
142        Self {
143            parts: vec![SqlPart::Bind(value.into())],
144            backend: PhantomData,
145        }
146    }
147
148    /// Returns the ordered bind values.
149    pub fn binds(&self) -> impl Iterator<Item = &SqlxValue> {
150        self.parts.iter().filter_map(|part| match part {
151            SqlPart::Text(_) => None,
152            SqlPart::Bind(value) => Some(value),
153        })
154    }
155
156    /// Appends another fragment to this one.
157    pub fn push_fragment(&mut self, fragment: Self) {
158        self.parts.extend(fragment.parts);
159    }
160
161    pub(crate) fn push_sql(&mut self, sql: impl Into<String>) {
162        let sql = sql.into();
163        if !sql.is_empty() {
164            self.parts.push(SqlPart::Text(sql));
165        }
166    }
167
168    #[must_use]
169    pub(crate) fn wrapped(self) -> Self {
170        let mut fragment = Self::trusted("(");
171        fragment.push_fragment(self);
172        fragment.push_sql(")");
173        fragment
174    }
175
176    #[must_use]
177    pub(crate) fn unary(prefix: &str, inner: Self) -> Self {
178        let mut fragment = Self::trusted(prefix);
179        fragment.push_fragment(inner.wrapped());
180        fragment
181    }
182
183    #[must_use]
184    pub(crate) fn binary(separator: &str, fragments: impl IntoIterator<Item = Self>) -> Self {
185        let mut iter = fragments.into_iter();
186        let Some(first) = iter.next() else {
187            return Self::trusted("FALSE");
188        };
189
190        let mut fragment = first.wrapped();
191        for next in iter {
192            fragment.push_sql(separator);
193            fragment.push_fragment(next.wrapped());
194        }
195        fragment
196    }
197
198    #[must_use]
199    pub(crate) fn function(name: &str, fragments: impl IntoIterator<Item = Self>) -> Self {
200        let mut fragment = Self::trusted(name);
201        fragment.push_sql("(");
202
203        let mut iter = fragments.into_iter();
204        if let Some(first) = iter.next() {
205            fragment.push_fragment(first);
206            for next in iter {
207                fragment.push_sql(", ");
208                fragment.push_fragment(next);
209            }
210        }
211
212        fragment.push_sql(")");
213        fragment
214    }
215}
216
217impl<B> SqlxFragment<B>
218where
219    B: GatekeepSqlxBackend,
220{
221    /// Converts the fragment to SQL with this backend's placeholder syntax.
222    #[must_use]
223    pub fn to_sql(&self) -> String {
224        let mut sql = String::new();
225        let mut placeholders = 0usize;
226
227        for part in &self.parts {
228            match part {
229                SqlPart::Text(text) => sql.push_str(text),
230                SqlPart::Bind(_) => {
231                    // Each bind occupies a Vec entry, so the count cannot exceed usize::MAX.
232                    placeholders = placeholders.saturating_add(1);
233                    B::push_placeholder(&mut sql, placeholders);
234                }
235            }
236        }
237        sql
238    }
239
240    /// Appends this fragment to a `SQLx` query builder.
241    pub fn push_to(&self, builder: &mut QueryBuilder<B::Database>) {
242        for part in &self.parts {
243            match part {
244                SqlPart::Text(text) => {
245                    builder.push(text);
246                }
247                SqlPart::Bind(value) => B::push_bind(builder, value),
248            }
249        }
250    }
251}
252
253/// Postgres scalar value carried by a lowered SQL fragment.
254#[cfg(feature = "postgres")]
255pub type PgValue = SqlxValue;
256
257/// Trusted Postgres SQL plus ordered bind values.
258#[cfg(feature = "postgres")]
259pub type PgFragment = SqlxFragment<PostgresBackend>;
260
261#[cfg(feature = "postgres")]
262impl SqlxFragment<PostgresBackend> {
263    /// Converts the fragment to Postgres placeholders (`$1`, `$2`, ...).
264    #[must_use]
265    pub fn to_postgres_sql(&self) -> String {
266        self.to_sql()
267    }
268}