aboutsummaryrefslogtreecommitdiff
path: root/src/types
diff options
context:
space:
mode:
authorJoel Galenson <jgalenson@google.com>2020-07-28 13:34:02 -0700
committerJoel Galenson <jgalenson@google.com>2020-07-28 13:34:02 -0700
commit8b0df7ff3a4139db9f9ed71ea9d0bc82eca3eb30 (patch)
treeba5a264b0979e0fa15b1912cde8b9c04bd0e109f /src/types
parent26442fd4c4f3e1f469c323e695c4fcd972b150f5 (diff)
downloadrusqlite-8b0df7ff3a4139db9f9ed71ea9d0bc82eca3eb30.tar.gz
Import rusqlite-0.23.1
Change-Id: Id1ca7bcaad7820f463bfcce6945d80fc1d6918f5
Diffstat (limited to 'src/types')
-rw-r--r--src/types/chrono.rs280
-rw-r--r--src/types/from_sql.rs272
-rw-r--r--src/types/mod.rs363
-rw-r--r--src/types/serde_json.rs60
-rw-r--r--src/types/time.rs79
-rw-r--r--src/types/to_sql.rs376
-rw-r--r--src/types/url.rs81
-rw-r--r--src/types/value.rs121
-rw-r--r--src/types/value_ref.rs170
9 files changed, 1802 insertions, 0 deletions
diff --git a/src/types/chrono.rs b/src/types/chrono.rs
new file mode 100644
index 0000000..3cba1e9
--- /dev/null
+++ b/src/types/chrono.rs
@@ -0,0 +1,280 @@
+//! Convert most of the [Time Strings](http://sqlite.org/lang_datefunc.html) to chrono types.
+
+use std::borrow::Cow;
+
+use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
+
+use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
+use crate::Result;
+
+/// ISO 8601 calendar date without timezone => "YYYY-MM-DD"
+impl ToSql for NaiveDate {
+ fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
+ let date_str = self.format("%Y-%m-%d").to_string();
+ Ok(ToSqlOutput::from(date_str))
+ }
+}
+
+/// "YYYY-MM-DD" => ISO 8601 calendar date without timezone.
+impl FromSql for NaiveDate {
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ value
+ .as_str()
+ .and_then(|s| match NaiveDate::parse_from_str(s, "%Y-%m-%d") {
+ Ok(dt) => Ok(dt),
+ Err(err) => Err(FromSqlError::Other(Box::new(err))),
+ })
+ }
+}
+
+/// ISO 8601 time without timezone => "HH:MM:SS.SSS"
+impl ToSql for NaiveTime {
+ fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
+ let date_str = self.format("%H:%M:%S%.f").to_string();
+ Ok(ToSqlOutput::from(date_str))
+ }
+}
+
+/// "HH:MM"/"HH:MM:SS"/"HH:MM:SS.SSS" => ISO 8601 time without timezone.
+impl FromSql for NaiveTime {
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ value.as_str().and_then(|s| {
+ let fmt = match s.len() {
+ 5 => "%H:%M",
+ 8 => "%H:%M:%S",
+ _ => "%H:%M:%S%.f",
+ };
+ match NaiveTime::parse_from_str(s, fmt) {
+ Ok(dt) => Ok(dt),
+ Err(err) => Err(FromSqlError::Other(Box::new(err))),
+ }
+ })
+ }
+}
+
+/// ISO 8601 combined date and time without timezone =>
+/// "YYYY-MM-DDTHH:MM:SS.SSS"
+impl ToSql for NaiveDateTime {
+ fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
+ let date_str = self.format("%Y-%m-%dT%H:%M:%S%.f").to_string();
+ Ok(ToSqlOutput::from(date_str))
+ }
+}
+
+/// "YYYY-MM-DD HH:MM:SS"/"YYYY-MM-DD HH:MM:SS.SSS" => ISO 8601 combined date
+/// and time without timezone. ("YYYY-MM-DDTHH:MM:SS"/"YYYY-MM-DDTHH:MM:SS.SSS"
+/// also supported)
+impl FromSql for NaiveDateTime {
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ value.as_str().and_then(|s| {
+ let fmt = if s.len() >= 11 && s.as_bytes()[10] == b'T' {
+ "%Y-%m-%dT%H:%M:%S%.f"
+ } else {
+ "%Y-%m-%d %H:%M:%S%.f"
+ };
+
+ match NaiveDateTime::parse_from_str(s, fmt) {
+ Ok(dt) => Ok(dt),
+ Err(err) => Err(FromSqlError::Other(Box::new(err))),
+ }
+ })
+ }
+}
+
+/// Date and time with time zone => UTC RFC3339 timestamp
+/// ("YYYY-MM-DDTHH:MM:SS.SSS+00:00").
+impl<Tz: TimeZone> ToSql for DateTime<Tz> {
+ fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
+ Ok(ToSqlOutput::from(self.with_timezone(&Utc).to_rfc3339()))
+ }
+}
+
+/// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into `DateTime<Utc>`.
+impl FromSql for DateTime<Utc> {
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ {
+ // Try to parse value as rfc3339 first.
+ let s = value.as_str()?;
+
+ // If timestamp looks space-separated, make a copy and replace it with 'T'.
+ let s = if s.len() >= 11 && s.as_bytes()[10] == b' ' {
+ let mut s = s.to_string();
+ unsafe {
+ let sbytes = s.as_mut_vec();
+ sbytes[10] = b'T';
+ }
+ Cow::Owned(s)
+ } else {
+ Cow::Borrowed(s)
+ };
+
+ if let Ok(dt) = DateTime::parse_from_rfc3339(&s) {
+ return Ok(dt.with_timezone(&Utc));
+ }
+ }
+
+ // Couldn't parse as rfc3339 - fall back to NaiveDateTime.
+ NaiveDateTime::column_result(value).map(|dt| Utc.from_utc_datetime(&dt))
+ }
+}
+
+/// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into `DateTime<Local>`.
+impl FromSql for DateTime<Local> {
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ let utc_dt = DateTime::<Utc>::column_result(value)?;
+ Ok(utc_dt.with_timezone(&Local))
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::{Connection, Result, NO_PARAMS};
+ use chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
+
+ fn checked_memory_handle() -> Connection {
+ let db = Connection::open_in_memory().unwrap();
+ db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER, f FLOAT, b BLOB)")
+ .unwrap();
+ db
+ }
+
+ #[test]
+ fn test_naive_date() {
+ let db = checked_memory_handle();
+ let date = NaiveDate::from_ymd(2016, 2, 23);
+ db.execute("INSERT INTO foo (t) VALUES (?)", &[&date])
+ .unwrap();
+
+ let s: String = db
+ .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
+ .unwrap();
+ assert_eq!("2016-02-23", s);
+ let t: NaiveDate = db
+ .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
+ .unwrap();
+ assert_eq!(date, t);
+ }
+
+ #[test]
+ fn test_naive_time() {
+ let db = checked_memory_handle();
+ let time = NaiveTime::from_hms(23, 56, 4);
+ db.execute("INSERT INTO foo (t) VALUES (?)", &[&time])
+ .unwrap();
+
+ let s: String = db
+ .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
+ .unwrap();
+ assert_eq!("23:56:04", s);
+ let v: NaiveTime = db
+ .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
+ .unwrap();
+ assert_eq!(time, v);
+ }
+
+ #[test]
+ fn test_naive_date_time() {
+ let db = checked_memory_handle();
+ let date = NaiveDate::from_ymd(2016, 2, 23);
+ let time = NaiveTime::from_hms(23, 56, 4);
+ let dt = NaiveDateTime::new(date, time);
+
+ db.execute("INSERT INTO foo (t) VALUES (?)", &[&dt])
+ .unwrap();
+
+ let s: String = db
+ .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
+ .unwrap();
+ assert_eq!("2016-02-23T23:56:04", s);
+ let v: NaiveDateTime = db
+ .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
+ .unwrap();
+ assert_eq!(dt, v);
+
+ db.execute("UPDATE foo set b = datetime(t)", NO_PARAMS)
+ .unwrap(); // "YYYY-MM-DD HH:MM:SS"
+ let hms: NaiveDateTime = db
+ .query_row("SELECT b FROM foo", NO_PARAMS, |r| r.get(0))
+ .unwrap();
+ assert_eq!(dt, hms);
+ }
+
+ #[test]
+ fn test_date_time_utc() {
+ let db = checked_memory_handle();
+ let date = NaiveDate::from_ymd(2016, 2, 23);
+ let time = NaiveTime::from_hms_milli(23, 56, 4, 789);
+ let dt = NaiveDateTime::new(date, time);
+ let utc = Utc.from_utc_datetime(&dt);
+
+ db.execute("INSERT INTO foo (t) VALUES (?)", &[&utc])
+ .unwrap();
+
+ let s: String = db
+ .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
+ .unwrap();
+ assert_eq!("2016-02-23T23:56:04.789+00:00", s);
+
+ let v1: DateTime<Utc> = db
+ .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
+ .unwrap();
+ assert_eq!(utc, v1);
+
+ let v2: DateTime<Utc> = db
+ .query_row("SELECT '2016-02-23 23:56:04.789'", NO_PARAMS, |r| r.get(0))
+ .unwrap();
+ assert_eq!(utc, v2);
+
+ let v3: DateTime<Utc> = db
+ .query_row("SELECT '2016-02-23 23:56:04'", NO_PARAMS, |r| r.get(0))
+ .unwrap();
+ assert_eq!(utc - Duration::milliseconds(789), v3);
+
+ let v4: DateTime<Utc> = db
+ .query_row("SELECT '2016-02-23 23:56:04.789+00:00'", NO_PARAMS, |r| {
+ r.get(0)
+ })
+ .unwrap();
+ assert_eq!(utc, v4);
+ }
+
+ #[test]
+ fn test_date_time_local() {
+ let db = checked_memory_handle();
+ let date = NaiveDate::from_ymd(2016, 2, 23);
+ let time = NaiveTime::from_hms_milli(23, 56, 4, 789);
+ let dt = NaiveDateTime::new(date, time);
+ let local = Local.from_local_datetime(&dt).single().unwrap();
+
+ db.execute("INSERT INTO foo (t) VALUES (?)", &[&local])
+ .unwrap();
+
+ // Stored string should be in UTC
+ let s: String = db
+ .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
+ .unwrap();
+ assert!(s.ends_with("+00:00"));
+
+ let v: DateTime<Local> = db
+ .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
+ .unwrap();
+ assert_eq!(local, v);
+ }
+
+ #[test]
+ fn test_sqlite_functions() {
+ let db = checked_memory_handle();
+ let result: Result<NaiveTime> =
+ db.query_row("SELECT CURRENT_TIME", NO_PARAMS, |r| r.get(0));
+ assert!(result.is_ok());
+ let result: Result<NaiveDate> =
+ db.query_row("SELECT CURRENT_DATE", NO_PARAMS, |r| r.get(0));
+ assert!(result.is_ok());
+ let result: Result<NaiveDateTime> =
+ db.query_row("SELECT CURRENT_TIMESTAMP", NO_PARAMS, |r| r.get(0));
+ assert!(result.is_ok());
+ let result: Result<DateTime<Utc>> =
+ db.query_row("SELECT CURRENT_TIMESTAMP", NO_PARAMS, |r| r.get(0));
+ assert!(result.is_ok());
+ }
+}
diff --git a/src/types/from_sql.rs b/src/types/from_sql.rs
new file mode 100644
index 0000000..e8eadc5
--- /dev/null
+++ b/src/types/from_sql.rs
@@ -0,0 +1,272 @@
+use super::{Value, ValueRef};
+use std::error::Error;
+use std::fmt;
+
+/// Enum listing possible errors from `FromSql` trait.
+#[derive(Debug)]
+#[non_exhaustive]
+pub enum FromSqlError {
+ /// Error when an SQLite value is requested, but the type of the result
+ /// cannot be converted to the requested Rust type.
+ InvalidType,
+
+ /// Error when the i64 value returned by SQLite cannot be stored into the
+ /// requested type.
+ OutOfRange(i64),
+
+ /// `feature = "i128_blob"` Error returned when reading an `i128` from a
+ /// blob with a size other than 16. Only available when the `i128_blob`
+ /// feature is enabled.
+ #[cfg(feature = "i128_blob")]
+ InvalidI128Size(usize),
+
+ /// `feature = "uuid"` Error returned when reading a `uuid` from a blob with
+ /// a size other than 16. Only available when the `uuid` feature is enabled.
+ #[cfg(feature = "uuid")]
+ InvalidUuidSize(usize),
+
+ /// An error case available for implementors of the `FromSql` trait.
+ Other(Box<dyn Error + Send + Sync + 'static>),
+}
+
+impl PartialEq for FromSqlError {
+ fn eq(&self, other: &FromSqlError) -> bool {
+ match (self, other) {
+ (FromSqlError::InvalidType, FromSqlError::InvalidType) => true,
+ (FromSqlError::OutOfRange(n1), FromSqlError::OutOfRange(n2)) => n1 == n2,
+ #[cfg(feature = "i128_blob")]
+ (FromSqlError::InvalidI128Size(s1), FromSqlError::InvalidI128Size(s2)) => s1 == s2,
+ #[cfg(feature = "uuid")]
+ (FromSqlError::InvalidUuidSize(s1), FromSqlError::InvalidUuidSize(s2)) => s1 == s2,
+ (..) => false,
+ }
+ }
+}
+
+impl fmt::Display for FromSqlError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match *self {
+ FromSqlError::InvalidType => write!(f, "Invalid type"),
+ FromSqlError::OutOfRange(i) => write!(f, "Value {} out of range", i),
+ #[cfg(feature = "i128_blob")]
+ FromSqlError::InvalidI128Size(s) => {
+ write!(f, "Cannot read 128bit value out of {} byte blob", s)
+ }
+ #[cfg(feature = "uuid")]
+ FromSqlError::InvalidUuidSize(s) => {
+ write!(f, "Cannot read UUID value out of {} byte blob", s)
+ }
+ FromSqlError::Other(ref err) => err.fmt(f),
+ }
+ }
+}
+
+impl Error for FromSqlError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ if let FromSqlError::Other(ref err) = self {
+ Some(&**err)
+ } else {
+ None
+ }
+ }
+}
+
+/// Result type for implementors of the `FromSql` trait.
+pub type FromSqlResult<T> = Result<T, FromSqlError>;
+
+/// A trait for types that can be created from a SQLite value.
+///
+/// Note that `FromSql` and `ToSql` are defined for most integral types, but
+/// not `u64` or `usize`. This is intentional; SQLite returns integers as
+/// signed 64-bit values, which cannot fully represent the range of these
+/// types. Rusqlite would have to
+/// decide how to handle negative values: return an error or reinterpret as a
+/// very large postive numbers, neither of which
+/// is guaranteed to be correct for everyone. Callers can work around this by
+/// fetching values as i64 and then doing the interpretation themselves or by
+/// defining a newtype and implementing `FromSql`/`ToSql` for it.
+pub trait FromSql: Sized {
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self>;
+}
+
+impl FromSql for isize {
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ i64::column_result(value).and_then(|i| {
+ if i < isize::min_value() as i64 || i > isize::max_value() as i64 {
+ Err(FromSqlError::OutOfRange(i))
+ } else {
+ Ok(i as isize)
+ }
+ })
+ }
+}
+
+macro_rules! from_sql_integral(
+ ($t:ident) => (
+ impl FromSql for $t {
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ i64::column_result(value).and_then(|i| {
+ if i < i64::from($t::min_value()) || i > i64::from($t::max_value()) {
+ Err(FromSqlError::OutOfRange(i))
+ } else {
+ Ok(i as $t)
+ }
+ })
+ }
+ }
+ )
+);
+
+from_sql_integral!(i8);
+from_sql_integral!(i16);
+from_sql_integral!(i32);
+from_sql_integral!(u8);
+from_sql_integral!(u16);
+from_sql_integral!(u32);
+
+impl FromSql for i64 {
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ value.as_i64()
+ }
+}
+
+impl FromSql for f64 {
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ match value {
+ ValueRef::Integer(i) => Ok(i as f64),
+ ValueRef::Real(f) => Ok(f),
+ _ => Err(FromSqlError::InvalidType),
+ }
+ }
+}
+
+impl FromSql for bool {
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ i64::column_result(value).map(|i| match i {
+ 0 => false,
+ _ => true,
+ })
+ }
+}
+
+impl FromSql for String {
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ value.as_str().map(ToString::to_string)
+ }
+}
+
+impl FromSql for Box<str> {
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ value.as_str().map(Into::into)
+ }
+}
+
+impl FromSql for std::rc::Rc<str> {
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ value.as_str().map(Into::into)
+ }
+}
+
+impl FromSql for std::sync::Arc<str> {
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ value.as_str().map(Into::into)
+ }
+}
+
+impl FromSql for Vec<u8> {
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ value.as_blob().map(|b| b.to_vec())
+ }
+}
+
+#[cfg(feature = "i128_blob")]
+impl FromSql for i128 {
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ use byteorder::{BigEndian, ByteOrder};
+
+ value.as_blob().and_then(|bytes| {
+ if bytes.len() == 16 {
+ Ok(BigEndian::read_i128(bytes) ^ (1i128 << 127))
+ } else {
+ Err(FromSqlError::InvalidI128Size(bytes.len()))
+ }
+ })
+ }
+}
+
+#[cfg(feature = "uuid")]
+impl FromSql for uuid::Uuid {
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ value
+ .as_blob()
+ .and_then(|bytes| {
+ uuid::Builder::from_slice(bytes)
+ .map_err(|_| FromSqlError::InvalidUuidSize(bytes.len()))
+ })
+ .map(|mut builder| builder.build())
+ }
+}
+
+impl<T: FromSql> FromSql for Option<T> {
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ match value {
+ ValueRef::Null => Ok(None),
+ _ => FromSql::column_result(value).map(Some),
+ }
+ }
+}
+
+impl FromSql for Value {
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ Ok(value.into())
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::FromSql;
+ use crate::{Connection, Error};
+
+ fn checked_memory_handle() -> Connection {
+ Connection::open_in_memory().unwrap()
+ }
+
+ #[test]
+ fn test_integral_ranges() {
+ let db = checked_memory_handle();
+
+ fn check_ranges<T>(db: &Connection, out_of_range: &[i64], in_range: &[i64])
+ where
+ T: Into<i64> + FromSql + ::std::fmt::Debug,
+ {
+ for n in out_of_range {
+ let err = db
+ .query_row("SELECT ?", &[n], |r| r.get::<_, T>(0))
+ .unwrap_err();
+ match err {
+ Error::IntegralValueOutOfRange(_, value) => assert_eq!(*n, value),
+ _ => panic!("unexpected error: {}", err),
+ }
+ }
+ for n in in_range {
+ assert_eq!(
+ *n,
+ db.query_row("SELECT ?", &[n], |r| r.get::<_, T>(0))
+ .unwrap()
+ .into()
+ );
+ }
+ }
+
+ check_ranges::<i8>(&db, &[-129, 128], &[-128, 0, 1, 127]);
+ check_ranges::<i16>(&db, &[-32769, 32768], &[-32768, -1, 0, 1, 32767]);
+ check_ranges::<i32>(
+ &db,
+ &[-2_147_483_649, 2_147_483_648],
+ &[-2_147_483_648, -1, 0, 1, 2_147_483_647],
+ );
+ check_ranges::<u8>(&db, &[-2, -1, 256], &[0, 1, 255]);
+ check_ranges::<u16>(&db, &[-2, -1, 65536], &[0, 1, 65535]);
+ check_ranges::<u32>(&db, &[-2, -1, 4_294_967_296], &[0, 1, 4_294_967_295]);
+ }
+}
diff --git a/src/types/mod.rs b/src/types/mod.rs
new file mode 100644
index 0000000..d79ff82
--- /dev/null
+++ b/src/types/mod.rs
@@ -0,0 +1,363 @@
+//! Traits dealing with SQLite data types.
+//!
+//! SQLite uses a [dynamic type system](https://www.sqlite.org/datatype3.html). Implementations of
+//! the `ToSql` and `FromSql` traits are provided for the basic types that
+//! SQLite provides methods for:
+//!
+//! * Integers (`i32` and `i64`; SQLite uses `i64` internally, so getting an
+//! `i32` will truncate if the value is too large or too small).
+//! * Reals (`f64`)
+//! * Strings (`String` and `&str`)
+//! * Blobs (`Vec<u8>` and `&[u8]`)
+//!
+//! Additionally, because it is such a common data type, implementations are
+//! provided for `time::Timespec` that use the RFC 3339 date/time format,
+//! `"%Y-%m-%dT%H:%M:%S.%fZ"`, to store time values as strings. These values
+//! can be parsed by SQLite's builtin
+//! [datetime](https://www.sqlite.org/lang_datefunc.html) functions. If you
+//! want different storage for timespecs, you can use a newtype. For example, to
+//! store timespecs as `f64`s:
+//!
+//! ```rust
+//! use rusqlite::types::{FromSql, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
+//! use rusqlite::Result;
+//!
+//! pub struct TimespecSql(pub time::Timespec);
+//!
+//! impl FromSql for TimespecSql {
+//! fn column_result(value: ValueRef) -> FromSqlResult<Self> {
+//! f64::column_result(value).map(|as_f64| {
+//! TimespecSql(time::Timespec {
+//! sec: as_f64.trunc() as i64,
+//! nsec: (as_f64.fract() * 1.0e9) as i32,
+//! })
+//! })
+//! }
+//! }
+//!
+//! impl ToSql for TimespecSql {
+//! fn to_sql(&self) -> Result<ToSqlOutput> {
+//! let TimespecSql(ts) = *self;
+//! let as_f64 = ts.sec as f64 + (ts.nsec as f64) / 1.0e9;
+//! Ok(as_f64.into())
+//! }
+//! }
+//! ```
+//!
+//! `ToSql` and `FromSql` are also implemented for `Option<T>` where `T`
+//! implements `ToSql` or `FromSql` for the cases where you want to know if a
+//! value was NULL (which gets translated to `None`).
+
+pub use self::from_sql::{FromSql, FromSqlError, FromSqlResult};
+pub use self::to_sql::{ToSql, ToSqlOutput};
+pub use self::value::Value;
+pub use self::value_ref::ValueRef;
+
+use std::fmt;
+
+#[cfg(feature = "chrono")]
+mod chrono;
+mod from_sql;
+#[cfg(feature = "serde_json")]
+mod serde_json;
+mod time;
+mod to_sql;
+#[cfg(feature = "url")]
+mod url;
+mod value;
+mod value_ref;
+
+/// Empty struct that can be used to fill in a query parameter as `NULL`.
+///
+/// ## Example
+///
+/// ```rust,no_run
+/// # use rusqlite::{Connection, Result};
+/// # use rusqlite::types::{Null};
+///
+/// fn insert_null(conn: &Connection) -> Result<usize> {
+/// conn.execute("INSERT INTO people (name) VALUES (?)", &[Null])
+/// }
+/// ```
+#[derive(Copy, Clone)]
+pub struct Null;
+
+#[derive(Clone, Debug, PartialEq)]
+pub enum Type {
+ Null,
+ Integer,
+ Real,
+ Text,
+ Blob,
+}
+
+impl fmt::Display for Type {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match *self {
+ Type::Null => write!(f, "Null"),
+ Type::Integer => write!(f, "Integer"),
+ Type::Real => write!(f, "Real"),
+ Type::Text => write!(f, "Text"),
+ Type::Blob => write!(f, "Blob"),
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::Value;
+ use crate::{Connection, Error, NO_PARAMS};
+ use std::f64::EPSILON;
+ use std::os::raw::{c_double, c_int};
+
+ fn checked_memory_handle() -> Connection {
+ let db = Connection::open_in_memory().unwrap();
+ db.execute_batch("CREATE TABLE foo (b BLOB, t TEXT, i INTEGER, f FLOAT, n)")
+ .unwrap();
+ db
+ }
+
+ #[test]
+ fn test_blob() {
+ let db = checked_memory_handle();
+
+ let v1234 = vec![1u8, 2, 3, 4];
+ db.execute("INSERT INTO foo(b) VALUES (?)", &[&v1234])
+ .unwrap();
+
+ let v: Vec<u8> = db
+ .query_row("SELECT b FROM foo", NO_PARAMS, |r| r.get(0))
+ .unwrap();
+ assert_eq!(v, v1234);
+ }
+
+ #[test]
+ fn test_empty_blob() {
+ let db = checked_memory_handle();
+
+ let empty = vec![];
+ db.execute("INSERT INTO foo(b) VALUES (?)", &[&empty])
+ .unwrap();
+
+ let v: Vec<u8> = db
+ .query_row("SELECT b FROM foo", NO_PARAMS, |r| r.get(0))
+ .unwrap();
+ assert_eq!(v, empty);
+ }
+
+ #[test]
+ fn test_str() {
+ let db = checked_memory_handle();
+
+ let s = "hello, world!";
+ db.execute("INSERT INTO foo(t) VALUES (?)", &[&s]).unwrap();
+
+ let from: String = db
+ .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
+ .unwrap();
+ assert_eq!(from, s);
+ }
+
+ #[test]
+ fn test_string() {
+ let db = checked_memory_handle();
+
+ let s = "hello, world!";
+ db.execute("INSERT INTO foo(t) VALUES (?)", &[s.to_owned()])
+ .unwrap();
+
+ let from: String = db
+ .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
+ .unwrap();
+ assert_eq!(from, s);
+ }
+
+ #[test]
+ fn test_value() {
+ let db = checked_memory_handle();
+
+ db.execute("INSERT INTO foo(i) VALUES (?)", &[Value::Integer(10)])
+ .unwrap();
+
+ assert_eq!(
+ 10i64,
+ db.query_row::<i64, _, _>("SELECT i FROM foo", NO_PARAMS, |r| r.get(0))
+ .unwrap()
+ );
+ }
+
+ #[test]
+ fn test_option() {
+ let db = checked_memory_handle();
+
+ let s = Some("hello, world!");
+ let b = Some(vec![1u8, 2, 3, 4]);
+
+ db.execute("INSERT INTO foo(t) VALUES (?)", &[&s]).unwrap();
+ db.execute("INSERT INTO foo(b) VALUES (?)", &[&b]).unwrap();
+
+ let mut stmt = db
+ .prepare("SELECT t, b FROM foo ORDER BY ROWID ASC")
+ .unwrap();
+ let mut rows = stmt.query(NO_PARAMS).unwrap();
+
+ {
+ let row1 = rows.next().unwrap().unwrap();
+ let s1: Option<String> = row1.get_unwrap(0);
+ let b1: Option<Vec<u8>> = row1.get_unwrap(1);
+ assert_eq!(s.unwrap(), s1.unwrap());
+ assert!(b1.is_none());
+ }
+
+ {
+ let row2 = rows.next().unwrap().unwrap();
+ let s2: Option<String> = row2.get_unwrap(0);
+ let b2: Option<Vec<u8>> = row2.get_unwrap(1);
+ assert!(s2.is_none());
+ assert_eq!(b, b2);
+ }
+ }
+
+ #[test]
+ #[allow(clippy::cognitive_complexity)]
+ fn test_mismatched_types() {
+ fn is_invalid_column_type(err: Error) -> bool {
+ match err {
+ Error::InvalidColumnType(..) => true,
+ _ => false,
+ }
+ }
+
+ let db = checked_memory_handle();
+
+ db.execute(
+ "INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)",
+ NO_PARAMS,
+ )
+ .unwrap();
+
+ let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo").unwrap();
+ let mut rows = stmt.query(NO_PARAMS).unwrap();
+
+ let row = rows.next().unwrap().unwrap();
+
+ // check the correct types come back as expected
+ assert_eq!(vec![1, 2], row.get::<_, Vec<u8>>(0).unwrap());
+ assert_eq!("text", row.get::<_, String>(1).unwrap());
+ assert_eq!(1, row.get::<_, c_int>(2).unwrap());
+ assert!((1.5 - row.get::<_, c_double>(3).unwrap()).abs() < EPSILON);
+ assert!(row.get::<_, Option<c_int>>(4).unwrap().is_none());
+ assert!(row.get::<_, Option<c_double>>(4).unwrap().is_none());
+ assert!(row.get::<_, Option<String>>(4).unwrap().is_none());
+
+ // check some invalid types
+
+ // 0 is actually a blob (Vec<u8>)
+ assert!(is_invalid_column_type(
+ row.get::<_, c_int>(0).err().unwrap()
+ ));
+ assert!(is_invalid_column_type(
+ row.get::<_, c_int>(0).err().unwrap()
+ ));
+ assert!(is_invalid_column_type(row.get::<_, i64>(0).err().unwrap()));
+ assert!(is_invalid_column_type(
+ row.get::<_, c_double>(0).err().unwrap()
+ ));
+ assert!(is_invalid_column_type(
+ row.get::<_, String>(0).err().unwrap()
+ ));
+ assert!(is_invalid_column_type(
+ row.get::<_, time::Timespec>(0).err().unwrap()
+ ));
+ assert!(is_invalid_column_type(
+ row.get::<_, Option<c_int>>(0).err().unwrap()
+ ));
+
+ // 1 is actually a text (String)
+ assert!(is_invalid_column_type(
+ row.get::<_, c_int>(1).err().unwrap()
+ ));
+ assert!(is_invalid_column_type(row.get::<_, i64>(1).err().unwrap()));
+ assert!(is_invalid_column_type(
+ row.get::<_, c_double>(1).err().unwrap()
+ ));
+ assert!(is_invalid_column_type(
+ row.get::<_, Vec<u8>>(1).err().unwrap()
+ ));
+ assert!(is_invalid_column_type(
+ row.get::<_, Option<c_int>>(1).err().unwrap()
+ ));
+
+ // 2 is actually an integer
+ assert!(is_invalid_column_type(
+ row.get::<_, String>(2).err().unwrap()
+ ));
+ assert!(is_invalid_column_type(
+ row.get::<_, Vec<u8>>(2).err().unwrap()
+ ));
+ assert!(is_invalid_column_type(
+ row.get::<_, Option<String>>(2).err().unwrap()
+ ));
+
+ // 3 is actually a float (c_double)
+ assert!(is_invalid_column_type(
+ row.get::<_, c_int>(3).err().unwrap()
+ ));
+ assert!(is_invalid_column_type(row.get::<_, i64>(3).err().unwrap()));
+ assert!(is_invalid_column_type(
+ row.get::<_, String>(3).err().unwrap()
+ ));
+ assert!(is_invalid_column_type(
+ row.get::<_, Vec<u8>>(3).err().unwrap()
+ ));
+ assert!(is_invalid_column_type(
+ row.get::<_, Option<c_int>>(3).err().unwrap()
+ ));
+
+ // 4 is actually NULL
+ assert!(is_invalid_column_type(
+ row.get::<_, c_int>(4).err().unwrap()
+ ));
+ assert!(is_invalid_column_type(row.get::<_, i64>(4).err().unwrap()));
+ assert!(is_invalid_column_type(
+ row.get::<_, c_double>(4).err().unwrap()
+ ));
+ assert!(is_invalid_column_type(
+ row.get::<_, String>(4).err().unwrap()
+ ));
+ assert!(is_invalid_column_type(
+ row.get::<_, Vec<u8>>(4).err().unwrap()
+ ));
+ assert!(is_invalid_column_type(
+ row.get::<_, time::Timespec>(4).err().unwrap()
+ ));
+ }
+
+ #[test]
+ fn test_dynamic_type() {
+ use super::Value;
+ let db = checked_memory_handle();
+
+ db.execute(
+ "INSERT INTO foo(b, t, i, f) VALUES (X'0102', 'text', 1, 1.5)",
+ NO_PARAMS,
+ )
+ .unwrap();
+
+ let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo").unwrap();
+ let mut rows = stmt.query(NO_PARAMS).unwrap();
+
+ let row = rows.next().unwrap().unwrap();
+ assert_eq!(Value::Blob(vec![1, 2]), row.get::<_, Value>(0).unwrap());
+ assert_eq!(
+ Value::Text(String::from("text")),
+ row.get::<_, Value>(1).unwrap()
+ );
+ assert_eq!(Value::Integer(1), row.get::<_, Value>(2).unwrap());
+ match row.get::<_, Value>(3).unwrap() {
+ Value::Real(val) => assert!((1.5 - val).abs() < EPSILON),
+ x => panic!("Invalid Value {:?}", x),
+ }
+ assert_eq!(Value::Null, row.get::<_, Value>(4).unwrap());
+ }
+}
diff --git a/src/types/serde_json.rs b/src/types/serde_json.rs
new file mode 100644
index 0000000..abaecda
--- /dev/null
+++ b/src/types/serde_json.rs
@@ -0,0 +1,60 @@
+//! `ToSql` and `FromSql` implementation for JSON `Value`.
+
+use serde_json::Value;
+
+use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
+use crate::Result;
+
+/// Serialize JSON `Value` to text.
+impl ToSql for Value {
+ fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
+ Ok(ToSqlOutput::from(serde_json::to_string(self).unwrap()))
+ }
+}
+
+/// Deserialize text/blob to JSON `Value`.
+impl FromSql for Value {
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ match value {
+ ValueRef::Text(s) => serde_json::from_slice(s),
+ ValueRef::Blob(b) => serde_json::from_slice(b),
+ _ => return Err(FromSqlError::InvalidType),
+ }
+ .map_err(|err| FromSqlError::Other(Box::new(err)))
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::types::ToSql;
+ use crate::{Connection, NO_PARAMS};
+
+ fn checked_memory_handle() -> Connection {
+ let db = Connection::open_in_memory().unwrap();
+ db.execute_batch("CREATE TABLE foo (t TEXT, b BLOB)")
+ .unwrap();
+ db
+ }
+
+ #[test]
+ fn test_json_value() {
+ let db = checked_memory_handle();
+
+ let json = r#"{"foo": 13, "bar": "baz"}"#;
+ let data: serde_json::Value = serde_json::from_str(json).unwrap();
+ db.execute(
+ "INSERT INTO foo (t, b) VALUES (?, ?)",
+ &[&data as &dyn ToSql, &json.as_bytes()],
+ )
+ .unwrap();
+
+ let t: serde_json::Value = db
+ .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
+ .unwrap();
+ assert_eq!(data, t);
+ let b: serde_json::Value = db
+ .query_row("SELECT b FROM foo", NO_PARAMS, |r| r.get(0))
+ .unwrap();
+ assert_eq!(data, b);
+ }
+}
diff --git a/src/types/time.rs b/src/types/time.rs
new file mode 100644
index 0000000..097b22a
--- /dev/null
+++ b/src/types/time.rs
@@ -0,0 +1,79 @@
+use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
+use crate::Result;
+
+const CURRENT_TIMESTAMP_FMT: &str = "%Y-%m-%d %H:%M:%S";
+const SQLITE_DATETIME_FMT: &str = "%Y-%m-%dT%H:%M:%S.%fZ";
+const SQLITE_DATETIME_FMT_LEGACY: &str = "%Y-%m-%d %H:%M:%S:%f %Z";
+
+impl ToSql for time::Timespec {
+ fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
+ let time_string = time::at_utc(*self)
+ .strftime(SQLITE_DATETIME_FMT)
+ .unwrap()
+ .to_string();
+ Ok(ToSqlOutput::from(time_string))
+ }
+}
+
+impl FromSql for time::Timespec {
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ value
+ .as_str()
+ .and_then(|s| {
+ match s.len() {
+ 19 => time::strptime(s, CURRENT_TIMESTAMP_FMT),
+ _ => time::strptime(s, SQLITE_DATETIME_FMT).or_else(|err| {
+ time::strptime(s, SQLITE_DATETIME_FMT_LEGACY).or_else(|_| Err(err))
+ }),
+ }
+ .or_else(|err| Err(FromSqlError::Other(Box::new(err))))
+ })
+ .map(|tm| tm.to_timespec())
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::{Connection, Result, NO_PARAMS};
+
+ fn checked_memory_handle() -> Connection {
+ let db = Connection::open_in_memory().unwrap();
+ db.execute_batch("CREATE TABLE foo (t TEXT, i INTEGER, f FLOAT)")
+ .unwrap();
+ db
+ }
+
+ #[test]
+ fn test_timespec() {
+ let db = checked_memory_handle();
+
+ let mut ts_vec = vec![];
+
+ ts_vec.push(time::Timespec::new(10_000, 0)); //January 1, 1970 2:46:40 AM
+ ts_vec.push(time::Timespec::new(10_000, 1000)); //January 1, 1970 2:46:40 AM (and one microsecond)
+ ts_vec.push(time::Timespec::new(1_500_391_124, 1_000_000)); //July 18, 2017
+ ts_vec.push(time::Timespec::new(2_000_000_000, 2_000_000)); //May 18, 2033
+ ts_vec.push(time::Timespec::new(3_000_000_000, 999_999_999)); //January 24, 2065
+ ts_vec.push(time::Timespec::new(10_000_000_000, 0)); //November 20, 2286
+
+ for ts in ts_vec {
+ db.execute("INSERT INTO foo(t) VALUES (?)", &[&ts]).unwrap();
+
+ let from: time::Timespec = db
+ .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
+ .unwrap();
+
+ db.execute("DELETE FROM foo", NO_PARAMS).unwrap();
+
+ assert_eq!(from, ts);
+ }
+ }
+
+ #[test]
+ fn test_sqlite_functions() {
+ let db = checked_memory_handle();
+ let result: Result<time::Timespec> =
+ db.query_row("SELECT CURRENT_TIMESTAMP", NO_PARAMS, |r| r.get(0));
+ assert!(result.is_ok());
+ }
+}
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");
+ }
+}
diff --git a/src/types/url.rs b/src/types/url.rs
new file mode 100644
index 0000000..1c9c63a
--- /dev/null
+++ b/src/types/url.rs
@@ -0,0 +1,81 @@
+//! `ToSql` and `FromSql` implementation for [`url::Url`].
+use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
+use crate::Result;
+use url::Url;
+
+/// Serialize `Url` to text.
+impl ToSql for Url {
+ fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
+ Ok(ToSqlOutput::from(self.as_str()))
+ }
+}
+
+/// Deserialize text to `Url`.
+impl FromSql for Url {
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ match value {
+ ValueRef::Text(s) => {
+ let s = std::str::from_utf8(s).map_err(|e| FromSqlError::Other(Box::new(e)))?;
+ Url::parse(s).map_err(|e| FromSqlError::Other(Box::new(e)))
+ }
+ _ => Err(FromSqlError::InvalidType),
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::{params, Connection, Error, Result};
+ use url::{ParseError, Url};
+
+ fn checked_memory_handle() -> Connection {
+ let db = Connection::open_in_memory().unwrap();
+ db.execute_batch("CREATE TABLE urls (i INTEGER, v TEXT)")
+ .unwrap();
+ db
+ }
+
+ fn get_url(db: &Connection, id: i64) -> Result<Url> {
+ db.query_row("SELECT v FROM urls WHERE i = ?", params![id], |r| r.get(0))
+ }
+
+ #[test]
+ fn test_sql_url() {
+ let db = &checked_memory_handle();
+
+ let url0 = Url::parse("http://www.example1.com").unwrap();
+ let url1 = Url::parse("http://www.example1.com/👌").unwrap();
+ let url2 = "http://www.example2.com/👌";
+
+ db.execute(
+ "INSERT INTO urls (i, v) VALUES (0, ?), (1, ?), (2, ?), (3, ?)",
+ // also insert a non-hex encoded url (which might be present if it was
+ // inserted separately)
+ params![url0, url1, url2, "illegal"],
+ )
+ .unwrap();
+
+ assert_eq!(get_url(db, 0).unwrap(), url0);
+
+ assert_eq!(get_url(db, 1).unwrap(), url1);
+
+ // Should successfully read it, even though it wasn't inserted as an
+ // escaped url.
+ let out_url2: Url = get_url(db, 2).unwrap();
+ assert_eq!(out_url2, Url::parse(url2).unwrap());
+
+ // Make sure the conversion error comes through correctly.
+ let err = get_url(db, 3).unwrap_err();
+ match err {
+ Error::FromSqlConversionFailure(_, _, e) => {
+ assert_eq!(
+ *e.downcast::<ParseError>().unwrap(),
+ ParseError::RelativeUrlWithoutBase,
+ );
+ }
+ e => {
+ panic!("Expected conversion failure, got {}", e);
+ }
+ }
+ }
+}
diff --git a/src/types/value.rs b/src/types/value.rs
new file mode 100644
index 0000000..332d78b
--- /dev/null
+++ b/src/types/value.rs
@@ -0,0 +1,121 @@
+use super::{Null, Type};
+
+/// Owning [dynamic type value](http://sqlite.org/datatype3.html). Value's type is typically
+/// dictated by SQLite (not by the caller).
+///
+/// See [`ValueRef`](enum.ValueRef.html) for a non-owning dynamic type value.
+#[derive(Clone, Debug, PartialEq)]
+pub enum Value {
+ /// The value is a `NULL` value.
+ Null,
+ /// The value is a signed integer.
+ Integer(i64),
+ /// The value is a floating point number.
+ Real(f64),
+ /// The value is a text string.
+ Text(String),
+ /// The value is a blob of data
+ Blob(Vec<u8>),
+}
+
+impl From<Null> for Value {
+ fn from(_: Null) -> Value {
+ Value::Null
+ }
+}
+
+impl From<bool> for Value {
+ fn from(i: bool) -> Value {
+ Value::Integer(i as i64)
+ }
+}
+
+impl From<isize> for Value {
+ fn from(i: isize) -> Value {
+ Value::Integer(i as i64)
+ }
+}
+
+#[cfg(feature = "i128_blob")]
+impl From<i128> for Value {
+ fn from(i: i128) -> Value {
+ use byteorder::{BigEndian, ByteOrder};
+ let mut buf = vec![0u8; 16];
+ // We store these biased (e.g. with the most significant bit flipped)
+ // so that comparisons with negative numbers work properly.
+ BigEndian::write_i128(&mut buf, i ^ (1i128 << 127));
+ Value::Blob(buf)
+ }
+}
+
+#[cfg(feature = "uuid")]
+impl From<uuid::Uuid> for Value {
+ fn from(id: uuid::Uuid) -> Value {
+ Value::Blob(id.as_bytes().to_vec())
+ }
+}
+
+macro_rules! from_i64(
+ ($t:ty) => (
+ impl From<$t> for Value {
+ fn from(i: $t) -> Value {
+ Value::Integer(i64::from(i))
+ }
+ }
+ )
+);
+
+from_i64!(i8);
+from_i64!(i16);
+from_i64!(i32);
+from_i64!(u8);
+from_i64!(u16);
+from_i64!(u32);
+
+impl From<i64> for Value {
+ fn from(i: i64) -> Value {
+ Value::Integer(i)
+ }
+}
+
+impl From<f64> for Value {
+ fn from(f: f64) -> Value {
+ Value::Real(f)
+ }
+}
+
+impl From<String> for Value {
+ fn from(s: String) -> Value {
+ Value::Text(s)
+ }
+}
+
+impl From<Vec<u8>> for Value {
+ fn from(v: Vec<u8>) -> Value {
+ Value::Blob(v)
+ }
+}
+
+impl<T> From<Option<T>> for Value
+where
+ T: Into<Value>,
+{
+ fn from(v: Option<T>) -> Value {
+ match v {
+ Some(x) => x.into(),
+ None => Value::Null,
+ }
+ }
+}
+
+impl Value {
+ pub fn data_type(&self) -> Type {
+ match *self {
+ Value::Null => Type::Null,
+ Value::Integer(_) => Type::Integer,
+ Value::Real(_) => Type::Real,
+ Value::Text(_) => Type::Text,
+ Value::Blob(_) => Type::Blob,
+ }
+ }
+}
diff --git a/src/types/value_ref.rs b/src/types/value_ref.rs
new file mode 100644
index 0000000..80d2457
--- /dev/null
+++ b/src/types/value_ref.rs
@@ -0,0 +1,170 @@
+use super::{Type, Value};
+use crate::types::{FromSqlError, FromSqlResult};
+
+/// A non-owning [dynamic type value](http://sqlite.org/datatype3.html). Typically the
+/// memory backing this value is owned by SQLite.
+///
+/// See [`Value`](enum.Value.html) for an owning dynamic type value.
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum ValueRef<'a> {
+ /// The value is a `NULL` value.
+ Null,
+ /// The value is a signed integer.
+ Integer(i64),
+ /// The value is a floating point number.
+ Real(f64),
+ /// The value is a text string.
+ Text(&'a [u8]),
+ /// The value is a blob of data
+ Blob(&'a [u8]),
+}
+
+impl ValueRef<'_> {
+ pub fn data_type(&self) -> Type {
+ match *self {
+ ValueRef::Null => Type::Null,
+ ValueRef::Integer(_) => Type::Integer,
+ ValueRef::Real(_) => Type::Real,
+ ValueRef::Text(_) => Type::Text,
+ ValueRef::Blob(_) => Type::Blob,
+ }
+ }
+}
+
+impl<'a> ValueRef<'a> {
+ /// If `self` is case `Integer`, returns the integral value. Otherwise,
+ /// returns `Err(Error::InvalidColumnType)`.
+ pub fn as_i64(&self) -> FromSqlResult<i64> {
+ match *self {
+ ValueRef::Integer(i) => Ok(i),
+ _ => Err(FromSqlError::InvalidType),
+ }
+ }
+
+ /// If `self` is case `Real`, returns the floating point value. Otherwise,
+ /// returns `Err(Error::InvalidColumnType)`.
+ pub fn as_f64(&self) -> FromSqlResult<f64> {
+ match *self {
+ ValueRef::Real(f) => Ok(f),
+ _ => Err(FromSqlError::InvalidType),
+ }
+ }
+
+ /// If `self` is case `Text`, returns the string value. Otherwise, returns
+ /// `Err(Error::InvalidColumnType)`.
+ pub fn as_str(&self) -> FromSqlResult<&'a str> {
+ match *self {
+ ValueRef::Text(t) => {
+ std::str::from_utf8(t).map_err(|e| FromSqlError::Other(Box::new(e)))
+ }
+ _ => Err(FromSqlError::InvalidType),
+ }
+ }
+
+ /// If `self` is case `Blob`, returns the byte slice. Otherwise, returns
+ /// `Err(Error::InvalidColumnType)`.
+ pub fn as_blob(&self) -> FromSqlResult<&'a [u8]> {
+ match *self {
+ ValueRef::Blob(b) => Ok(b),
+ _ => Err(FromSqlError::InvalidType),
+ }
+ }
+}
+
+impl From<ValueRef<'_>> for Value {
+ fn from(borrowed: ValueRef<'_>) -> Value {
+ match borrowed {
+ ValueRef::Null => Value::Null,
+ ValueRef::Integer(i) => Value::Integer(i),
+ ValueRef::Real(r) => Value::Real(r),
+ ValueRef::Text(s) => {
+ let s = std::str::from_utf8(s).expect("invalid UTF-8");
+ Value::Text(s.to_string())
+ }
+ ValueRef::Blob(b) => Value::Blob(b.to_vec()),
+ }
+ }
+}
+
+impl<'a> From<&'a str> for ValueRef<'a> {
+ fn from(s: &str) -> ValueRef<'_> {
+ ValueRef::Text(s.as_bytes())
+ }
+}
+
+impl<'a> From<&'a [u8]> for ValueRef<'a> {
+ fn from(s: &[u8]) -> ValueRef<'_> {
+ ValueRef::Blob(s)
+ }
+}
+
+impl<'a> From<&'a Value> for ValueRef<'a> {
+ fn from(value: &'a Value) -> ValueRef<'a> {
+ match *value {
+ Value::Null => ValueRef::Null,
+ Value::Integer(i) => ValueRef::Integer(i),
+ Value::Real(r) => ValueRef::Real(r),
+ Value::Text(ref s) => ValueRef::Text(s.as_bytes()),
+ Value::Blob(ref b) => ValueRef::Blob(b),
+ }
+ }
+}
+
+impl<'a, T> From<Option<T>> for ValueRef<'a>
+where
+ T: Into<ValueRef<'a>>,
+{
+ fn from(s: Option<T>) -> ValueRef<'a> {
+ match s {
+ Some(x) => x.into(),
+ None => ValueRef::Null,
+ }
+ }
+}
+
+#[cfg(any(feature = "functions", feature = "session", feature = "vtab"))]
+impl<'a> ValueRef<'a> {
+ pub(crate) unsafe fn from_value(value: *mut crate::ffi::sqlite3_value) -> ValueRef<'a> {
+ use crate::ffi;
+ use std::slice::from_raw_parts;
+
+ match ffi::sqlite3_value_type(value) {
+ ffi::SQLITE_NULL => ValueRef::Null,
+ ffi::SQLITE_INTEGER => ValueRef::Integer(ffi::sqlite3_value_int64(value)),
+ ffi::SQLITE_FLOAT => ValueRef::Real(ffi::sqlite3_value_double(value)),
+ ffi::SQLITE_TEXT => {
+ let text = ffi::sqlite3_value_text(value);
+ let len = ffi::sqlite3_value_bytes(value);
+ assert!(
+ !text.is_null(),
+ "unexpected SQLITE_TEXT value type with NULL data"
+ );
+ let s = from_raw_parts(text as *const u8, len as usize);
+ ValueRef::Text(s)
+ }
+ ffi::SQLITE_BLOB => {
+ let (blob, len) = (
+ ffi::sqlite3_value_blob(value),
+ ffi::sqlite3_value_bytes(value),
+ );
+
+ assert!(
+ len >= 0,
+ "unexpected negative return from sqlite3_value_bytes"
+ );
+ if len > 0 {
+ assert!(
+ !blob.is_null(),
+ "unexpected SQLITE_BLOB value type with NULL data"
+ );
+ ValueRef::Blob(from_raw_parts(blob as *const u8, len as usize))
+ } else {
+ // The return value from sqlite3_value_blob() for a zero-length BLOB
+ // is a NULL pointer.
+ ValueRef::Blob(&[])
+ }
+ }
+ _ => unreachable!("sqlite3_value_type returned invalid value"),
+ }
+ }
+}