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 diff --git a/src/functions.rs b/src/functions.rs index 8c8723e..32e4d1e 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, ValueRef}; 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> ValueRef<'a> { + unsafe fn from_value(value: *mut sqlite3_value) -> ValueRef<'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 => 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"); + 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"); + ValueRef::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) + ValueRef::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 { ValueRef::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 diff --git a/src/lib.rs b/src/lib.rs index e3894cd..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}; +use types::{ToSql, FromSql, ValueRef}; 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 { ValueRef::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> 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 => 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"); + 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"); + ValueRef::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"); + + ValueRef::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/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)) } } diff --git a/src/types/chrono.rs b/src/types/chrono.rs index a9e0bea..3d17dd6 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, ValueRef}; 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: 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))), - } - } - - 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: ValueRef) -> 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: 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" + } 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: ValueRef) -> 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: ValueRef) -> 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..28df415 100644 --- a/src/types/from_sql.rs +++ b/src/types/from_sql.rs @@ -1,119 +1,66 @@ -use std::ffi::CStr; -use std::mem; -use std::str; - -use libc::{c_char, c_double, c_int}; - -use super::Value; -use ffi::{sqlite3_stmt, sqlite3_column_type}; -use ::{ffi, Result}; +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 { - unsafe fn column_result(stmt: *mut sqlite3_stmt, col: c_int) -> Result; + fn column_result(value: ValueRef) -> 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: ValueRef) -> 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)) - } - - 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 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 +impl FromSql for i64 { + fn column_result(value: ValueRef) -> Result { + value.as_i64() } } -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 - } -} - -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 - } -} - -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) - } - } - - 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), +impl FromSql for f64 { + fn column_result(value: ValueRef) -> Result { + match value { + ValueRef::Integer(i) => Ok(i as f64), + ValueRef::Real(f) => Ok(f), _ => Err(Error::InvalidColumnType), } } } + +impl FromSql for bool { + fn column_result(value: ValueRef) -> Result { + i64::column_result(value).map(|i| match i { + 0 => false, + _ => true, + }) + } +} + +impl FromSql for String { + fn column_result(value: ValueRef) -> Result { + value.as_str().map(|s| s.to_string()) + } +} + +impl FromSql for Vec { + fn column_result(value: ValueRef) -> Result { + value.as_blob().map(|b| b.to_vec()) + } +} + +impl FromSql for Option { + fn column_result(value: ValueRef) -> Result { + match value { + ValueRef::Null => Ok(None), + _ => FromSql::column_result(value).map(Some), + } + } +} + +impl FromSql for Value { + fn column_result(value: ValueRef) -> Result { + Ok(value.into()) + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs index 6110d25..758eb5d 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]`) //! @@ -13,13 +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: -//! -//! `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. +//! use a newtype. For example, to store timespecs as `f64`s: //! //! ```rust,ignore //! extern crate rusqlite; @@ -33,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: 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 }) //! }) @@ -51,14 +45,18 @@ //! } //! } //! ``` +//! +//! `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; -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; +pub use self::value_ref::ValueRef; +mod value_ref; mod from_sql; mod to_sql; mod time; @@ -86,8 +84,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 [`ValueRef`](enum.ValueRef.html) for a non-owning dynamic type value. #[derive(Clone,Debug,PartialEq)] pub enum Value { /// The value is a `NULL` value. @@ -219,10 +219,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())); diff --git a/src/types/serde_json.rs b/src/types/serde_json.rs index dc1eeff..937f914 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, ValueRef}; -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: ValueRef) -> Result { + match value { + ValueRef::Text(ref s) => serde_json::from_str(s), + ValueRef::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..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}; +use types::{FromSql, ToSql, ValueRef}; 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: 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))), - } - } - - unsafe fn column_has_valid_sqlite_type(stmt: *mut sqlite3_stmt, col: c_int) -> bool { - String::column_has_valid_sqlite_type(stmt, col) + }) } } diff --git a/src/types/value_ref.rs b/src/types/value_ref.rs new file mode 100644 index 0000000..aa2d3b8 --- /dev/null +++ b/src/types/value_ref.rs @@ -0,0 +1,83 @@ +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 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 str), + /// The value is a blob of data + Blob(&'a [u8]), +} + +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 { + ValueRef::Integer(i) => Ok(i), + _ => Err(Error::InvalidColumnType), + } + } + + /// If `self` is case `Real`, returns the floating point value. Otherwise, returns + /// `Err(Error::InvalidColumnType)`. + pub fn as_f64(&self) -> Result { + match *self { + ValueRef::Real(f) => Ok(f), + _ => Err(Error::InvalidColumnType), + } + } + + /// If `self` is case `Text`, returns the string value. Otherwise, returns + /// `Err(Error::InvalidColumnType)`. + pub fn as_str(&self) -> Result<&str> { + match *self { + ValueRef::Text(ref t) => Ok(t), + _ => Err(Error::InvalidColumnType), + } + } + + /// If `self` is case `Blob`, returns the byte slice. Otherwise, returns + /// `Err(Error::InvalidColumnType)`. + pub fn as_blob(&self) -> Result<&[u8]> { + match *self { + ValueRef::Blob(ref b) => Ok(b), + _ => Err(Error::InvalidColumnType), + } + } +} + +impl<'a> From> 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) => Value::Text(s.to_string()), + ValueRef::Blob(b) => Value::Blob(b.to_vec()), + } + } +} + +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), + Value::Blob(ref b) => ValueRef::Blob(b), + } + } +}