Skip to main content

duckdb/types/
to_sql.rs

1use super::{Null, TimeUnit, Value, ValueRef, binding_unsupported_value, value_ref_from_value};
2use crate::Result;
3use std::borrow::Cow;
4
5/// `ToSqlOutput` represents the possible output types for implementers of the
6/// [`ToSql`] trait.
7#[derive(Clone, Debug, PartialEq)]
8#[non_exhaustive]
9pub enum ToSqlOutput<'a> {
10    /// A borrowed DuckDB-representable value.
11    Borrowed(ValueRef<'a>),
12
13    /// An owned DuckDB-representable value.
14    Owned(Value),
15}
16
17// Generically allow any type that can be converted into a ValueRef
18// to be converted into a ToSqlOutput as well.
19impl<'a, T: ?Sized> From<&'a T> for ToSqlOutput<'a>
20where
21    &'a T: Into<ValueRef<'a>>,
22{
23    #[inline]
24    fn from(t: &'a T) -> Self {
25        ToSqlOutput::Borrowed(t.into())
26    }
27}
28
29// We cannot also generically allow any type that can be converted
30// into a Value to be converted into a ToSqlOutput because of
31// coherence rules (https://github.com/rust-lang/rust/pull/46192),
32// so we'll manually implement it for all the types we know can
33// be converted into Values.
34macro_rules! from_value(
35    ($t:ty) => (
36        impl From<$t> for ToSqlOutput<'_> {
37            #[inline]
38            fn from(t: $t) -> Self { ToSqlOutput::Owned(t.into())}
39        }
40    )
41);
42from_value!(String);
43from_value!(Null);
44from_value!(bool);
45from_value!(i8);
46from_value!(i16);
47from_value!(i32);
48from_value!(i64);
49from_value!(i128);
50from_value!(u128);
51from_value!(isize);
52from_value!(u8);
53from_value!(u16);
54from_value!(u32);
55from_value!(u64);
56from_value!(usize);
57from_value!(f32);
58from_value!(f64);
59from_value!(Vec<u8>);
60
61#[cfg(feature = "uuid")]
62from_value!(uuid::Uuid);
63
64impl ToSql for ToSqlOutput<'_> {
65    #[inline]
66    fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
67        Ok(match *self {
68            ToSqlOutput::Borrowed(v) => ToSqlOutput::Borrowed(v),
69            ToSqlOutput::Owned(ref v) => ToSqlOutput::Borrowed(value_ref_from_value(v, binding_unsupported_value)?),
70        })
71    }
72}
73
74/// A trait for types that can be converted into DuckDB values. Returns
75/// [`Error::ToSqlConversionFailure`](crate::Error::ToSqlConversionFailure) if
76/// the conversion fails.
77pub trait ToSql {
78    /// Converts Rust value to DuckDB value
79    fn to_sql(&self) -> Result<ToSqlOutput<'_>>;
80}
81
82impl<T: ToSql + ToOwned + ?Sized> ToSql for Cow<'_, T> {
83    #[inline]
84    fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
85        self.as_ref().to_sql()
86    }
87}
88
89impl<T: ToSql + ?Sized> ToSql for Box<T> {
90    #[inline]
91    fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
92        self.as_ref().to_sql()
93    }
94}
95
96impl<T: ToSql + ?Sized> ToSql for std::rc::Rc<T> {
97    #[inline]
98    fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
99        self.as_ref().to_sql()
100    }
101}
102
103impl<T: ToSql + ?Sized> ToSql for std::sync::Arc<T> {
104    #[inline]
105    fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
106        self.as_ref().to_sql()
107    }
108}
109
110// We should be able to use a generic impl like this:
111//
112// impl<T: Copy> ToSql for T where T: Into<Value> {
113//     fn to_sql(&self) -> Result<ToSqlOutput> {
114//         Ok(ToSqlOutput::from((*self).into()))
115//     }
116// }
117//
118// instead of the following macro, but this runs afoul of
119// https://github.com/rust-lang/rust/issues/30191 and reports conflicting
120// implementations even when there aren't any.
121
122macro_rules! to_sql_self(
123    ($t:ty) => (
124        impl ToSql for $t {
125            #[inline]
126            fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
127                Ok(ToSqlOutput::from(*self))
128            }
129        }
130    )
131);
132
133to_sql_self!(Null);
134to_sql_self!(bool);
135to_sql_self!(i8);
136to_sql_self!(i16);
137to_sql_self!(i32);
138to_sql_self!(i64);
139to_sql_self!(i128);
140to_sql_self!(u128);
141to_sql_self!(isize);
142to_sql_self!(u8);
143to_sql_self!(u16);
144to_sql_self!(u32);
145to_sql_self!(f32);
146to_sql_self!(f64);
147to_sql_self!(u64);
148to_sql_self!(usize);
149
150#[cfg(feature = "uuid")]
151to_sql_self!(uuid::Uuid);
152
153impl<T: ?Sized> ToSql for &'_ T
154where
155    T: ToSql,
156{
157    #[inline]
158    fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
159        (*self).to_sql()
160    }
161}
162
163impl ToSql for String {
164    #[inline]
165    fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
166        Ok(ToSqlOutput::from(self.as_str()))
167    }
168}
169
170impl ToSql for str {
171    #[inline]
172    fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
173        Ok(ToSqlOutput::from(self))
174    }
175}
176
177impl ToSql for Vec<u8> {
178    #[inline]
179    fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
180        Ok(ToSqlOutput::from(self.as_slice()))
181    }
182}
183
184impl ToSql for [u8] {
185    #[inline]
186    fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
187        Ok(ToSqlOutput::from(self))
188    }
189}
190
191impl ToSql for Value {
192    #[inline]
193    fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
194        Ok(ToSqlOutput::Borrowed(value_ref_from_value(
195            self,
196            binding_unsupported_value,
197        )?))
198    }
199}
200
201impl<T: ToSql> ToSql for Option<T> {
202    #[inline]
203    fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
204        match *self {
205            None => Ok(ToSqlOutput::from(Null)),
206            Some(ref t) => t.to_sql(),
207        }
208    }
209}
210
211impl ToSql for std::time::Duration {
212    fn to_sql(&self) -> crate::Result<ToSqlOutput<'_>> {
213        Ok(ToSqlOutput::Owned(Value::Timestamp(
214            TimeUnit::Microsecond,
215            self.as_micros() as i64,
216        )))
217    }
218}
219
220#[cfg(test)]
221mod test {
222    use super::{ToSql, ToSqlOutput};
223
224    fn is_to_sql<T: ToSql>() {}
225
226    #[test]
227    fn test_integral_types() {
228        is_to_sql::<i8>();
229        is_to_sql::<i16>();
230        is_to_sql::<i32>();
231        is_to_sql::<i64>();
232        is_to_sql::<i128>();
233        is_to_sql::<isize>();
234        is_to_sql::<u8>();
235        is_to_sql::<u16>();
236        is_to_sql::<u32>();
237        is_to_sql::<u64>();
238        is_to_sql::<u128>();
239        is_to_sql::<usize>();
240    }
241
242    #[test]
243    fn test_value_to_sql_rejects_unsupported_variants() {
244        use crate::{
245            Error,
246            types::{OrderedMap, Value},
247        };
248
249        let cases: &[(&str, Value)] = &[
250            ("List", Value::List(vec![Value::Int(1)])),
251            ("Array", Value::Array(vec![Value::Int(1)])),
252            (
253                "Struct",
254                Value::Struct(OrderedMap::from(vec![("k".to_string(), Value::Int(1))])),
255            ),
256            (
257                "Map",
258                Value::Map(OrderedMap::from(vec![(Value::Int(1), Value::Int(2))])),
259            ),
260            ("Union", Value::Union(Box::new(Value::Int(1)))),
261            ("Enum", Value::Enum("variant".to_string())),
262        ];
263
264        for (variant, value) in cases {
265            match value.to_sql() {
266                Err(Error::ToSqlConversionFailure(e)) => {
267                    let msg = e.to_string();
268                    assert!(
269                        msg.contains(&format!("binding {variant} parameters is not yet supported")),
270                        "{variant}: unexpected message {msg}"
271                    );
272                }
273                Err(other) => panic!("{variant}: expected ToSqlConversionFailure, got {other:?}"),
274                Ok(_) => panic!("{variant}: expected error, got Ok"),
275            }
276            match ToSqlOutput::Owned(value.clone()).to_sql() {
277                Err(Error::ToSqlConversionFailure(e)) => {
278                    let msg = e.to_string();
279                    assert!(
280                        msg.contains(&format!("binding {variant} parameters is not yet supported")),
281                        "{variant}: unexpected message {msg}"
282                    );
283                }
284                Err(other) => panic!("{variant}: expected ToSqlConversionFailure, got {other:?}"),
285                Ok(_) => panic!("{variant}: expected error, got Ok"),
286            }
287        }
288    }
289
290    #[test]
291    fn test_cow_str() {
292        use std::borrow::Cow;
293        let s = "str";
294        let cow: Cow<'_, str> = Cow::Borrowed(s);
295        let r = cow.to_sql();
296        assert!(r.is_ok());
297        let cow: Cow<'_, str> = Cow::Owned(String::from(s));
298        let r = cow.to_sql();
299        assert!(r.is_ok());
300        // Ensure this compiles.
301        let _p: &[&dyn ToSql] = crate::params![cow];
302    }
303
304    #[test]
305    fn test_box_dyn() {
306        let s: Box<dyn ToSql> = Box::new("Hello world!");
307        let _s: &[&dyn ToSql] = crate::params![s];
308        let r = ToSql::to_sql(&s);
309
310        assert!(r.is_ok());
311    }
312
313    #[test]
314    fn test_box_deref() {
315        let s: Box<str> = "Hello world!".into();
316        let _s: &[&dyn ToSql] = crate::params![s];
317        let r = s.to_sql();
318
319        assert!(r.is_ok());
320    }
321
322    #[test]
323    fn test_box_direct() {
324        let s: Box<str> = "Hello world!".into();
325        let _s: &[&dyn ToSql] = crate::params![s];
326        let r = ToSql::to_sql(&s);
327
328        assert!(r.is_ok());
329    }
330
331    #[test]
332    fn test_cells() {
333        use std::{rc::Rc, sync::Arc};
334
335        let source_str: Box<str> = "Hello world!".into();
336
337        let s: Rc<Box<str>> = Rc::new(source_str.clone());
338        let _s: &[&dyn ToSql] = crate::params![s];
339        let r = s.to_sql();
340        assert!(r.is_ok());
341
342        let s: Arc<Box<str>> = Arc::new(source_str.clone());
343        let _s: &[&dyn ToSql] = crate::params![s];
344        let r = s.to_sql();
345        assert!(r.is_ok());
346
347        let s: Arc<str> = Arc::from(&*source_str);
348        let _s: &[&dyn ToSql] = crate::params![s];
349        let r = s.to_sql();
350        assert!(r.is_ok());
351
352        let s: Arc<dyn ToSql> = Arc::new(source_str.clone());
353        let _s: &[&dyn ToSql] = crate::params![s];
354        let r = s.to_sql();
355        assert!(r.is_ok());
356
357        let s: Rc<str> = Rc::from(&*source_str);
358        let _s: &[&dyn ToSql] = crate::params![s];
359        let r = s.to_sql();
360        assert!(r.is_ok());
361
362        let s: Rc<dyn ToSql> = Rc::new(source_str);
363        let _s: &[&dyn ToSql] = crate::params![s];
364        let r = s.to_sql();
365        assert!(r.is_ok());
366    }
367
368    // Use gen_random_uuid() to generate uuid
369    #[test]
370    fn test_uuid_gen() -> crate::Result<()> {
371        use crate::Connection;
372
373        let db = Connection::open_in_memory()?;
374        db.execute_batch("CREATE TABLE foo (id uuid NOT NULL);")?;
375
376        db.execute("INSERT INTO foo (id) VALUES (gen_random_uuid())", [])?;
377
378        let found_id: String = db.prepare("SELECT id FROM foo")?.query_one([], |r| r.get(0))?;
379        assert_eq!(found_id.len(), 36);
380        Ok(())
381    }
382
383    #[cfg(feature = "uuid")]
384    #[test]
385    fn test_uuid_blob_type() -> crate::Result<()> {
386        use crate::{Connection, params};
387        use uuid::Uuid;
388
389        let db = Connection::open_in_memory()?;
390        db.execute_batch("CREATE TABLE foo (id BLOB CONSTRAINT uuidchk CHECK (octet_length(id) = 16), label TEXT);")?;
391
392        let id = Uuid::new_v4();
393        let id_vec = id.as_bytes().to_vec();
394        db.execute("INSERT INTO foo (id, label) VALUES (?, ?)", params![id_vec, "target"])?;
395
396        let (found_id, found_label): (Uuid, String) = db
397            .prepare("SELECT id, label FROM foo WHERE id = ?")?
398            .query_one(params![id_vec], |r| Ok((r.get_unwrap(0), r.get_unwrap(1))))?;
399        assert_eq!(found_id, id);
400        assert_eq!(found_label, "target");
401        Ok(())
402    }
403
404    #[cfg(feature = "uuid")]
405    #[test]
406    fn test_uuid_type() -> crate::Result<()> {
407        use crate::{Connection, params};
408        use uuid::Uuid;
409
410        let db = Connection::open_in_memory()?;
411        db.execute_batch("CREATE TABLE foo (id uuid, label TEXT);")?;
412
413        let id = Uuid::new_v4();
414        db.execute("INSERT INTO foo (id, label) VALUES (?, ?)", params![id, "target"])?;
415
416        let (found_id, found_label): (Uuid, String) = db
417            .prepare("SELECT id, label FROM foo WHERE id = ?")?
418            .query_one(params![id], |r| Ok((r.get_unwrap(0), r.get_unwrap(1))))?;
419        assert_eq!(found_id, id);
420        assert_eq!(found_label, "target");
421        Ok(())
422    }
423}