duckdb/types/
to_sql.rs

1use super::{Null, TimeUnit, Value, ValueRef};
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 SQLite-representable value.
11    Borrowed(ValueRef<'a>),
12
13    /// An owned SQLite-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!(isize);
51from_value!(u8);
52from_value!(u16);
53from_value!(u32);
54from_value!(u64);
55from_value!(usize);
56from_value!(f32);
57from_value!(f64);
58from_value!(Vec<u8>);
59
60#[cfg(feature = "uuid")]
61from_value!(uuid::Uuid);
62
63impl ToSql for ToSqlOutput<'_> {
64    #[inline]
65    fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
66        Ok(match *self {
67            ToSqlOutput::Borrowed(v) => ToSqlOutput::Borrowed(v),
68            ToSqlOutput::Owned(ref v) => ToSqlOutput::Borrowed(ValueRef::from(v)),
69        })
70    }
71}
72
73/// A trait for types that can be converted into DuckDB values. Returns
74/// [`Error::ToSqlConversionFailure`] if the conversion fails.
75pub trait ToSql {
76    /// Converts Rust value to DuckDB value
77    fn to_sql(&self) -> Result<ToSqlOutput<'_>>;
78}
79
80impl<T: ToSql + ToOwned + ?Sized> ToSql for Cow<'_, T> {
81    #[inline]
82    fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
83        self.as_ref().to_sql()
84    }
85}
86
87impl<T: ToSql + ?Sized> ToSql for Box<T> {
88    #[inline]
89    fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
90        self.as_ref().to_sql()
91    }
92}
93
94impl<T: ToSql + ?Sized> ToSql for std::rc::Rc<T> {
95    #[inline]
96    fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
97        self.as_ref().to_sql()
98    }
99}
100
101impl<T: ToSql + ?Sized> ToSql for std::sync::Arc<T> {
102    #[inline]
103    fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
104        self.as_ref().to_sql()
105    }
106}
107
108// We should be able to use a generic impl like this:
109//
110// impl<T: Copy> ToSql for T where T: Into<Value> {
111//     fn to_sql(&self) -> Result<ToSqlOutput> {
112//         Ok(ToSqlOutput::from((*self).into()))
113//     }
114// }
115//
116// instead of the following macro, but this runs afoul of
117// https://github.com/rust-lang/rust/issues/30191 and reports conflicting
118// implementations even when there aren't any.
119
120macro_rules! to_sql_self(
121    ($t:ty) => (
122        impl ToSql for $t {
123            #[inline]
124            fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
125                Ok(ToSqlOutput::from(*self))
126            }
127        }
128    )
129);
130
131to_sql_self!(Null);
132to_sql_self!(bool);
133to_sql_self!(i8);
134to_sql_self!(i16);
135to_sql_self!(i32);
136to_sql_self!(i64);
137to_sql_self!(i128);
138to_sql_self!(isize);
139to_sql_self!(u8);
140to_sql_self!(u16);
141to_sql_self!(u32);
142to_sql_self!(f32);
143to_sql_self!(f64);
144to_sql_self!(u64);
145to_sql_self!(usize);
146
147#[cfg(feature = "uuid")]
148to_sql_self!(uuid::Uuid);
149
150impl<T: ?Sized> ToSql for &'_ T
151where
152    T: ToSql,
153{
154    #[inline]
155    fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
156        (*self).to_sql()
157    }
158}
159
160impl ToSql for String {
161    #[inline]
162    fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
163        Ok(ToSqlOutput::from(self.as_str()))
164    }
165}
166
167impl ToSql for str {
168    #[inline]
169    fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
170        Ok(ToSqlOutput::from(self))
171    }
172}
173
174impl ToSql for Vec<u8> {
175    #[inline]
176    fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
177        Ok(ToSqlOutput::from(self.as_slice()))
178    }
179}
180
181impl ToSql for [u8] {
182    #[inline]
183    fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
184        Ok(ToSqlOutput::from(self))
185    }
186}
187
188impl ToSql for Value {
189    #[inline]
190    fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
191        Ok(ToSqlOutput::from(self))
192    }
193}
194
195impl<T: ToSql> ToSql for Option<T> {
196    #[inline]
197    fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
198        match *self {
199            None => Ok(ToSqlOutput::from(Null)),
200            Some(ref t) => t.to_sql(),
201        }
202    }
203}
204
205impl ToSql for std::time::Duration {
206    fn to_sql(&self) -> crate::Result<ToSqlOutput<'_>> {
207        Ok(ToSqlOutput::Owned(Value::Timestamp(
208            TimeUnit::Microsecond,
209            self.as_micros() as i64,
210        )))
211    }
212}
213
214#[cfg(test)]
215mod test {
216    use super::ToSql;
217
218    fn is_to_sql<T: ToSql>() {}
219
220    #[test]
221    fn test_integral_types() {
222        is_to_sql::<i8>();
223        is_to_sql::<i16>();
224        is_to_sql::<i32>();
225        is_to_sql::<i64>();
226        is_to_sql::<u8>();
227        is_to_sql::<u16>();
228        is_to_sql::<u32>();
229    }
230
231    #[test]
232    fn test_cow_str() {
233        use std::borrow::Cow;
234        let s = "str";
235        let cow: Cow<'_, str> = Cow::Borrowed(s);
236        let r = cow.to_sql();
237        assert!(r.is_ok());
238        let cow: Cow<'_, str> = Cow::Owned(String::from(s));
239        let r = cow.to_sql();
240        assert!(r.is_ok());
241        // Ensure this compiles.
242        let _p: &[&dyn ToSql] = crate::params![cow];
243    }
244
245    #[test]
246    fn test_box_dyn() {
247        let s: Box<dyn ToSql> = Box::new("Hello world!");
248        let _s: &[&dyn ToSql] = crate::params![s];
249        let r = ToSql::to_sql(&s);
250
251        assert!(r.is_ok());
252    }
253
254    #[test]
255    fn test_box_deref() {
256        let s: Box<str> = "Hello world!".into();
257        let _s: &[&dyn ToSql] = crate::params![s];
258        let r = s.to_sql();
259
260        assert!(r.is_ok());
261    }
262
263    #[test]
264    fn test_box_direct() {
265        let s: Box<str> = "Hello world!".into();
266        let _s: &[&dyn ToSql] = crate::params![s];
267        let r = ToSql::to_sql(&s);
268
269        assert!(r.is_ok());
270    }
271
272    #[test]
273    fn test_cells() {
274        use std::{rc::Rc, sync::Arc};
275
276        let source_str: Box<str> = "Hello world!".into();
277
278        let s: Rc<Box<str>> = Rc::new(source_str.clone());
279        let _s: &[&dyn ToSql] = crate::params![s];
280        let r = s.to_sql();
281        assert!(r.is_ok());
282
283        let s: Arc<Box<str>> = Arc::new(source_str.clone());
284        let _s: &[&dyn ToSql] = crate::params![s];
285        let r = s.to_sql();
286        assert!(r.is_ok());
287
288        let s: Arc<str> = Arc::from(&*source_str);
289        let _s: &[&dyn ToSql] = crate::params![s];
290        let r = s.to_sql();
291        assert!(r.is_ok());
292
293        let s: Arc<dyn ToSql> = Arc::new(source_str.clone());
294        let _s: &[&dyn ToSql] = crate::params![s];
295        let r = s.to_sql();
296        assert!(r.is_ok());
297
298        let s: Rc<str> = Rc::from(&*source_str);
299        let _s: &[&dyn ToSql] = crate::params![s];
300        let r = s.to_sql();
301        assert!(r.is_ok());
302
303        let s: Rc<dyn ToSql> = Rc::new(source_str);
304        let _s: &[&dyn ToSql] = crate::params![s];
305        let r = s.to_sql();
306        assert!(r.is_ok());
307    }
308
309    // Use gen_random_uuid() to generate uuid
310    #[test]
311    fn test_uuid_gen() -> crate::Result<()> {
312        use crate::Connection;
313
314        let db = Connection::open_in_memory()?;
315        db.execute_batch("CREATE TABLE foo (id uuid NOT NULL);")?;
316
317        db.execute("INSERT INTO foo (id) VALUES (gen_random_uuid())", [])?;
318
319        let found_id: String = db.prepare("SELECT id FROM foo")?.query_one([], |r| r.get(0))?;
320        assert_eq!(found_id.len(), 36);
321        Ok(())
322    }
323
324    #[cfg(feature = "uuid")]
325    #[test]
326    fn test_uuid_blob_type() -> crate::Result<()> {
327        use crate::{params, Connection};
328        use uuid::Uuid;
329
330        let db = Connection::open_in_memory()?;
331        db.execute_batch("CREATE TABLE foo (id BLOB CONSTRAINT uuidchk CHECK (octet_length(id) = 16), label TEXT);")?;
332
333        let id = Uuid::new_v4();
334        let id_vec = id.as_bytes().to_vec();
335        db.execute("INSERT INTO foo (id, label) VALUES (?, ?)", params![id_vec, "target"])?;
336
337        let (found_id, found_label): (Uuid, String) = db
338            .prepare("SELECT id, label FROM foo WHERE id = ?")?
339            .query_one(params![id_vec], |r| Ok((r.get_unwrap(0), r.get_unwrap(1))))?;
340        assert_eq!(found_id, id);
341        assert_eq!(found_label, "target");
342        Ok(())
343    }
344
345    #[cfg(feature = "uuid")]
346    #[test]
347    fn test_uuid_type() -> crate::Result<()> {
348        use crate::{params, Connection};
349        use uuid::Uuid;
350
351        let db = Connection::open_in_memory()?;
352        db.execute_batch("CREATE TABLE foo (id uuid, label TEXT);")?;
353
354        let id = Uuid::new_v4();
355        db.execute("INSERT INTO foo (id, label) VALUES (?, ?)", params![id, "target"])?;
356
357        let (found_id, found_label): (Uuid, String) = db
358            .prepare("SELECT id, label FROM foo WHERE id = ?")?
359            .query_one(params![id], |r| Ok((r.get_unwrap(0), r.get_unwrap(1))))?;
360        assert_eq!(found_id, id);
361        assert_eq!(found_label, "target");
362        Ok(())
363    }
364}