From efc6c8937022c910d314a8885001880b936da301 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Mon, 23 May 2016 21:46:51 -0400 Subject: [PATCH 01/11] Add RawStatement::column_type. --- src/raw_statement.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/raw_statement.rs b/src/raw_statement.rs index 9676d94..adb5396 100644 --- a/src/raw_statement.rs +++ b/src/raw_statement.rs @@ -20,6 +20,10 @@ impl RawStatement { unsafe { ffi::sqlite3_column_count(self.0) } } + pub fn column_type(&self, idx: c_int) -> c_int { + unsafe { ffi::sqlite3_column_type(self.0, idx) } + } + pub fn column_name(&self, idx: c_int) -> &CStr { unsafe { CStr::from_ptr(ffi::sqlite3_column_name(self.0, idx)) } } From c90cd37c00e12e673441f6b0e1e9160b222a053f Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Mon, 23 May 2016 21:48:56 -0400 Subject: [PATCH 02/11] Add types::BorrowedValue. --- src/types/borrowed_value.rs | 51 +++++++++++++++++++++++++++++++++++++ src/types/mod.rs | 2 ++ 2 files changed, 53 insertions(+) create mode 100644 src/types/borrowed_value.rs diff --git a/src/types/borrowed_value.rs b/src/types/borrowed_value.rs new file mode 100644 index 0000000..8b97a33 --- /dev/null +++ b/src/types/borrowed_value.rs @@ -0,0 +1,51 @@ +use ::Result; +use ::error::Error; +use super::Value; + +pub enum BorrowedValue<'a> { + Null, + Integer(i64), + Real(f64), + Text(&'a str), + Blob(&'a [u8]), +} + +impl<'a> BorrowedValue<'a> { + pub fn to_value(&self) -> Value { + match *self { + BorrowedValue::Null => Value::Null, + BorrowedValue::Integer(i) => Value::Integer(i), + BorrowedValue::Real(r) => Value::Real(r), + BorrowedValue::Text(s) => Value::Text(s.to_string()), + BorrowedValue::Blob(b) => Value::Blob(b.to_vec()), + } + } + + pub fn as_i64(&self) -> Result { + match *self { + BorrowedValue::Integer(i) => Ok(i), + _ => Err(Error::InvalidColumnType), + } + } + + pub fn as_f64(&self) -> Result { + match *self { + BorrowedValue::Real(f) => Ok(f), + _ => Err(Error::InvalidColumnType), + } + } + + pub fn as_str(&self) -> Result<&str> { + match *self { + BorrowedValue::Text(ref t) => Ok(t), + _ => Err(Error::InvalidColumnType), + } + } + + pub fn as_blob(&self) -> Result<&[u8]> { + match *self { + BorrowedValue::Blob(ref b) => Ok(b), + _ => Err(Error::InvalidColumnType), + } + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs index 6110d25..f8c8fba 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -58,7 +58,9 @@ pub use ffi::{SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB, SQLITE_NUL pub use self::from_sql::FromSql; pub use self::to_sql::ToSql; +pub use self::borrowed_value::BorrowedValue; +mod borrowed_value; mod from_sql; mod to_sql; mod time; From 5b0cdbaa5674becd1c04d25b21dc0b89b67a4fd9 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Mon, 23 May 2016 21:49:54 -0400 Subject: [PATCH 03/11] Redo FromSql to make implementing it not `unsafe`. Pass implementers a BorrowedValue instead of relying on them to use the FFI interface. We take the responsibility of converting the raw statement and column index into a BorrowedValue. --- src/lib.rs | 47 +++++++++++---- src/types/chrono.rs | 116 ++++++++++++++++++------------------- src/types/from_sql.rs | 123 +++++++++++----------------------------- src/types/serde_json.rs | 21 ++----- src/types/time.rs | 13 ++--- 5 files changed, 133 insertions(+), 187 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e3894cd..909ba1f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,7 +75,7 @@ use std::result; use std::str; use libc::{c_int, c_char}; -use types::{ToSql, FromSql}; +use types::{ToSql, FromSql, BorrowedValue}; use error::{error_from_sqlite_code, error_from_handle}; use raw_statement::RawStatement; use cache::StatementCache; @@ -1064,15 +1064,9 @@ impl<'a, 'stmt> Row<'a, 'stmt> { /// Returns an `Error::InvalidColumnName` if `idx` is not a valid column name /// for this row. pub fn get_checked(&self, idx: I) -> Result { - unsafe { - let idx = try!(idx.idx(self.stmt)); - - if T::column_has_valid_sqlite_type(self.stmt.stmt.ptr(), idx) { - FromSql::column_result(self.stmt.stmt.ptr(), idx) - } else { - Err(Error::InvalidColumnType) - } - } + let idx = try!(idx.idx(self.stmt)); + let value = unsafe { BorrowedValue::new(&self.stmt.stmt, idx) }; + FromSql::column_result(value) } /// Return the number of columns in the current row. @@ -1106,6 +1100,39 @@ impl<'a> RowIndex for &'a str { } } +impl<'a> BorrowedValue<'a> { + unsafe fn new(stmt: &RawStatement, col: c_int) -> BorrowedValue { + use std::slice::from_raw_parts; + + let raw = stmt.ptr(); + + match stmt.column_type(col) { + ffi::SQLITE_NULL => BorrowedValue::Null, + ffi::SQLITE_INTEGER => BorrowedValue::Integer(ffi::sqlite3_column_int64(raw, col)), + ffi::SQLITE_FLOAT => BorrowedValue::Real(ffi::sqlite3_column_double(raw, col)), + ffi::SQLITE_TEXT => { + let text = ffi::sqlite3_column_text(raw, col); + assert!(!text.is_null(), "unexpected SQLITE_TEXT column type with NULL data"); + let s = CStr::from_ptr(text as *const c_char); + + // sqlite3_column_text returns UTF8 data, so our unwrap here should be fine. + let s = s.to_str().expect("sqlite3_column_text returned invalid UTF-8"); + BorrowedValue::Text(s) + } + ffi::SQLITE_BLOB => { + let blob = ffi::sqlite3_column_blob(raw, col); + assert!(!blob.is_null(), "unexpected SQLITE_BLOB column type with NULL data"); + + let len = ffi::sqlite3_column_bytes(raw, col); + assert!(len >= 0, "unexpected negative return from sqlite3_column_bytes"); + + BorrowedValue::Blob(from_raw_parts(blob as *const u8, len as usize)) + } + _ => unreachable!("sqlite3_column_type returned invalid value") + } + } +} + #[cfg(test)] mod test { extern crate tempdir; diff --git a/src/types/chrono.rs b/src/types/chrono.rs index a9e0bea..7ecb28f 100644 --- a/src/types/chrono.rs +++ b/src/types/chrono.rs @@ -1,11 +1,13 @@ //! Convert most of the [Time Strings](http://sqlite.org/lang_datefunc.html) to chrono types. extern crate chrono; +use std::borrow::Cow; + use self::chrono::{NaiveDate, NaiveTime, NaiveDateTime, DateTime, TimeZone, UTC, Local}; use libc::c_int; use {Error, Result}; -use types::{FromSql, ToSql}; +use types::{FromSql, ToSql, BorrowedValue}; use ffi::sqlite3_stmt; @@ -19,16 +21,11 @@ impl ToSql for NaiveDate { /// "YYYY-MM-DD" => ISO 8601 calendar date without timezone. impl FromSql for NaiveDate { - unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result { - let s = try!(String::column_result(stmt, col)); - match NaiveDate::parse_from_str(&s, "%Y-%m-%d") { + fn column_result(value: BorrowedValue) -> Result { + value.as_str().and_then(|s| match NaiveDate::parse_from_str(s, "%Y-%m-%d") { Ok(dt) => Ok(dt), Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))), - } - } - - unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool { - String::column_has_valid_sqlite_type(stmt, col) + }) } } @@ -42,21 +39,18 @@ impl ToSql for NaiveTime { /// "HH:MM"/"HH:MM:SS"/"HH:MM:SS.SSS" => ISO 8601 time without timezone. impl FromSql for NaiveTime { - unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result { - let s = try!(String::column_result(stmt, col)); - 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(Error::FromSqlConversionFailure(Box::new(err))), - } - } - - unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool { - String::column_has_valid_sqlite_type(stmt, col) + fn column_result(value: BorrowedValue) -> Result { + 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(Error::FromSqlConversionFailure(Box::new(err))), + } + }) } } @@ -71,23 +65,19 @@ impl ToSql for NaiveDateTime { /// "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 { - unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result { - let s = try!(String::column_result(stmt, col)); + fn column_result(value: BorrowedValue) -> Result { + 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" + }; - 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(Error::FromSqlConversionFailure(Box::new(err))), - } - } - - unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool { - String::column_has_valid_sqlite_type(stmt, col) + match NaiveDateTime::parse_from_str(s, fmt) { + Ok(dt) => Ok(dt), + Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))), + } + }) } } @@ -101,38 +91,40 @@ impl ToSql for DateTime { /// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into DateTime. impl FromSql for DateTime { - unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result> { - let s = { - let mut s = try!(String::column_result(stmt, col)); - if s.len() >= 11 { - let sbytes = s.as_mut_vec(); - if sbytes[10] == b' ' { + fn column_result(value: BorrowedValue) -> Result { + { + // Try to parse value as rfc3339 first. + let s = try!(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'; } - } - s - }; - match DateTime::parse_from_rfc3339(&s) { - Ok(dt) => Ok(dt.with_timezone(&UTC)), - Err(_) => NaiveDateTime::column_result(stmt, col).map(|dt| UTC.from_utc_datetime(&dt)), - } - } + Cow::Owned(s) + } else { + Cow::Borrowed(s) + }; - unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool { - String::column_has_valid_sqlite_type(stmt, col) + match DateTime::parse_from_rfc3339(&s) { + Ok(dt) => return Ok(dt.with_timezone(&UTC)), + Err(_) => (), + } + } + + // 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. impl FromSql for DateTime { - unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result> { - let utc_dt = try!(DateTime::::column_result(stmt, col)); + fn column_result(value: BorrowedValue) -> Result { + let utc_dt = try!(DateTime::::column_result(value)); Ok(utc_dt.with_timezone(&Local)) } - - unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool { - DateTime::::column_has_valid_sqlite_type(stmt, col) - } } #[cfg(test)] diff --git a/src/types/from_sql.rs b/src/types/from_sql.rs index a9fc3f6..a996347 100644 --- a/src/types/from_sql.rs +++ b/src/types/from_sql.rs @@ -1,119 +1,60 @@ -use std::ffi::CStr; -use std::mem; -use std::str; +use super::{BorrowedValue, Value}; +use ::Result; -use libc::{c_char, c_double, c_int}; - -use super::Value; -use ffi::{sqlite3_stmt, sqlite3_column_type}; -use ::{ffi, Result}; -use ::error::Error; - -/// A trait for types that can be created from a SQLite value. pub trait FromSql: Sized { - unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result; + fn column_result(value: BorrowedValue) -> Result; +} - /// FromSql types can implement this method and use sqlite3_column_type to check that - /// the type reported by SQLite matches a type suitable for Self. This method is used - /// by `Row::get_checked` to confirm that the column contains a valid type before - /// attempting to retrieve the value. - unsafe fn column_has_valid_sqlite_type(_: *mut sqlite3_stmt, _: c_int) -> bool { - true +impl FromSql for i32 { + fn column_result(value: BorrowedValue) -> Result { + i64::column_result(value).map(|i| i as i32) } } -macro_rules! raw_from_impl( - ($t:ty, $f:ident, $c:expr) => ( - impl FromSql for $t { - unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result<$t> { - Ok(ffi::$f(stmt, col)) - } +impl FromSql for i64 { + fn column_result(value: BorrowedValue) -> Result { + value.as_i64() + } +} - unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool { - sqlite3_column_type(stmt, col) == $c - } - } - ) -); - -raw_from_impl!(c_int, sqlite3_column_int, ffi::SQLITE_INTEGER); // i32 -raw_from_impl!(i64, sqlite3_column_int64, ffi::SQLITE_INTEGER); -raw_from_impl!(c_double, sqlite3_column_double, ffi::SQLITE_FLOAT); // f64 +impl FromSql for f64 { + fn column_result(value: BorrowedValue) -> Result { + value.as_f64() + } +} impl FromSql for bool { - unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result { - match ffi::sqlite3_column_int(stmt, col) { - 0 => Ok(false), - _ => Ok(true), - } - } - - unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool { - sqlite3_column_type(stmt, col) == ffi::SQLITE_INTEGER + fn column_result(value: BorrowedValue) -> Result { + i64::column_result(value).map(|i| match i { + 0 => false, + _ => true, + }) } } impl FromSql for String { - unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result { - let c_text = ffi::sqlite3_column_text(stmt, col); - if c_text.is_null() { - Ok("".to_owned()) - } else { - let c_slice = CStr::from_ptr(c_text as *const c_char).to_bytes(); - let utf8_str = try!(str::from_utf8(c_slice)); - Ok(utf8_str.into()) - } - } - - unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool { - sqlite3_column_type(stmt, col) == ffi::SQLITE_TEXT + fn column_result(value: BorrowedValue) -> Result { + value.as_str().map(|s| s.to_string()) } } impl FromSql for Vec { - unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result> { - use std::slice::from_raw_parts; - let c_blob = ffi::sqlite3_column_blob(stmt, col); - let len = ffi::sqlite3_column_bytes(stmt, col); - - // The documentation for sqlite3_column_bytes indicates it is always non-negative, - // but we should assert here just to be sure. - assert!(len >= 0, - "unexpected negative return from sqlite3_column_bytes"); - let len = len as usize; - - Ok(from_raw_parts(mem::transmute(c_blob), len).to_vec()) - } - - unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool { - sqlite3_column_type(stmt, col) == ffi::SQLITE_BLOB + fn column_result(value: BorrowedValue) -> Result { + value.as_blob().map(|b| b.to_vec()) } } impl FromSql for Option { - unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result> { - if sqlite3_column_type(stmt, col) == ffi::SQLITE_NULL { - Ok(None) - } else { - FromSql::column_result(stmt, col).map(Some) + fn column_result(value: BorrowedValue) -> Result { + match value { + BorrowedValue::Null => Ok(None), + _ => FromSql::column_result(value).map(Some), } } - - unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool { - sqlite3_column_type(stmt, col) == ffi::SQLITE_NULL || - T::column_has_valid_sqlite_type(stmt, col) - } } impl FromSql for Value { - unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result { - match sqlite3_column_type(stmt, col) { - ffi::SQLITE_TEXT => FromSql::column_result(stmt, col).map(Value::Text), - ffi::SQLITE_INTEGER => Ok(Value::Integer(ffi::sqlite3_column_int64(stmt, col))), - ffi::SQLITE_FLOAT => Ok(Value::Real(ffi::sqlite3_column_double(stmt, col))), - ffi::SQLITE_NULL => Ok(Value::Null), - ffi::SQLITE_BLOB => FromSql::column_result(stmt, col).map(Value::Blob), - _ => Err(Error::InvalidColumnType), - } + fn column_result(value: BorrowedValue) -> Result { + Ok(value.to_value()) } } diff --git a/src/types/serde_json.rs b/src/types/serde_json.rs index dc1eeff..a3da2fc 100644 --- a/src/types/serde_json.rs +++ b/src/types/serde_json.rs @@ -5,11 +5,9 @@ use libc::c_int; use self::serde_json::Value; use {Error, Result}; -use types::{FromSql, ToSql}; +use types::{FromSql, ToSql, BorrowedValue}; -use ffi; use ffi::sqlite3_stmt; -use ffi::sqlite3_column_type; /// Serialize JSON `Value` to text. impl ToSql for Value { @@ -21,19 +19,12 @@ impl ToSql for Value { /// Deserialize text/blob to JSON `Value`. impl FromSql for Value { - unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result { - let value_result = match sqlite3_column_type(stmt, col) { - ffi::SQLITE_TEXT => { - let s = try!(String::column_result(stmt, col)); - serde_json::from_str(&s) - } - ffi::SQLITE_BLOB => { - let blob = try!(Vec::::column_result(stmt, col)); - serde_json::from_slice(&blob) - } + fn column_result(value: BorrowedValue) -> Result { + match value { + BorrowedValue::Text(ref s) => serde_json::from_str(s), + BorrowedValue::Blob(ref b) => serde_json::from_slice(b), _ => return Err(Error::InvalidColumnType), - }; - value_result.map_err(|err| Error::FromSqlConversionFailure(Box::new(err))) + }.map_err(|err| Error::FromSqlConversionFailure(Box::new(err))) } } diff --git a/src/types/time.rs b/src/types/time.rs index fe77956..ae524ec 100644 --- a/src/types/time.rs +++ b/src/types/time.rs @@ -2,7 +2,7 @@ extern crate time; use libc::c_int; use {Error, Result}; -use types::{FromSql, ToSql}; +use types::{FromSql, ToSql, BorrowedValue}; use ffi::sqlite3_stmt; @@ -16,16 +16,11 @@ impl ToSql for time::Timespec { } impl FromSql for time::Timespec { - unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result { - let s = try!(String::column_result(stmt, col)); - match time::strptime(&s, SQLITE_DATETIME_FMT) { + fn column_result(value: BorrowedValue) -> Result { + value.as_str().and_then(|s| match time::strptime(s, SQLITE_DATETIME_FMT) { Ok(tm) => Ok(tm.to_timespec()), Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))), - } - } - - unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool { - String::column_has_valid_sqlite_type(stmt, col) + }) } } From 734f18c9856637ac3e498ae242f2397496231cb4 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Tue, 24 May 2016 19:36:38 -0400 Subject: [PATCH 04/11] Impl `From` for converting Value <-> BorrowedValue --- src/types/borrowed_value.rs | 34 ++++++++++++++++++++++++---------- src/types/from_sql.rs | 2 +- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/types/borrowed_value.rs b/src/types/borrowed_value.rs index 8b97a33..cbbb977 100644 --- a/src/types/borrowed_value.rs +++ b/src/types/borrowed_value.rs @@ -11,16 +11,6 @@ pub enum BorrowedValue<'a> { } impl<'a> BorrowedValue<'a> { - pub fn to_value(&self) -> Value { - match *self { - BorrowedValue::Null => Value::Null, - BorrowedValue::Integer(i) => Value::Integer(i), - BorrowedValue::Real(r) => Value::Real(r), - BorrowedValue::Text(s) => Value::Text(s.to_string()), - BorrowedValue::Blob(b) => Value::Blob(b.to_vec()), - } - } - pub fn as_i64(&self) -> Result { match *self { BorrowedValue::Integer(i) => Ok(i), @@ -49,3 +39,27 @@ impl<'a> BorrowedValue<'a> { } } } + +impl<'a> From> for Value { + fn from(borrowed: BorrowedValue) -> Value { + match borrowed { + BorrowedValue::Null => Value::Null, + BorrowedValue::Integer(i) => Value::Integer(i), + BorrowedValue::Real(r) => Value::Real(r), + BorrowedValue::Text(s) => Value::Text(s.to_string()), + BorrowedValue::Blob(b) => Value::Blob(b.to_vec()), + } + } +} + +impl<'a> From<&'a Value> for BorrowedValue<'a> { + fn from(value: &'a Value) -> BorrowedValue<'a> { + match *value { + Value::Null => BorrowedValue::Null, + Value::Integer(i) => BorrowedValue::Integer(i), + Value::Real(r) => BorrowedValue::Real(r), + Value::Text(ref s) => BorrowedValue::Text(s), + Value::Blob(ref b) => BorrowedValue::Blob(b), + } + } +} diff --git a/src/types/from_sql.rs b/src/types/from_sql.rs index a996347..c733f07 100644 --- a/src/types/from_sql.rs +++ b/src/types/from_sql.rs @@ -55,6 +55,6 @@ impl FromSql for Option { impl FromSql for Value { fn column_result(value: BorrowedValue) -> Result { - Ok(value.to_value()) + Ok(value.into()) } } From 9d47d5109aeaad1c5d3c3fb940c48f0a90c66feb Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Tue, 24 May 2016 19:48:26 -0400 Subject: [PATCH 05/11] Add doc comments for BorrowedValue and friends. --- src/types/borrowed_value.rs | 18 ++++++++++++++++++ src/types/from_sql.rs | 1 + src/types/mod.rs | 20 +++++++++++--------- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/types/borrowed_value.rs b/src/types/borrowed_value.rs index cbbb977..f8e5068 100644 --- a/src/types/borrowed_value.rs +++ b/src/types/borrowed_value.rs @@ -2,15 +2,27 @@ use ::Result; use ::error::Error; use super::Value; +/// 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 BorrowedValue<'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 str), + /// The value is a blob of data Blob(&'a [u8]), } impl<'a> BorrowedValue<'a> { + /// If `self` is case `Integer`, returns the integral value. Otherwise, returns + /// `Err(Error::InvalidColumnType)`. pub fn as_i64(&self) -> Result { match *self { BorrowedValue::Integer(i) => Ok(i), @@ -18,6 +30,8 @@ impl<'a> BorrowedValue<'a> { } } + /// If `self` is case `Real`, returns the floating point value. Otherwise, returns + /// `Err(Error::InvalidColumnType)`. pub fn as_f64(&self) -> Result { match *self { BorrowedValue::Real(f) => Ok(f), @@ -25,6 +39,8 @@ impl<'a> BorrowedValue<'a> { } } + /// If `self` is case `Text`, returns the string value. Otherwise, returns + /// `Err(Error::InvalidColumnType)`. pub fn as_str(&self) -> Result<&str> { match *self { BorrowedValue::Text(ref t) => Ok(t), @@ -32,6 +48,8 @@ impl<'a> BorrowedValue<'a> { } } + /// If `self` is case `Blob`, returns the byte slice. Otherwise, returns + /// `Err(Error::InvalidColumnType)`. pub fn as_blob(&self) -> Result<&[u8]> { match *self { BorrowedValue::Blob(ref b) => Ok(b), diff --git a/src/types/from_sql.rs b/src/types/from_sql.rs index c733f07..cddf287 100644 --- a/src/types/from_sql.rs +++ b/src/types/from_sql.rs @@ -1,6 +1,7 @@ use super::{BorrowedValue, Value}; use ::Result; +/// A trait for types that can be created from a SQLite value. pub trait FromSql: Sized { fn column_result(value: BorrowedValue) -> Result; } diff --git a/src/types/mod.rs b/src/types/mod.rs index f8c8fba..d00693e 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -4,7 +4,9 @@ //! the `ToSql` and `FromSql` traits are provided for the basic types that SQLite provides methods //! for: //! -//! * C integers and doubles (`c_int` and `c_double`) +//! * 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` and `&[u8]`) //! @@ -15,12 +17,6 @@ //! truncates timespecs to the nearest second. If you want different storage for timespecs, you can //! use a newtype. For example, to store timespecs as doubles: //! -//! `ToSql` and `FromSql` are also implemented for `Option` where `T` implements `ToSql` or -//! `FromSql` for the cases where you want to know if a value was NULL (which gets translated to -//! `None`). If you get a value that was NULL in SQLite but you store it into a non-`Option` value -//! in Rust, you will get a "sensible" zero value - 0 for numeric types (including timespecs), an -//! empty string, or an empty vector of bytes. -//! //! ```rust,ignore //! extern crate rusqlite; //! extern crate libc; @@ -51,6 +47,10 @@ //! } //! } //! ``` +//! +//! `ToSql` and `FromSql` are also implemented for `Option` 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 ffi::sqlite3_stmt; pub use ffi::sqlite3_column_type; @@ -88,8 +88,10 @@ mod serde_json; #[derive(Copy,Clone)] pub struct Null; -/// Dynamic type value (http://sqlite.org/datatype3.html) -/// Value's type is dictated by SQLite (not by the caller). +/// Owning [dynamic type value](http://sqlite.org/datatype3.html). Value's type is typically +/// dictated by SQLite (not by the caller). +/// +/// See [`BorrowedValue`](enum.BorrowedValue.html) for a non-owning dynamic type value. #[derive(Clone,Debug,PartialEq)] pub enum Value { /// The value is a `NULL` value. From 4662b9b93278c612ca905bd0d108a75cf9b97987 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Tue, 24 May 2016 20:05:32 -0400 Subject: [PATCH 06/11] Allow FromSql:: to work on SQLite integer values. --- src/types/from_sql.rs | 7 ++++++- src/types/mod.rs | 3 +-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/types/from_sql.rs b/src/types/from_sql.rs index cddf287..ba35b73 100644 --- a/src/types/from_sql.rs +++ b/src/types/from_sql.rs @@ -1,5 +1,6 @@ use super::{BorrowedValue, Value}; use ::Result; +use ::error::Error; /// A trait for types that can be created from a SQLite value. pub trait FromSql: Sized { @@ -20,7 +21,11 @@ impl FromSql for i64 { impl FromSql for f64 { fn column_result(value: BorrowedValue) -> Result { - value.as_f64() + match value { + BorrowedValue::Integer(i) => Ok(i as f64), + BorrowedValue::Real(f) => Ok(f), + _ => Err(Error::InvalidColumnType), + } } } diff --git a/src/types/mod.rs b/src/types/mod.rs index d00693e..cfb8280 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -223,10 +223,9 @@ mod test { assert!(is_invalid_column_type(row.get_checked::>(1).err().unwrap())); // 2 is actually an integer - assert!(is_invalid_column_type(row.get_checked::(2).err().unwrap())); assert!(is_invalid_column_type(row.get_checked::(2).err().unwrap())); assert!(is_invalid_column_type(row.get_checked::>(2).err().unwrap())); - assert!(is_invalid_column_type(row.get_checked::>(2).err().unwrap())); + assert!(is_invalid_column_type(row.get_checked::>(2).err().unwrap())); // 3 is actually a float (c_double) assert!(is_invalid_column_type(row.get_checked::(3).err().unwrap())); From 0fbfad24525f4cbc11d3eb3154a11906d63ccd41 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Tue, 24 May 2016 20:08:12 -0400 Subject: [PATCH 07/11] Remove functions::FromValue. With the new definition of FromSql, we can reuse it since we can convert a sqlite3_value into a BorrowedValue. --- src/functions.rs | 134 ++++++++++------------------------------------- 1 file changed, 29 insertions(+), 105 deletions(-) diff --git a/src/functions.rs b/src/functions.rs index 8c8723e..b261fce 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -54,7 +54,6 @@ use std::ffi::CStr; use std::mem; use std::ptr; use std::slice; -use std::str; use libc::{c_int, c_double, c_char, c_void}; use ffi; @@ -63,7 +62,7 @@ pub use ffi::sqlite3_value; pub use ffi::sqlite3_value_type; pub use ffi::sqlite3_value_numeric_type; -use types::Null; +use types::{Null, FromSql, BorrowedValue}; use {Result, Error, Connection, str_to_cstring, InnerConnection}; @@ -157,108 +156,35 @@ impl ToResult for Null { } } -/// A trait for types that can be created from a SQLite function parameter value. -pub trait FromValue: Sized { - unsafe fn parameter_value(v: *mut sqlite3_value) -> Result; - - /// FromValue types can implement this method and use sqlite3_value_type to check that - /// the type reported by SQLite matches a type suitable for Self. This method is used - /// by `Context::get` to confirm that the parameter contains a valid type before - /// attempting to retrieve the value. - unsafe fn parameter_has_valid_sqlite_type(_: *mut sqlite3_value) -> bool { - true - } -} - - -macro_rules! raw_from_impl( - ($t:ty, $f:ident, $c:expr) => ( - impl FromValue for $t { - unsafe fn parameter_value(v: *mut sqlite3_value) -> Result<$t> { - Ok(ffi::$f(v)) - } - - unsafe fn parameter_has_valid_sqlite_type(v: *mut sqlite3_value) -> bool { - sqlite3_value_numeric_type(v) == $c - } - } - ) -); - -raw_from_impl!(c_int, sqlite3_value_int, ffi::SQLITE_INTEGER); -raw_from_impl!(i64, sqlite3_value_int64, ffi::SQLITE_INTEGER); - -impl FromValue for bool { - unsafe fn parameter_value(v: *mut sqlite3_value) -> Result { - match ffi::sqlite3_value_int(v) { - 0 => Ok(false), - _ => Ok(true), - } - } - - unsafe fn parameter_has_valid_sqlite_type(v: *mut sqlite3_value) -> bool { - sqlite3_value_numeric_type(v) == ffi::SQLITE_INTEGER - } -} - -impl FromValue for c_double { - unsafe fn parameter_value(v: *mut sqlite3_value) -> Result { - Ok(ffi::sqlite3_value_double(v)) - } - - unsafe fn parameter_has_valid_sqlite_type(v: *mut sqlite3_value) -> bool { - sqlite3_value_numeric_type(v) == ffi::SQLITE_FLOAT || - sqlite3_value_numeric_type(v) == ffi::SQLITE_INTEGER - } -} - -impl FromValue for String { - unsafe fn parameter_value(v: *mut sqlite3_value) -> Result { - let c_text = ffi::sqlite3_value_text(v); - if c_text.is_null() { - Ok("".to_owned()) - } else { - let c_slice = CStr::from_ptr(c_text as *const c_char).to_bytes(); - let utf8_str = try!(str::from_utf8(c_slice)); - Ok(utf8_str.into()) - } - } - - unsafe fn parameter_has_valid_sqlite_type(v: *mut sqlite3_value) -> bool { - sqlite3_value_type(v) == ffi::SQLITE_TEXT - } -} - -impl FromValue for Vec { - unsafe fn parameter_value(v: *mut sqlite3_value) -> Result> { +impl<'a> BorrowedValue<'a> { + unsafe fn from_value(value: *mut sqlite3_value) -> BorrowedValue<'a> { use std::slice::from_raw_parts; - let c_blob = ffi::sqlite3_value_blob(v); - let len = ffi::sqlite3_value_bytes(v); - assert!(len >= 0, - "unexpected negative return from sqlite3_value_bytes"); - let len = len as usize; + match ffi::sqlite3_value_type(value) { + ffi::SQLITE_NULL => BorrowedValue::Null, + ffi::SQLITE_INTEGER => BorrowedValue::Integer(ffi::sqlite3_value_int64(value)), + ffi::SQLITE_FLOAT => BorrowedValue::Real(ffi::sqlite3_value_double(value)), + ffi::SQLITE_TEXT => { + let text = ffi::sqlite3_value_text(value); + assert!(!text.is_null(), "unexpected SQLITE_TEXT value type with NULL data"); + let s = CStr::from_ptr(text as *const c_char); - Ok(from_raw_parts(mem::transmute(c_blob), len).to_vec()) - } + // sqlite3_value_text returns UTF8 data, so our unwrap here should be fine. + let s = s.to_str().expect("sqlite3_value_text returned invalid UTF-8"); + BorrowedValue::Text(s) + } + ffi::SQLITE_BLOB => { + let blob = ffi::sqlite3_value_blob(value); + assert!(!blob.is_null(), "unexpected SQLITE_BLOB value type with NULL data"); - unsafe fn parameter_has_valid_sqlite_type(v: *mut sqlite3_value) -> bool { - sqlite3_value_type(v) == ffi::SQLITE_BLOB - } -} + let len = ffi::sqlite3_value_bytes(value); + assert!(len >= 0, "unexpected negative return from sqlite3_value_bytes"); -impl FromValue for Option { - unsafe fn parameter_value(v: *mut sqlite3_value) -> Result> { - if sqlite3_value_type(v) == ffi::SQLITE_NULL { - Ok(None) - } else { - FromValue::parameter_value(v).map(Some) + BorrowedValue::Blob(from_raw_parts(blob as *const u8, len as usize)) + } + _ => unreachable!("sqlite3_value_type returned invalid value") } } - - unsafe fn parameter_has_valid_sqlite_type(v: *mut sqlite3_value) -> bool { - sqlite3_value_type(v) == ffi::SQLITE_NULL || T::parameter_has_valid_sqlite_type(v) - } } unsafe extern "C" fn free_boxed_value(p: *mut c_void) { @@ -288,15 +214,13 @@ impl<'a> Context<'a> { /// Will panic if `idx` is greater than or equal to `self.len()`. /// /// Will return Err if the underlying SQLite type cannot be converted to a `T`. - pub fn get(&self, idx: usize) -> Result { + pub fn get(&self, idx: usize) -> Result { let arg = self.args[idx]; - unsafe { - if T::parameter_has_valid_sqlite_type(arg) { - T::parameter_value(arg) - } else { - Err(Error::InvalidFunctionParameterType) - } - } + let value = unsafe { BorrowedValue::from_value(arg) }; + FromSql::column_result(value).map_err(|err| match err { + Error::InvalidColumnType => Error::InvalidFunctionParameterType, + _ => err + }) } /// Sets the auxilliary data associated with a particular parameter. See From d9df23ca90064a474d40be61992c62a93327a9e0 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Tue, 24 May 2016 20:12:29 -0400 Subject: [PATCH 08/11] Fix doc comment example of FromSql implementation. --- src/types/mod.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/types/mod.rs b/src/types/mod.rs index cfb8280..03bb75a 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -15,7 +15,7 @@ //! `"%Y-%m-%d %H:%M:%S"`, as SQLite's builtin //! [datetime](https://www.sqlite.org/lang_datefunc.html) function. Note that this storage //! truncates timespecs to the nearest second. If you want different storage for timespecs, you can -//! use a newtype. For example, to store timespecs as doubles: +//! use a newtype. For example, to store timespecs as `f64`s: //! //! ```rust,ignore //! extern crate rusqlite; @@ -29,10 +29,8 @@ //! pub struct TimespecSql(pub time::Timespec); //! //! impl FromSql for TimespecSql { -//! unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -//! -> Result { -//! let as_f64_result = FromSql::column_result(stmt, col); -//! as_f64_result.map(|as_f64: f64| { +//! fn column_result(value: BorrowedValue) -> Result { +//! f64::column_result(value).map(|as_f64| { //! TimespecSql(time::Timespec{ sec: as_f64.trunc() as i64, //! nsec: (as_f64.fract() * 1.0e9) as i32 }) //! }) From 308789495c1497397e2b6cf6469cbbb6daa69a9a Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Tue, 24 May 2016 20:16:13 -0400 Subject: [PATCH 09/11] Add FromSql breaking change note to Changelog. --- Changelog.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Changelog.md b/Changelog.md index 358d93f..233a58b 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,9 @@ +# Version UPCOMING (...) + +* BREAKING CHANGE: The `FromSql` trait has been redesigned. It now requires a single, safe + method instead of the previous definition which required implementing one or two unsafe + methods. + # Version 0.7.2 (2016-05-19) * BREAKING CHANGE: `Rows` no longer implements `Iterator`. It still has a `next()` method, but From 2f4990dafd18816fc99182f37768683471e32dcd Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Tue, 24 May 2016 21:34:18 -0400 Subject: [PATCH 10/11] Rename BorrowedValue -> ValueRef. --- src/functions.rs | 18 ++++----- src/lib.rs | 18 ++++----- src/types/chrono.rs | 12 +++--- src/types/from_sql.rs | 26 ++++++------ src/types/mod.rs | 8 ++-- src/types/serde_json.rs | 8 ++-- src/types/time.rs | 4 +- src/types/{borrowed_value.rs => value_ref.rs} | 40 +++++++++---------- 8 files changed, 67 insertions(+), 67 deletions(-) rename src/types/{borrowed_value.rs => value_ref.rs} (62%) diff --git a/src/functions.rs b/src/functions.rs index b261fce..32e4d1e 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -62,7 +62,7 @@ pub use ffi::sqlite3_value; pub use ffi::sqlite3_value_type; pub use ffi::sqlite3_value_numeric_type; -use types::{Null, FromSql, BorrowedValue}; +use types::{Null, FromSql, ValueRef}; use {Result, Error, Connection, str_to_cstring, InnerConnection}; @@ -156,14 +156,14 @@ impl ToResult for Null { } } -impl<'a> BorrowedValue<'a> { - unsafe fn from_value(value: *mut sqlite3_value) -> BorrowedValue<'a> { +impl<'a> ValueRef<'a> { + unsafe fn from_value(value: *mut sqlite3_value) -> ValueRef<'a> { use std::slice::from_raw_parts; match ffi::sqlite3_value_type(value) { - ffi::SQLITE_NULL => BorrowedValue::Null, - ffi::SQLITE_INTEGER => BorrowedValue::Integer(ffi::sqlite3_value_int64(value)), - ffi::SQLITE_FLOAT => BorrowedValue::Real(ffi::sqlite3_value_double(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); assert!(!text.is_null(), "unexpected SQLITE_TEXT value type with NULL data"); @@ -171,7 +171,7 @@ impl<'a> BorrowedValue<'a> { // sqlite3_value_text returns UTF8 data, so our unwrap here should be fine. let s = s.to_str().expect("sqlite3_value_text returned invalid UTF-8"); - BorrowedValue::Text(s) + ValueRef::Text(s) } ffi::SQLITE_BLOB => { let blob = ffi::sqlite3_value_blob(value); @@ -180,7 +180,7 @@ impl<'a> BorrowedValue<'a> { let len = ffi::sqlite3_value_bytes(value); assert!(len >= 0, "unexpected negative return from sqlite3_value_bytes"); - BorrowedValue::Blob(from_raw_parts(blob as *const u8, len as usize)) + ValueRef::Blob(from_raw_parts(blob as *const u8, len as usize)) } _ => unreachable!("sqlite3_value_type returned invalid value") } @@ -216,7 +216,7 @@ impl<'a> Context<'a> { /// Will return Err if the underlying SQLite type cannot be converted to a `T`. pub fn get(&self, idx: usize) -> Result { let arg = self.args[idx]; - let value = unsafe { BorrowedValue::from_value(arg) }; + let value = unsafe { ValueRef::from_value(arg) }; FromSql::column_result(value).map_err(|err| match err { Error::InvalidColumnType => Error::InvalidFunctionParameterType, _ => err diff --git a/src/lib.rs b/src/lib.rs index 909ba1f..8d84890 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,7 +75,7 @@ use std::result; use std::str; use libc::{c_int, c_char}; -use types::{ToSql, FromSql, BorrowedValue}; +use types::{ToSql, FromSql, ValueRef}; use error::{error_from_sqlite_code, error_from_handle}; use raw_statement::RawStatement; use cache::StatementCache; @@ -1065,7 +1065,7 @@ impl<'a, 'stmt> Row<'a, 'stmt> { /// for this row. pub fn get_checked(&self, idx: I) -> Result { let idx = try!(idx.idx(self.stmt)); - let value = unsafe { BorrowedValue::new(&self.stmt.stmt, idx) }; + let value = unsafe { ValueRef::new(&self.stmt.stmt, idx) }; FromSql::column_result(value) } @@ -1100,16 +1100,16 @@ impl<'a> RowIndex for &'a str { } } -impl<'a> BorrowedValue<'a> { - unsafe fn new(stmt: &RawStatement, col: c_int) -> BorrowedValue { +impl<'a> ValueRef<'a> { + unsafe fn new(stmt: &RawStatement, col: c_int) -> ValueRef { use std::slice::from_raw_parts; let raw = stmt.ptr(); match stmt.column_type(col) { - ffi::SQLITE_NULL => BorrowedValue::Null, - ffi::SQLITE_INTEGER => BorrowedValue::Integer(ffi::sqlite3_column_int64(raw, col)), - ffi::SQLITE_FLOAT => BorrowedValue::Real(ffi::sqlite3_column_double(raw, col)), + ffi::SQLITE_NULL => ValueRef::Null, + ffi::SQLITE_INTEGER => ValueRef::Integer(ffi::sqlite3_column_int64(raw, col)), + ffi::SQLITE_FLOAT => ValueRef::Real(ffi::sqlite3_column_double(raw, col)), ffi::SQLITE_TEXT => { let text = ffi::sqlite3_column_text(raw, col); assert!(!text.is_null(), "unexpected SQLITE_TEXT column type with NULL data"); @@ -1117,7 +1117,7 @@ impl<'a> BorrowedValue<'a> { // sqlite3_column_text returns UTF8 data, so our unwrap here should be fine. let s = s.to_str().expect("sqlite3_column_text returned invalid UTF-8"); - BorrowedValue::Text(s) + ValueRef::Text(s) } ffi::SQLITE_BLOB => { let blob = ffi::sqlite3_column_blob(raw, col); @@ -1126,7 +1126,7 @@ impl<'a> BorrowedValue<'a> { let len = ffi::sqlite3_column_bytes(raw, col); assert!(len >= 0, "unexpected negative return from sqlite3_column_bytes"); - BorrowedValue::Blob(from_raw_parts(blob as *const u8, len as usize)) + ValueRef::Blob(from_raw_parts(blob as *const u8, len as usize)) } _ => unreachable!("sqlite3_column_type returned invalid value") } diff --git a/src/types/chrono.rs b/src/types/chrono.rs index 7ecb28f..3d17dd6 100644 --- a/src/types/chrono.rs +++ b/src/types/chrono.rs @@ -7,7 +7,7 @@ use self::chrono::{NaiveDate, NaiveTime, NaiveDateTime, DateTime, TimeZone, UTC, use libc::c_int; use {Error, Result}; -use types::{FromSql, ToSql, BorrowedValue}; +use types::{FromSql, ToSql, ValueRef}; use ffi::sqlite3_stmt; @@ -21,7 +21,7 @@ impl ToSql for NaiveDate { /// "YYYY-MM-DD" => ISO 8601 calendar date without timezone. impl FromSql for NaiveDate { - fn column_result(value: BorrowedValue) -> Result { + fn column_result(value: ValueRef) -> Result { value.as_str().and_then(|s| match NaiveDate::parse_from_str(s, "%Y-%m-%d") { Ok(dt) => Ok(dt), Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))), @@ -39,7 +39,7 @@ impl ToSql for NaiveTime { /// "HH:MM"/"HH:MM:SS"/"HH:MM:SS.SSS" => ISO 8601 time without timezone. impl FromSql for NaiveTime { - fn column_result(value: BorrowedValue) -> Result { + fn column_result(value: ValueRef) -> Result { value.as_str().and_then(|s| { let fmt = match s.len() { 5 => "%H:%M", @@ -65,7 +65,7 @@ impl ToSql for NaiveDateTime { /// "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: BorrowedValue) -> Result { + fn column_result(value: ValueRef) -> Result { 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" @@ -91,7 +91,7 @@ impl ToSql for DateTime { /// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into DateTime. impl FromSql for DateTime { - fn column_result(value: BorrowedValue) -> Result { + fn column_result(value: ValueRef) -> Result { { // Try to parse value as rfc3339 first. let s = try!(value.as_str()); @@ -121,7 +121,7 @@ impl FromSql for DateTime { /// RFC3339 ("YYYY-MM-DDTHH:MM:SS.SSS[+-]HH:MM") into DateTime. impl FromSql for DateTime { - fn column_result(value: BorrowedValue) -> Result { + fn column_result(value: ValueRef) -> Result { let utc_dt = try!(DateTime::::column_result(value)); Ok(utc_dt.with_timezone(&Local)) } diff --git a/src/types/from_sql.rs b/src/types/from_sql.rs index ba35b73..28df415 100644 --- a/src/types/from_sql.rs +++ b/src/types/from_sql.rs @@ -1,36 +1,36 @@ -use super::{BorrowedValue, Value}; +use super::{ValueRef, Value}; use ::Result; use ::error::Error; /// A trait for types that can be created from a SQLite value. pub trait FromSql: Sized { - fn column_result(value: BorrowedValue) -> Result; + fn column_result(value: ValueRef) -> Result; } impl FromSql for i32 { - fn column_result(value: BorrowedValue) -> Result { + fn column_result(value: ValueRef) -> Result { i64::column_result(value).map(|i| i as i32) } } impl FromSql for i64 { - fn column_result(value: BorrowedValue) -> Result { + fn column_result(value: ValueRef) -> Result { value.as_i64() } } impl FromSql for f64 { - fn column_result(value: BorrowedValue) -> Result { + fn column_result(value: ValueRef) -> Result { match value { - BorrowedValue::Integer(i) => Ok(i as f64), - BorrowedValue::Real(f) => Ok(f), + ValueRef::Integer(i) => Ok(i as f64), + ValueRef::Real(f) => Ok(f), _ => Err(Error::InvalidColumnType), } } } impl FromSql for bool { - fn column_result(value: BorrowedValue) -> Result { + fn column_result(value: ValueRef) -> Result { i64::column_result(value).map(|i| match i { 0 => false, _ => true, @@ -39,28 +39,28 @@ impl FromSql for bool { } impl FromSql for String { - fn column_result(value: BorrowedValue) -> Result { + fn column_result(value: ValueRef) -> Result { value.as_str().map(|s| s.to_string()) } } impl FromSql for Vec { - fn column_result(value: BorrowedValue) -> Result { + fn column_result(value: ValueRef) -> Result { value.as_blob().map(|b| b.to_vec()) } } impl FromSql for Option { - fn column_result(value: BorrowedValue) -> Result { + fn column_result(value: ValueRef) -> Result { match value { - BorrowedValue::Null => Ok(None), + ValueRef::Null => Ok(None), _ => FromSql::column_result(value).map(Some), } } } impl FromSql for Value { - fn column_result(value: BorrowedValue) -> Result { + fn column_result(value: ValueRef) -> Result { Ok(value.into()) } } diff --git a/src/types/mod.rs b/src/types/mod.rs index 03bb75a..7853291 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -29,7 +29,7 @@ //! pub struct TimespecSql(pub time::Timespec); //! //! impl FromSql for TimespecSql { -//! fn column_result(value: BorrowedValue) -> Result { +//! fn column_result(value: ValueRef) -> Result { //! f64::column_result(value).map(|as_f64| { //! TimespecSql(time::Timespec{ sec: as_f64.trunc() as i64, //! nsec: (as_f64.fract() * 1.0e9) as i32 }) @@ -56,9 +56,9 @@ pub use ffi::{SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB, SQLITE_NUL pub use self::from_sql::FromSql; pub use self::to_sql::ToSql; -pub use self::borrowed_value::BorrowedValue; +pub use self::value_ref::ValueRef; -mod borrowed_value; +mod value_ref; mod from_sql; mod to_sql; mod time; @@ -89,7 +89,7 @@ pub struct Null; /// Owning [dynamic type value](http://sqlite.org/datatype3.html). Value's type is typically /// dictated by SQLite (not by the caller). /// -/// See [`BorrowedValue`](enum.BorrowedValue.html) for a non-owning dynamic type value. +/// 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. diff --git a/src/types/serde_json.rs b/src/types/serde_json.rs index a3da2fc..937f914 100644 --- a/src/types/serde_json.rs +++ b/src/types/serde_json.rs @@ -5,7 +5,7 @@ use libc::c_int; use self::serde_json::Value; use {Error, Result}; -use types::{FromSql, ToSql, BorrowedValue}; +use types::{FromSql, ToSql, ValueRef}; use ffi::sqlite3_stmt; @@ -19,10 +19,10 @@ impl ToSql for Value { /// Deserialize text/blob to JSON `Value`. impl FromSql for Value { - fn column_result(value: BorrowedValue) -> Result { + fn column_result(value: ValueRef) -> Result { match value { - BorrowedValue::Text(ref s) => serde_json::from_str(s), - BorrowedValue::Blob(ref b) => serde_json::from_slice(b), + ValueRef::Text(ref s) => serde_json::from_str(s), + ValueRef::Blob(ref b) => serde_json::from_slice(b), _ => return Err(Error::InvalidColumnType), }.map_err(|err| Error::FromSqlConversionFailure(Box::new(err))) } diff --git a/src/types/time.rs b/src/types/time.rs index ae524ec..1f1a818 100644 --- a/src/types/time.rs +++ b/src/types/time.rs @@ -2,7 +2,7 @@ extern crate time; use libc::c_int; use {Error, Result}; -use types::{FromSql, ToSql, BorrowedValue}; +use types::{FromSql, ToSql, ValueRef}; use ffi::sqlite3_stmt; @@ -16,7 +16,7 @@ impl ToSql for time::Timespec { } impl FromSql for time::Timespec { - fn column_result(value: BorrowedValue) -> Result { + fn column_result(value: ValueRef) -> Result { value.as_str().and_then(|s| match time::strptime(s, SQLITE_DATETIME_FMT) { Ok(tm) => Ok(tm.to_timespec()), Err(err) => Err(Error::FromSqlConversionFailure(Box::new(err))), diff --git a/src/types/borrowed_value.rs b/src/types/value_ref.rs similarity index 62% rename from src/types/borrowed_value.rs rename to src/types/value_ref.rs index f8e5068..aa2d3b8 100644 --- a/src/types/borrowed_value.rs +++ b/src/types/value_ref.rs @@ -7,7 +7,7 @@ use super::Value; /// /// See [`Value`](enum.Value.html) for an owning dynamic type value. #[derive(Copy,Clone,Debug,PartialEq)] -pub enum BorrowedValue<'a> { +pub enum ValueRef<'a> { /// The value is a `NULL` value. Null, /// The value is a signed integer. @@ -20,12 +20,12 @@ pub enum BorrowedValue<'a> { Blob(&'a [u8]), } -impl<'a> BorrowedValue<'a> { +impl<'a> ValueRef<'a> { /// If `self` is case `Integer`, returns the integral value. Otherwise, returns /// `Err(Error::InvalidColumnType)`. pub fn as_i64(&self) -> Result { match *self { - BorrowedValue::Integer(i) => Ok(i), + ValueRef::Integer(i) => Ok(i), _ => Err(Error::InvalidColumnType), } } @@ -34,7 +34,7 @@ impl<'a> BorrowedValue<'a> { /// `Err(Error::InvalidColumnType)`. pub fn as_f64(&self) -> Result { match *self { - BorrowedValue::Real(f) => Ok(f), + ValueRef::Real(f) => Ok(f), _ => Err(Error::InvalidColumnType), } } @@ -43,7 +43,7 @@ impl<'a> BorrowedValue<'a> { /// `Err(Error::InvalidColumnType)`. pub fn as_str(&self) -> Result<&str> { match *self { - BorrowedValue::Text(ref t) => Ok(t), + ValueRef::Text(ref t) => Ok(t), _ => Err(Error::InvalidColumnType), } } @@ -52,32 +52,32 @@ impl<'a> BorrowedValue<'a> { /// `Err(Error::InvalidColumnType)`. pub fn as_blob(&self) -> Result<&[u8]> { match *self { - BorrowedValue::Blob(ref b) => Ok(b), + ValueRef::Blob(ref b) => Ok(b), _ => Err(Error::InvalidColumnType), } } } -impl<'a> From> for Value { - fn from(borrowed: BorrowedValue) -> Value { +impl<'a> From> for Value { + fn from(borrowed: ValueRef) -> Value { match borrowed { - BorrowedValue::Null => Value::Null, - BorrowedValue::Integer(i) => Value::Integer(i), - BorrowedValue::Real(r) => Value::Real(r), - BorrowedValue::Text(s) => Value::Text(s.to_string()), - BorrowedValue::Blob(b) => Value::Blob(b.to_vec()), + ValueRef::Null => Value::Null, + ValueRef::Integer(i) => Value::Integer(i), + ValueRef::Real(r) => Value::Real(r), + ValueRef::Text(s) => Value::Text(s.to_string()), + ValueRef::Blob(b) => Value::Blob(b.to_vec()), } } } -impl<'a> From<&'a Value> for BorrowedValue<'a> { - fn from(value: &'a Value) -> BorrowedValue<'a> { +impl<'a> From<&'a Value> for ValueRef<'a> { + fn from(value: &'a Value) -> ValueRef<'a> { match *value { - Value::Null => BorrowedValue::Null, - Value::Integer(i) => BorrowedValue::Integer(i), - Value::Real(r) => BorrowedValue::Real(r), - Value::Text(ref s) => BorrowedValue::Text(s), - Value::Blob(ref b) => BorrowedValue::Blob(b), + Value::Null => ValueRef::Null, + Value::Integer(i) => ValueRef::Integer(i), + Value::Real(r) => ValueRef::Real(r), + Value::Text(ref s) => ValueRef::Text(s), + Value::Blob(ref b) => ValueRef::Blob(b), } } } From 8adb96131b6c2f81442066b3cbf1f855230c4082 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Wed, 25 May 2016 19:53:18 -0400 Subject: [PATCH 11/11] Remove now-unnecessary FFI re-exports. --- src/types/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/types/mod.rs b/src/types/mod.rs index 7853291..758eb5d 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -51,8 +51,6 @@ //! `None`). pub use ffi::sqlite3_stmt; -pub use ffi::sqlite3_column_type; -pub use ffi::{SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB, SQLITE_NULL}; pub use self::from_sql::FromSql; pub use self::to_sql::ToSql;