diff --git a/src/lib.rs b/src/lib.rs index f646558..e3894cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -882,7 +882,12 @@ impl<'conn> Statement<'conn> { for (i, p) in params.iter().enumerate() { try!(unsafe { - self.conn.decode_result(p.bind_parameter(self.stmt.ptr(), (i + 1) as c_int)) + self.conn.decode_result( + // This should be + // `p.bind_parameter(self.stmt.ptr(), (i + 1) as c_int)` + // but that doesn't compile until Rust 1.9 due to a compiler bug. + ToSql::bind_parameter(*p, self.stmt.ptr(), (i + 1) as c_int) + ) }); } diff --git a/src/named_params.rs b/src/named_params.rs index d89cc6e..d864736 100644 --- a/src/named_params.rs +++ b/src/named_params.rs @@ -204,7 +204,12 @@ impl<'conn> Statement<'conn> { fn bind_parameters_named(&mut self, params: &[(&str, &ToSql)]) -> Result<()> { for &(name, value) in params { if let Some(i) = try!(self.parameter_index(name)) { - try!(self.conn.decode_result(unsafe { value.bind_parameter(self.stmt.ptr(), i) })); + try!(self.conn.decode_result(unsafe { + // This should be + // `value.bind_parameter(self.stmt.ptr(), i)` + // but that doesn't compile until Rust 1.9 due to a compiler bug. + ToSql::bind_parameter(value, self.stmt.ptr(), i) + })); } else { return Err(Error::InvalidParameterName(name.into())); } diff --git a/src/types/from_sql.rs b/src/types/from_sql.rs new file mode 100644 index 0000000..a9fc3f6 --- /dev/null +++ b/src/types/from_sql.rs @@ -0,0 +1,119 @@ +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 ::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; + + /// 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 + } +} + +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 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), + _ => Err(Error::InvalidColumnType), + } + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs index ba953a1..6110d25 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -52,119 +52,21 @@ //! } //! ``` -use libc::{c_int, c_double, c_char}; -use std::ffi::CStr; -use std::mem; -use std::str; -use super::ffi; -use super::{Result, Error, str_to_cstring}; - 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; + +mod from_sql; +mod to_sql; mod time; #[cfg(feature = "chrono")] mod chrono; #[cfg(feature = "serde_json")] mod serde_json; -/// A trait for types that can be converted into SQLite values. -pub trait ToSql { - unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int; -} - -/// 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; - - /// 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 - } -} - -macro_rules! raw_to_impl( - ($t:ty, $f:ident) => ( - impl ToSql for $t { - unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { - ffi::$f(stmt, col, *self) - } - } - ) -); - -raw_to_impl!(c_int, sqlite3_bind_int); // i32 -raw_to_impl!(i64, sqlite3_bind_int64); -raw_to_impl!(c_double, sqlite3_bind_double); - -impl ToSql for bool { - unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { - if *self { - ffi::sqlite3_bind_int(stmt, col, 1) - } else { - ffi::sqlite3_bind_int(stmt, col, 0) - } - } -} - -impl<'a> ToSql for &'a str { - unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { - let length = self.len(); - if length > ::std::i32::MAX as usize { - return ffi::SQLITE_TOOBIG; - } - match str_to_cstring(self) { - Ok(c_str) => { - ffi::sqlite3_bind_text(stmt, - col, - c_str.as_ptr(), - length as c_int, - ffi::SQLITE_TRANSIENT()) - } - Err(_) => ffi::SQLITE_MISUSE, - } - } -} - -impl ToSql for String { - unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { - (&self[..]).bind_parameter(stmt, col) - } -} - -impl<'a> ToSql for &'a [u8] { - unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { - if self.len() > ::std::i32::MAX as usize { - return ffi::SQLITE_TOOBIG; - } - ffi::sqlite3_bind_blob(stmt, - col, - mem::transmute(self.as_ptr()), - self.len() as c_int, - ffi::SQLITE_TRANSIENT()) - } -} - -impl ToSql for Vec { - unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { - (&self[..]).bind_parameter(stmt, col) - } -} - -impl ToSql for Option { - unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { - match *self { - None => ffi::sqlite3_bind_null(stmt, col), - Some(ref t) => t.bind_parameter(stmt, col), - } - } -} - /// Empty struct that can be used to fill in a query parameter as `NULL`. /// /// ## Example @@ -184,95 +86,6 @@ impl ToSql for Option { #[derive(Copy,Clone)] pub struct Null; -impl ToSql for Null { - unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { - ffi::sqlite3_bind_null(stmt, col) - } -} - -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 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) - } -} - /// Dynamic type value (http://sqlite.org/datatype3.html) /// Value's type is dictated by SQLite (not by the caller). #[derive(Clone,Debug,PartialEq)] @@ -289,19 +102,6 @@ pub enum Value { Blob(Vec), } -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), - } - } -} - #[cfg(test)] #[cfg_attr(feature="clippy", allow(similar_names))] mod test { diff --git a/src/types/to_sql.rs b/src/types/to_sql.rs new file mode 100644 index 0000000..99293b5 --- /dev/null +++ b/src/types/to_sql.rs @@ -0,0 +1,95 @@ +use std::mem; + +use libc::{c_double, c_int}; + +use super::Null; +use ::{ffi, str_to_cstring}; +use ffi::sqlite3_stmt; + +/// A trait for types that can be converted into SQLite values. +pub trait ToSql { + unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int; +} + +macro_rules! raw_to_impl( + ($t:ty, $f:ident) => ( + impl ToSql for $t { + unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { + ffi::$f(stmt, col, *self) + } + } + ) +); + +raw_to_impl!(c_int, sqlite3_bind_int); // i32 +raw_to_impl!(i64, sqlite3_bind_int64); +raw_to_impl!(c_double, sqlite3_bind_double); + +impl ToSql for bool { + unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { + if *self { + ffi::sqlite3_bind_int(stmt, col, 1) + } else { + ffi::sqlite3_bind_int(stmt, col, 0) + } + } +} + +impl<'a> ToSql for &'a str { + unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { + let length = self.len(); + if length > ::std::i32::MAX as usize { + return ffi::SQLITE_TOOBIG; + } + match str_to_cstring(self) { + Ok(c_str) => { + ffi::sqlite3_bind_text(stmt, + col, + c_str.as_ptr(), + length as c_int, + ffi::SQLITE_TRANSIENT()) + } + Err(_) => ffi::SQLITE_MISUSE, + } + } +} + +impl ToSql for String { + unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { + (&self[..]).bind_parameter(stmt, col) + } +} + +impl<'a> ToSql for &'a [u8] { + unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { + if self.len() > ::std::i32::MAX as usize { + return ffi::SQLITE_TOOBIG; + } + ffi::sqlite3_bind_blob(stmt, + col, + mem::transmute(self.as_ptr()), + self.len() as c_int, + ffi::SQLITE_TRANSIENT()) + } +} + +impl ToSql for Vec { + unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { + (&self[..]).bind_parameter(stmt, col) + } +} + +impl ToSql for Option { + unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { + match *self { + None => ffi::sqlite3_bind_null(stmt, col), + Some(ref t) => t.bind_parameter(stmt, col), + } + } +} + +impl ToSql for Null { + unsafe fn bind_parameter(&self, stmt: *mut sqlite3_stmt, col: c_int) -> c_int { + ffi::sqlite3_bind_null(stmt, col) + } +}