Skip to main content

drizzle_core/traits/
to_sql.rs

1//! `ToSQL` trait for converting types to SQL fragments.
2
3use crate::prelude::*;
4use crate::{
5    sql::{ColumnRef, SQL, TableRef, Token},
6    traits::SQLParam,
7};
8
9#[cfg(feature = "uuid")]
10use uuid::Uuid;
11
12/// Trait for types that can be converted to SQL fragments.
13///
14/// The `'a` lifetime ties any borrowed parameter values to the resulting SQL
15/// fragment, allowing zero-copy SQL construction when inputs are already
16/// borrowed.
17#[diagnostic::on_unimplemented(
18    message = "`{Self}` cannot be converted to SQL",
19    label = "this type does not implement ToSQL for the current dialect",
20    note = "tuples larger than the enabled arity need a larger `colN` feature (col16, col32, col64, col128, col200) on drizzle-core"
21)]
22pub trait ToSQL<'a, V: SQLParam> {
23    fn to_sql(&self) -> SQL<'a, V>;
24
25    /// Consume self and return SQL without cloning.
26    /// Default delegates to `to_sql()` (which clones). Types that own their SQL
27    /// (like `SQL` and `SQLExpr`) override this to avoid the clone.
28    fn into_sql(self) -> SQL<'a, V>
29    where
30        Self: Sized,
31    {
32        self.to_sql()
33    }
34}
35
36/// Wrapper for byte slices to avoid list semantics (`Vec<u8>` normally becomes a list).
37///
38/// Use this when you want a single BLOB/bytea parameter:
39/// ```rust
40/// # let _ = r####"
41/// use drizzle_core::{SQLBytes, SQL};
42///
43/// let data = vec![1u8, 2, 3];
44/// let sql = SQL::bytes(&data); // or SQL::param(SQLBytes::new(&data))
45/// # "####;
46/// ```
47#[derive(Debug, Clone)]
48pub struct SQLBytes<'a>(pub Cow<'a, [u8]>);
49
50/// Explicit SQL NULL marker.
51#[derive(Debug, Clone, Copy, Default)]
52pub struct SQLNull;
53
54impl<'a> SQLBytes<'a> {
55    #[inline]
56    pub fn new(bytes: impl Into<Cow<'a, [u8]>>) -> Self {
57        Self(bytes.into())
58    }
59}
60
61impl<'a, V: SQLParam + 'a> ToSQL<'a, V> for SQLNull {
62    fn to_sql(&self) -> SQL<'a, V> {
63        SQL::raw("NULL")
64    }
65}
66
67impl<'a, T, V> From<&T> for SQL<'a, V>
68where
69    T: ToSQL<'a, V>,
70    V: SQLParam,
71{
72    fn from(value: &T) -> Self {
73        value.to_sql()
74    }
75}
76
77impl<'a, V: SQLParam, T> ToSQL<'a, V> for &T
78where
79    T: ToSQL<'a, V>,
80{
81    fn to_sql(&self) -> SQL<'a, V> {
82        (**self).to_sql()
83    }
84}
85
86impl<'a, V: SQLParam + 'a> ToSQL<'a, V> for () {
87    fn to_sql(&self) -> SQL<'a, V> {
88        SQL::empty()
89    }
90}
91
92impl<'a, V, T> ToSQL<'a, V> for Vec<T>
93where
94    V: SQLParam + 'a,
95    T: ToSQL<'a, V>,
96{
97    fn to_sql(&self) -> SQL<'a, V> {
98        SQL::join(self.iter().map(ToSQL::to_sql), Token::COMMA)
99    }
100}
101
102impl<'a, V, T> ToSQL<'a, V> for &'a [T]
103where
104    V: SQLParam + 'a,
105    T: ToSQL<'a, V>,
106{
107    fn to_sql(&self) -> SQL<'a, V> {
108        SQL::join(self.iter().map(ToSQL::to_sql), Token::COMMA)
109    }
110}
111
112impl<'a, V, T, const N: usize> ToSQL<'a, V> for [T; N]
113where
114    V: SQLParam + 'a,
115    T: ToSQL<'a, V>,
116{
117    fn to_sql(&self) -> SQL<'a, V> {
118        SQL::join(self.iter().map(ToSQL::to_sql), Token::COMMA)
119    }
120}
121
122impl<'a, V: SQLParam + 'a> ToSQL<'a, V> for TableRef {
123    fn to_sql(&self) -> SQL<'a, V> {
124        SQL::table(*self)
125    }
126}
127
128impl<'a, V: SQLParam + 'a> ToSQL<'a, V> for ColumnRef {
129    fn to_sql(&self) -> SQL<'a, V> {
130        SQL::column(*self)
131    }
132}
133
134// Implement ToSQL for primitive types
135impl<'a, V> ToSQL<'a, V> for &'a str
136where
137    V: SQLParam + 'a + From<&'a str> + Into<Cow<'a, V>>,
138{
139    fn to_sql(&self) -> SQL<'a, V> {
140        SQL::param(V::from(self))
141    }
142}
143
144impl<'a, V> ToSQL<'a, V> for Box<str>
145where
146    V: SQLParam + 'a + From<String> + Into<Cow<'a, V>>,
147{
148    fn to_sql(&self) -> SQL<'a, V> {
149        SQL::param(V::from(self.to_string()))
150    }
151
152    fn into_sql(self) -> SQL<'a, V> {
153        SQL::param(V::from(self.into_string()))
154    }
155}
156
157#[cfg(any(feature = "std", feature = "alloc"))]
158impl<'a, V> ToSQL<'a, V> for Rc<str>
159where
160    V: SQLParam + 'a + From<String> + Into<Cow<'a, V>>,
161{
162    fn to_sql(&self) -> SQL<'a, V> {
163        SQL::param(V::from(self.as_ref().to_string()))
164    }
165}
166
167#[cfg(any(feature = "std", feature = "alloc"))]
168impl<'a, V> ToSQL<'a, V> for Arc<str>
169where
170    V: SQLParam + 'a + From<String> + Into<Cow<'a, V>>,
171{
172    fn to_sql(&self) -> SQL<'a, V> {
173        SQL::param(V::from(self.as_ref().to_string()))
174    }
175}
176
177impl<'a, V, T> ToSQL<'a, V> for Box<T>
178where
179    V: SQLParam + 'a,
180    T: ToSQL<'a, V>,
181{
182    fn to_sql(&self) -> SQL<'a, V> {
183        (**self).to_sql()
184    }
185}
186
187#[cfg(any(feature = "std", feature = "alloc"))]
188impl<'a, V, T> ToSQL<'a, V> for Rc<T>
189where
190    V: SQLParam + 'a,
191    T: ToSQL<'a, V>,
192{
193    fn to_sql(&self) -> SQL<'a, V> {
194        (**self).to_sql()
195    }
196}
197
198#[cfg(any(feature = "std", feature = "alloc"))]
199impl<'a, V, T> ToSQL<'a, V> for Arc<T>
200where
201    V: SQLParam + 'a,
202    T: ToSQL<'a, V>,
203{
204    fn to_sql(&self) -> SQL<'a, V> {
205        (**self).to_sql()
206    }
207}
208
209impl<'a, V> ToSQL<'a, V> for String
210where
211    V: SQLParam + 'a + From<Self> + Into<Cow<'a, V>>,
212{
213    fn to_sql(&self) -> SQL<'a, V> {
214        SQL::param(V::from(self.clone()))
215    }
216
217    fn into_sql(self) -> SQL<'a, V> {
218        SQL::param(V::from(self))
219    }
220}
221
222#[cfg(feature = "compact-str")]
223impl<'a, V> ToSQL<'a, V> for compact_str::CompactString
224where
225    V: SQLParam + 'a + From<Self> + Into<Cow<'a, V>>,
226{
227    fn to_sql(&self) -> SQL<'a, V> {
228        SQL::param(V::from(self.clone()))
229    }
230
231    fn into_sql(self) -> SQL<'a, V> {
232        SQL::param(V::from(self))
233    }
234}
235
236#[cfg(feature = "bytes")]
237impl<'a, V> ToSQL<'a, V> for bytes::Bytes
238where
239    V: SQLParam + 'a + From<Self> + Into<Cow<'a, V>>,
240{
241    fn to_sql(&self) -> SQL<'a, V> {
242        SQL::param(V::from(self.clone()))
243    }
244
245    fn into_sql(self) -> SQL<'a, V> {
246        SQL::param(V::from(self))
247    }
248}
249
250#[cfg(feature = "bytes")]
251impl<'a, V> ToSQL<'a, V> for bytes::BytesMut
252where
253    V: SQLParam + 'a + From<Self> + Into<Cow<'a, V>>,
254{
255    fn to_sql(&self) -> SQL<'a, V> {
256        SQL::param(V::from(self.clone()))
257    }
258
259    fn into_sql(self) -> SQL<'a, V> {
260        SQL::param(V::from(self))
261    }
262}
263
264#[cfg(feature = "arrayvec")]
265impl<'a, V, const N: usize> ToSQL<'a, V> for arrayvec::ArrayString<N>
266where
267    V: SQLParam + 'a + From<Self> + Into<Cow<'a, V>>,
268{
269    fn to_sql(&self) -> SQL<'a, V> {
270        SQL::param(V::from(*self))
271    }
272
273    fn into_sql(self) -> SQL<'a, V> {
274        SQL::param(V::from(self))
275    }
276}
277
278#[cfg(feature = "arrayvec")]
279impl<'a, V, const N: usize> ToSQL<'a, V> for arrayvec::ArrayVec<u8, N>
280where
281    V: SQLParam + 'a + From<Self> + Into<Cow<'a, V>>,
282{
283    fn to_sql(&self) -> SQL<'a, V> {
284        SQL::param(V::from(self.clone()))
285    }
286
287    fn into_sql(self) -> SQL<'a, V> {
288        SQL::param(V::from(self))
289    }
290}
291
292#[cfg(feature = "smallvec-types")]
293impl<'a, V, const N: usize> ToSQL<'a, V> for smallvec::SmallVec<[u8; N]>
294where
295    V: SQLParam + 'a + From<Self> + Into<Cow<'a, V>>,
296{
297    fn to_sql(&self) -> SQL<'a, V> {
298        SQL::param(V::from(self.clone()))
299    }
300
301    fn into_sql(self) -> SQL<'a, V> {
302        SQL::param(V::from(self))
303    }
304}
305
306impl<'a, V> ToSQL<'a, V> for Cow<'a, str>
307where
308    V: SQLParam + 'a + From<&'a str> + From<String> + Into<Cow<'a, V>>,
309{
310    fn to_sql(&self) -> SQL<'a, V> {
311        match self {
312            Cow::Borrowed(value) => SQL::param(V::from(*value)),
313            Cow::Owned(value) => SQL::param(V::from(value.clone())),
314        }
315    }
316
317    fn into_sql(self) -> SQL<'a, V> {
318        match self {
319            Cow::Borrowed(value) => SQL::param(V::from(value)),
320            Cow::Owned(value) => SQL::param(V::from(value)),
321        }
322    }
323}
324
325impl<'a, V> ToSQL<'a, V> for Cow<'a, [u8]>
326where
327    V: SQLParam + 'a + From<&'a [u8]> + From<Vec<u8>> + Into<Cow<'a, V>>,
328{
329    fn to_sql(&self) -> SQL<'a, V> {
330        match self {
331            Cow::Borrowed(value) => SQL::param(V::from(*value)),
332            Cow::Owned(value) => SQL::param(V::from(value.clone())),
333        }
334    }
335
336    fn into_sql(self) -> SQL<'a, V> {
337        match self {
338            Cow::Borrowed(value) => SQL::param(V::from(value)),
339            Cow::Owned(value) => SQL::param(V::from(value)),
340        }
341    }
342}
343
344impl<'a, V> ToSQL<'a, V> for SQLBytes<'a>
345where
346    V: SQLParam + 'a + From<&'a [u8]> + From<Vec<u8>> + Into<Cow<'a, V>>,
347{
348    fn to_sql(&self) -> SQL<'a, V> {
349        match &self.0 {
350            Cow::Borrowed(value) => SQL::param(V::from(*value)),
351            Cow::Owned(value) => SQL::param(V::from(value.clone())),
352        }
353    }
354
355    fn into_sql(self) -> SQL<'a, V> {
356        match self.0 {
357            Cow::Borrowed(value) => SQL::param(V::from(value)),
358            Cow::Owned(value) => SQL::param(V::from(value)),
359        }
360    }
361}
362
363macro_rules! impl_tosql_param_copy {
364    ($($ty:ty),+ $(,)?) => {
365        $(
366            impl<'a, V> ToSQL<'a, V> for $ty
367            where
368                V: SQLParam + 'a + From<$ty>,
369                V: Into<Cow<'a, V>>,
370            {
371                fn to_sql(&self) -> SQL<'a, V> {
372                    SQL::param(V::from(*self))
373                }
374            }
375        )+
376    };
377}
378
379impl_tosql_param_copy!(
380    i8, i16, i32, i64, f32, f64, bool, char, u8, u16, u32, u64, isize, usize
381);
382
383impl<'a, V, T> ToSQL<'a, V> for Option<T>
384where
385    V: SQLParam + 'a,
386    T: ToSQL<'a, V>,
387{
388    fn to_sql(&self) -> SQL<'a, V> {
389        self.as_ref()
390            .map_or_else(|| SQLNull.to_sql(), ToSQL::to_sql)
391    }
392}
393
394#[cfg(feature = "uuid")]
395impl<'a, V> ToSQL<'a, V> for Uuid
396where
397    V: SQLParam + 'a + From<Self> + Into<Cow<'a, V>>,
398{
399    fn to_sql(&self) -> SQL<'a, V> {
400        SQL::param(V::from(*self))
401    }
402}
403
404// Date and time values bind as parameters in every dialect that stores them.
405#[cfg(feature = "chrono")]
406impl_tosql_param_copy!(
407    chrono::NaiveDate,
408    chrono::NaiveTime,
409    chrono::NaiveDateTime,
410    chrono::DateTime<chrono::Utc>,
411    chrono::DateTime<chrono::FixedOffset>,
412    chrono::Duration,
413);
414
415#[cfg(feature = "time")]
416impl_tosql_param_copy!(
417    time::Date,
418    time::Time,
419    time::PrimitiveDateTime,
420    time::OffsetDateTime,
421    time::Duration,
422);
423
424#[cfg(feature = "jiff")]
425impl_tosql_param_copy!(
426    jiff::civil::Date,
427    jiff::civil::Time,
428    jiff::civil::DateTime,
429    jiff::Timestamp,
430);
431
432#[cfg(feature = "rust-decimal")]
433impl<'a, V> ToSQL<'a, V> for rust_decimal::Decimal
434where
435    V: SQLParam + 'a + From<Self> + Into<Cow<'a, V>>,
436{
437    fn to_sql(&self) -> SQL<'a, V> {
438        SQL::param(V::from(*self))
439    }
440}