aboutsummaryrefslogtreecommitdiff
path: root/src/types/to_sql.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/types/to_sql.rs')
-rw-r--r--src/types/to_sql.rs376
1 files changed, 376 insertions, 0 deletions
diff --git a/src/types/to_sql.rs b/src/types/to_sql.rs
new file mode 100644
index 0000000..bec53f8
--- /dev/null
+++ b/src/types/to_sql.rs
@@ -0,0 +1,376 @@
+use super::{Null, Value, ValueRef};
+#[cfg(feature = "array")]
+use crate::vtab::array::Array;
+use crate::Result;
+use std::borrow::Cow;
+
+/// `ToSqlOutput` represents the possible output types for implementors 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),
+
+ /// `feature = "blob"` A BLOB of the given length that is filled with
+ /// zeroes.
+ #[cfg(feature = "blob")]
+ ZeroBlob(i32),
+
+ /// `feature = "array"`
+ #[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>>,
+{
+ 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<'_> {
+ 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!(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")]
+from_value!(i128);
+
+#[cfg(feature = "uuid")]
+from_value!(uuid::Uuid);
+
+impl ToSql for ToSqlOutput<'_> {
+ 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.
+pub trait ToSql {
+ fn to_sql(&self) -> Result<ToSqlOutput<'_>>;
+}
+
+impl<T: ToSql + ToOwned + ?Sized> ToSql for Cow<'_, T> {
+ fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
+ self.as_ref().to_sql()
+ }
+}
+
+impl<T: ToSql + ?Sized> ToSql for Box<T> {
+ fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
+ self.as_ref().to_sql()
+ }
+}
+
+impl<T: ToSql + ?Sized> ToSql for std::rc::Rc<T> {
+ fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
+ self.as_ref().to_sql()
+ }
+}
+
+impl<T: ToSql + ?Sized> ToSql for std::sync::Arc<T> {
+ 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 {
+ 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!(f64);
+
+#[cfg(feature = "i128_blob")]
+to_sql_self!(i128);
+
+#[cfg(feature = "uuid")]
+to_sql_self!(uuid::Uuid);
+
+impl<T: ?Sized> ToSql for &'_ T
+where
+ T: ToSql,
+{
+ fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
+ (*self).to_sql()
+ }
+}
+
+impl ToSql for String {
+ fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
+ Ok(ToSqlOutput::from(self.as_str()))
+ }
+}
+
+impl ToSql for str {
+ fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
+ Ok(ToSqlOutput::from(self))
+ }
+}
+
+impl ToSql for Vec<u8> {
+ fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
+ Ok(ToSqlOutput::from(self.as_slice()))
+ }
+}
+
+impl ToSql for [u8] {
+ fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
+ Ok(ToSqlOutput::from(self))
+ }
+}
+
+impl ToSql for Value {
+ fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
+ Ok(ToSqlOutput::from(self))
+ }
+}
+
+impl<T: ToSql> ToSql for Option<T> {
+ 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_cow_str() {
+ use std::borrow::Cow;
+ let s = "str";
+ let cow: Cow<str> = Cow::Borrowed(s);
+ let r = cow.to_sql();
+ assert!(r.is_ok());
+ let cow: Cow<str> = Cow::Owned::<str>(String::from(s));
+ let r = cow.to_sql();
+ assert!(r.is_ok());
+ // 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);
+
+ assert!(r.is_ok());
+ }
+
+ #[test]
+ fn test_box_deref() {
+ let s: Box<str> = "Hello world!".into();
+ let _s: &[&dyn ToSql] = crate::params![s];
+ let r = s.to_sql();
+
+ assert!(r.is_ok());
+ }
+
+ #[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);
+
+ assert!(r.is_ok());
+ }
+
+ #[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();
+ assert!(r.is_ok());
+
+ let s: Arc<Box<str>> = Arc::new(source_str.clone());
+ let _s: &[&dyn ToSql] = crate::params![s];
+ let r = s.to_sql();
+ assert!(r.is_ok());
+
+ let s: Arc<str> = Arc::from(&*source_str);
+ let _s: &[&dyn ToSql] = crate::params![s];
+ let r = s.to_sql();
+ assert!(r.is_ok());
+
+ let s: Arc<dyn ToSql> = Arc::new(source_str.clone());
+ let _s: &[&dyn ToSql] = crate::params![s];
+ let r = s.to_sql();
+ assert!(r.is_ok());
+
+ let s: Rc<str> = Rc::from(&*source_str);
+ let _s: &[&dyn ToSql] = crate::params![s];
+ let r = s.to_sql();
+ assert!(r.is_ok());
+
+ let s: Rc<dyn ToSql> = Rc::new(source_str);
+ let _s: &[&dyn ToSql] = crate::params![s];
+ let r = s.to_sql();
+ assert!(r.is_ok());
+ }
+
+ #[cfg(feature = "i128_blob")]
+ #[test]
+ fn test_i128() {
+ use crate::{Connection, NO_PARAMS};
+ use std::i128;
+ let db = Connection::open_in_memory().unwrap();
+ db.execute_batch("CREATE TABLE foo (i128 BLOB, desc TEXT)")
+ .unwrap();
+ db.execute(
+ "
+ INSERT INTO foo(i128, desc) VALUES
+ (?, 'zero'),
+ (?, 'neg one'), (?, 'neg two'),
+ (?, 'pos one'), (?, 'pos two'),
+ (?, 'min'), (?, 'max')",
+ &[0i128, -1i128, -2i128, 1i128, 2i128, i128::MIN, i128::MAX],
+ )
+ .unwrap();
+
+ let mut stmt = db
+ .prepare("SELECT i128, desc FROM foo ORDER BY i128 ASC")
+ .unwrap();
+
+ let res = stmt
+ .query_map(NO_PARAMS, |row| {
+ Ok((row.get::<_, i128>(0)?, row.get::<_, String>(1)?))
+ })
+ .unwrap()
+ .collect::<Result<Vec<_>, _>>()
+ .unwrap();
+
+ 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()),
+ ]
+ );
+ }
+
+ #[cfg(feature = "uuid")]
+ #[test]
+ fn test_uuid() {
+ use crate::{params, Connection};
+ use uuid::Uuid;
+
+ let db = Connection::open_in_memory().unwrap();
+ db.execute_batch("CREATE TABLE foo (id BLOB CHECK(length(id) = 16), label TEXT);")
+ .unwrap();
+
+ let id = Uuid::new_v4();
+
+ db.execute(
+ "INSERT INTO foo (id, label) VALUES (?, ?)",
+ params![id, "target"],
+ )
+ .unwrap();
+
+ let mut stmt = db
+ .prepare("SELECT id, label FROM foo WHERE id = ?")
+ .unwrap();
+
+ let mut rows = stmt.query(params![id]).unwrap();
+ let row = rows.next().unwrap().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");
+ }
+}