| use super::{Null, Value, ValueRef}; |
| #[cfg(feature = "array")] |
| use crate::vtab::array::Array; |
| use crate::{Error, Result}; |
| use std::borrow::Cow; |
| use std::convert::TryFrom; |
| |
| /// `ToSqlOutput` represents the possible output types for implementers of the |
| /// [`ToSql`] trait. |
| #[derive(Clone, Debug, PartialEq)] |
| #[non_exhaustive] |
| pub enum ToSqlOutput<'a> { |
| /// A borrowed SQLite-representable value. |
| Borrowed(ValueRef<'a>), |
| |
| /// An owned SQLite-representable value. |
| Owned(Value), |
| |
| /// A BLOB of the given length that is filled with |
| /// zeroes. |
| #[cfg(feature = "blob")] |
| #[cfg_attr(docsrs, doc(cfg(feature = "blob")))] |
| ZeroBlob(i32), |
| |
| /// `feature = "array"` |
| #[cfg(feature = "array")] |
| #[cfg_attr(docsrs, doc(cfg(feature = "array")))] |
| Array(Array), |
| } |
| |
| // Generically allow any type that can be converted into a ValueRef |
| // to be converted into a ToSqlOutput as well. |
| impl<'a, T: ?Sized> From<&'a T> for ToSqlOutput<'a> |
| where |
| &'a T: Into<ValueRef<'a>>, |
| { |
| #[inline] |
| fn from(t: &'a T) -> Self { |
| ToSqlOutput::Borrowed(t.into()) |
| } |
| } |
| |
| // We cannot also generically allow any type that can be converted |
| // into a Value to be converted into a ToSqlOutput because of |
| // coherence rules (https://github.com/rust-lang/rust/pull/46192), |
| // so we'll manually implement it for all the types we know can |
| // be converted into Values. |
| macro_rules! from_value( |
| ($t:ty) => ( |
| impl From<$t> for ToSqlOutput<'_> { |
| #[inline] |
| fn from(t: $t) -> Self { ToSqlOutput::Owned(t.into())} |
| } |
| ) |
| ); |
| from_value!(String); |
| from_value!(Null); |
| from_value!(bool); |
| from_value!(i8); |
| from_value!(i16); |
| from_value!(i32); |
| from_value!(i64); |
| from_value!(isize); |
| from_value!(u8); |
| from_value!(u16); |
| from_value!(u32); |
| from_value!(f32); |
| from_value!(f64); |
| from_value!(Vec<u8>); |
| |
| // It would be nice if we could avoid the heap allocation (of the `Vec`) that |
| // `i128` needs in `Into<Value>`, but it's probably fine for the moment, and not |
| // worth adding another case to Value. |
| #[cfg(feature = "i128_blob")] |
| #[cfg_attr(docsrs, doc(cfg(feature = "i128_blob")))] |
| from_value!(i128); |
| |
| #[cfg(feature = "uuid")] |
| #[cfg_attr(docsrs, doc(cfg(feature = "uuid")))] |
| from_value!(uuid::Uuid); |
| |
| impl ToSql for ToSqlOutput<'_> { |
| #[inline] |
| fn to_sql(&self) -> Result<ToSqlOutput<'_>> { |
| Ok(match *self { |
| ToSqlOutput::Borrowed(v) => ToSqlOutput::Borrowed(v), |
| ToSqlOutput::Owned(ref v) => ToSqlOutput::Borrowed(ValueRef::from(v)), |
| |
| #[cfg(feature = "blob")] |
| ToSqlOutput::ZeroBlob(i) => ToSqlOutput::ZeroBlob(i), |
| #[cfg(feature = "array")] |
| ToSqlOutput::Array(ref a) => ToSqlOutput::Array(a.clone()), |
| }) |
| } |
| } |
| |
| /// A trait for types that can be converted into SQLite values. Returns |
| /// [`Error::ToSqlConversionFailure`] if the conversion fails. |
| pub trait ToSql { |
| /// Converts Rust value to SQLite value |
| fn to_sql(&self) -> Result<ToSqlOutput<'_>>; |
| } |
| |
| impl<T: ToSql + ToOwned + ?Sized> ToSql for Cow<'_, T> { |
| #[inline] |
| fn to_sql(&self) -> Result<ToSqlOutput<'_>> { |
| self.as_ref().to_sql() |
| } |
| } |
| |
| impl<T: ToSql + ?Sized> ToSql for Box<T> { |
| #[inline] |
| fn to_sql(&self) -> Result<ToSqlOutput<'_>> { |
| self.as_ref().to_sql() |
| } |
| } |
| |
| impl<T: ToSql + ?Sized> ToSql for std::rc::Rc<T> { |
| #[inline] |
| fn to_sql(&self) -> Result<ToSqlOutput<'_>> { |
| self.as_ref().to_sql() |
| } |
| } |
| |
| impl<T: ToSql + ?Sized> ToSql for std::sync::Arc<T> { |
| #[inline] |
| fn to_sql(&self) -> Result<ToSqlOutput<'_>> { |
| self.as_ref().to_sql() |
| } |
| } |
| |
| // We should be able to use a generic impl like this: |
| // |
| // impl<T: Copy> ToSql for T where T: Into<Value> { |
| // fn to_sql(&self) -> Result<ToSqlOutput> { |
| // Ok(ToSqlOutput::from((*self).into())) |
| // } |
| // } |
| // |
| // instead of the following macro, but this runs afoul of |
| // https://github.com/rust-lang/rust/issues/30191 and reports conflicting |
| // implementations even when there aren't any. |
| |
| macro_rules! to_sql_self( |
| ($t:ty) => ( |
| impl ToSql for $t { |
| #[inline] |
| fn to_sql(&self) -> Result<ToSqlOutput<'_>> { |
| Ok(ToSqlOutput::from(*self)) |
| } |
| } |
| ) |
| ); |
| |
| to_sql_self!(Null); |
| to_sql_self!(bool); |
| to_sql_self!(i8); |
| to_sql_self!(i16); |
| to_sql_self!(i32); |
| to_sql_self!(i64); |
| to_sql_self!(isize); |
| to_sql_self!(u8); |
| to_sql_self!(u16); |
| to_sql_self!(u32); |
| to_sql_self!(f32); |
| to_sql_self!(f64); |
| |
| #[cfg(feature = "i128_blob")] |
| #[cfg_attr(docsrs, doc(cfg(feature = "i128_blob")))] |
| to_sql_self!(i128); |
| |
| #[cfg(feature = "uuid")] |
| #[cfg_attr(docsrs, doc(cfg(feature = "uuid")))] |
| to_sql_self!(uuid::Uuid); |
| |
| macro_rules! to_sql_self_fallible( |
| ($t:ty) => ( |
| impl ToSql for $t { |
| #[inline] |
| fn to_sql(&self) -> Result<ToSqlOutput<'_>> { |
| Ok(ToSqlOutput::Owned(Value::Integer( |
| i64::try_from(*self).map_err( |
| // TODO: Include the values in the error message. |
| |err| Error::ToSqlConversionFailure(err.into()) |
| )? |
| ))) |
| } |
| } |
| ) |
| ); |
| |
| // Special implementations for usize and u64 because these conversions can fail. |
| to_sql_self_fallible!(u64); |
| to_sql_self_fallible!(usize); |
| |
| impl<T: ?Sized> ToSql for &'_ T |
| where |
| T: ToSql, |
| { |
| #[inline] |
| fn to_sql(&self) -> Result<ToSqlOutput<'_>> { |
| (*self).to_sql() |
| } |
| } |
| |
| impl ToSql for String { |
| #[inline] |
| fn to_sql(&self) -> Result<ToSqlOutput<'_>> { |
| Ok(ToSqlOutput::from(self.as_str())) |
| } |
| } |
| |
| impl ToSql for str { |
| #[inline] |
| fn to_sql(&self) -> Result<ToSqlOutput<'_>> { |
| Ok(ToSqlOutput::from(self)) |
| } |
| } |
| |
| impl ToSql for Vec<u8> { |
| #[inline] |
| fn to_sql(&self) -> Result<ToSqlOutput<'_>> { |
| Ok(ToSqlOutput::from(self.as_slice())) |
| } |
| } |
| |
| impl<const N: usize> ToSql for [u8; N] { |
| #[inline] |
| fn to_sql(&self) -> Result<ToSqlOutput<'_>> { |
| Ok(ToSqlOutput::from(&self[..])) |
| } |
| } |
| |
| impl ToSql for [u8] { |
| #[inline] |
| fn to_sql(&self) -> Result<ToSqlOutput<'_>> { |
| Ok(ToSqlOutput::from(self)) |
| } |
| } |
| |
| impl ToSql for Value { |
| #[inline] |
| fn to_sql(&self) -> Result<ToSqlOutput<'_>> { |
| Ok(ToSqlOutput::from(self)) |
| } |
| } |
| |
| impl<T: ToSql> ToSql for Option<T> { |
| #[inline] |
| fn to_sql(&self) -> Result<ToSqlOutput<'_>> { |
| match *self { |
| None => Ok(ToSqlOutput::from(Null)), |
| Some(ref t) => t.to_sql(), |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::ToSql; |
| |
| fn is_to_sql<T: ToSql>() {} |
| |
| #[test] |
| fn test_integral_types() { |
| is_to_sql::<i8>(); |
| is_to_sql::<i16>(); |
| is_to_sql::<i32>(); |
| is_to_sql::<i64>(); |
| is_to_sql::<u8>(); |
| is_to_sql::<u16>(); |
| is_to_sql::<u32>(); |
| } |
| |
| #[test] |
| fn test_u8_array() { |
| let a: [u8; 99] = [0u8; 99]; |
| let _a: &[&dyn ToSql] = crate::params![a]; |
| let r = ToSql::to_sql(&a); |
| |
| r.unwrap(); |
| } |
| |
| #[test] |
| fn test_cow_str() { |
| use std::borrow::Cow; |
| let s = "str"; |
| let cow: Cow<str> = Cow::Borrowed(s); |
| let r = cow.to_sql(); |
| r.unwrap(); |
| let cow: Cow<str> = Cow::Owned::<str>(String::from(s)); |
| let r = cow.to_sql(); |
| r.unwrap(); |
| // Ensure this compiles. |
| let _p: &[&dyn ToSql] = crate::params![cow]; |
| } |
| |
| #[test] |
| fn test_box_dyn() { |
| let s: Box<dyn ToSql> = Box::new("Hello world!"); |
| let _s: &[&dyn ToSql] = crate::params![s]; |
| let r = ToSql::to_sql(&s); |
| |
| r.unwrap(); |
| } |
| |
| #[test] |
| fn test_box_deref() { |
| let s: Box<str> = "Hello world!".into(); |
| let _s: &[&dyn ToSql] = crate::params![s]; |
| let r = s.to_sql(); |
| |
| r.unwrap(); |
| } |
| |
| #[test] |
| fn test_box_direct() { |
| let s: Box<str> = "Hello world!".into(); |
| let _s: &[&dyn ToSql] = crate::params![s]; |
| let r = ToSql::to_sql(&s); |
| |
| r.unwrap(); |
| } |
| |
| #[test] |
| fn test_cells() { |
| use std::{rc::Rc, sync::Arc}; |
| |
| let source_str: Box<str> = "Hello world!".into(); |
| |
| let s: Rc<Box<str>> = Rc::new(source_str.clone()); |
| let _s: &[&dyn ToSql] = crate::params![s]; |
| let r = s.to_sql(); |
| r.unwrap(); |
| |
| let s: Arc<Box<str>> = Arc::new(source_str.clone()); |
| let _s: &[&dyn ToSql] = crate::params![s]; |
| let r = s.to_sql(); |
| r.unwrap(); |
| |
| let s: Arc<str> = Arc::from(&*source_str); |
| let _s: &[&dyn ToSql] = crate::params![s]; |
| let r = s.to_sql(); |
| r.unwrap(); |
| |
| let s: Arc<dyn ToSql> = Arc::new(source_str.clone()); |
| let _s: &[&dyn ToSql] = crate::params![s]; |
| let r = s.to_sql(); |
| r.unwrap(); |
| |
| let s: Rc<str> = Rc::from(&*source_str); |
| let _s: &[&dyn ToSql] = crate::params![s]; |
| let r = s.to_sql(); |
| r.unwrap(); |
| |
| let s: Rc<dyn ToSql> = Rc::new(source_str); |
| let _s: &[&dyn ToSql] = crate::params![s]; |
| let r = s.to_sql(); |
| r.unwrap(); |
| } |
| |
| #[cfg(feature = "i128_blob")] |
| #[test] |
| fn test_i128() -> crate::Result<()> { |
| use crate::Connection; |
| let db = Connection::open_in_memory()?; |
| db.execute_batch("CREATE TABLE foo (i128 BLOB, desc TEXT)")?; |
| db.execute( |
| " |
| INSERT INTO foo(i128, desc) VALUES |
| (?1, 'zero'), |
| (?2, 'neg one'), (?3, 'neg two'), |
| (?4, 'pos one'), (?5, 'pos two'), |
| (?6, 'min'), (?7, 'max')", |
| [0i128, -1i128, -2i128, 1i128, 2i128, i128::MIN, i128::MAX], |
| )?; |
| |
| let mut stmt = db.prepare("SELECT i128, desc FROM foo ORDER BY i128 ASC")?; |
| |
| let res = stmt |
| .query_map([], |row| { |
| Ok((row.get::<_, i128>(0)?, row.get::<_, String>(1)?)) |
| })? |
| .collect::<Result<Vec<_>, _>>()?; |
| |
| assert_eq!( |
| res, |
| &[ |
| (i128::MIN, "min".to_owned()), |
| (-2, "neg two".to_owned()), |
| (-1, "neg one".to_owned()), |
| (0, "zero".to_owned()), |
| (1, "pos one".to_owned()), |
| (2, "pos two".to_owned()), |
| (i128::MAX, "max".to_owned()), |
| ] |
| ); |
| Ok(()) |
| } |
| |
| #[cfg(feature = "uuid")] |
| #[test] |
| fn test_uuid() -> crate::Result<()> { |
| use crate::{params, Connection}; |
| use uuid::Uuid; |
| |
| let db = Connection::open_in_memory()?; |
| db.execute_batch("CREATE TABLE foo (id BLOB CHECK(length(id) = 16), label TEXT);")?; |
| |
| let id = Uuid::new_v4(); |
| |
| db.execute( |
| "INSERT INTO foo (id, label) VALUES (?1, ?2)", |
| params![id, "target"], |
| )?; |
| |
| let mut stmt = db.prepare("SELECT id, label FROM foo WHERE id = ?1")?; |
| |
| let mut rows = stmt.query(params![id])?; |
| let row = rows.next()?.unwrap(); |
| |
| let found_id: Uuid = row.get_unwrap(0); |
| let found_label: String = row.get_unwrap(1); |
| |
| assert_eq!(found_id, id); |
| assert_eq!(found_label, "target"); |
| Ok(()) |
| } |
| } |