diff --git a/Cargo.toml b/Cargo.toml index 30159560..bbe0330d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,3 +52,6 @@ uuid = { version = ">= 0.1, < 0.4", optional = true } [dev-dependencies] url = "1.0" + +[replace] +"fallible-iterator:0.1.2" = { git = "https://github.com/sfackler/rust-fallible-iterator" } diff --git a/codegen/src/main.rs b/codegen/src/main.rs index f7143a40..0ab7eab1 100644 --- a/codegen/src/main.rs +++ b/codegen/src/main.rs @@ -6,12 +6,12 @@ use std::ascii::AsciiExt; use std::path::Path; mod sqlstate; -mod types; +mod type_gen; fn main() { let path = Path::new("../src"); sqlstate::build(path); - types::build(path); + type_gen::build(path); } fn snake_to_camel(s: &str) -> String { diff --git a/codegen/src/types.rs b/codegen/src/type_gen.rs similarity index 99% rename from codegen/src/types.rs rename to codegen/src/type_gen.rs index 5b73e3ec..49607538 100644 --- a/codegen/src/types.rs +++ b/codegen/src/type_gen.rs @@ -20,7 +20,7 @@ struct Type { } pub fn build(path: &Path) { - let mut file = BufWriter::new(File::create(path.join("types/types.rs")).unwrap()); + let mut file = BufWriter::new(File::create(path.join("types/type_gen.rs")).unwrap()); let ranges = parse_ranges(); let types = parse_types(&ranges); diff --git a/src/lib.rs b/src/lib.rs index 34ebabfa..31ed551a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -528,7 +528,7 @@ impl InnerConnection { let mut values = vec![]; for (param, ty) in params.iter().zip(param_types) { let mut buf = vec![]; - match try!(param.to_sql_checked(ty, &mut buf, &SessionInfo::new(self))) { + match try!(param.to_sql_checked(ty, &mut buf, &SessionInfo::new(self)).map_err(Error::Conversion)) { IsNull::Yes => values.push(None), IsNull::No => values.push(Some(buf)), } @@ -671,18 +671,30 @@ impl InnerConnection { let (name, type_, elem_oid, rngsubtype, basetype, schema, relid) = { let ctx = SessionInfo::new(self); - let name = try!(String::from_sql(&Type::Name, &mut &**row[0].as_ref().unwrap(), &ctx)); - let type_ = try!(i8::from_sql(&Type::Char, &mut &**row[1].as_ref().unwrap(), &ctx)); - let elem_oid = try!(Oid::from_sql(&Type::Oid, &mut &**row[2].as_ref().unwrap(), &ctx)); + let name = try!(String::from_sql(&Type::Name, &mut &**row[0].as_ref().unwrap(), &ctx) + .map_err(Error::Conversion)); + let type_ = try!(i8::from_sql(&Type::Char, &mut &**row[1].as_ref().unwrap(), &ctx) + .map_err(Error::Conversion)); + let elem_oid = try!(Oid::from_sql(&Type::Oid, &mut &**row[2].as_ref().unwrap(), &ctx) + .map_err(Error::Conversion)); let rngsubtype = match row[3] { - Some(ref data) => try!(Option::::from_sql(&Type::Oid, &mut &**data, &ctx)), - None => try!(Option::::from_sql_null(&Type::Oid, &ctx)), + Some(ref data) => { + try!(Option::::from_sql(&Type::Oid, &mut &**data, &ctx) + .map_err(Error::Conversion)) + }, + None => { + try!(Option::::from_sql_null(&Type::Oid, &ctx) + .map_err(Error::Conversion)) + }, }; - let basetype = try!(Oid::from_sql(&Type::Oid, &mut &**row[4].as_ref().unwrap(), &ctx)); + let basetype = try!(Oid::from_sql(&Type::Oid, &mut &**row[4].as_ref().unwrap(), &ctx) + .map_err(Error::Conversion)); let schema = try!(String::from_sql(&Type::Name, &mut &**row[5].as_ref().unwrap(), - &ctx)); - let relid = try!(Oid::from_sql(&Type::Oid, &mut &**row[6].as_ref().unwrap(), &ctx)); + &ctx) + .map_err(Error::Conversion)); + let relid = try!(Oid::from_sql(&Type::Oid, &mut &**row[6].as_ref().unwrap(), &ctx) + .map_err(Error::Conversion)); (name, type_, elem_oid, rngsubtype, basetype, schema, relid) }; @@ -743,7 +755,8 @@ impl InnerConnection { for row in rows { variants.push(try!(String::from_sql(&Type::Name, &mut &**row[0].as_ref().unwrap(), - &ctx))); + &ctx) + .map_err(Error::Conversion))); } Ok(variants) @@ -778,8 +791,10 @@ impl InnerConnection { let ctx = SessionInfo::new(self); let name = try!(String::from_sql(&Type::Name, &mut &**row[0].as_ref().unwrap(), - &ctx)); - let type_ = try!(Oid::from_sql(&Type::Oid, &mut &**row[1].as_ref().unwrap(), &ctx)); + &ctx) + .map_err(Error::Conversion)); + let type_ = try!(Oid::from_sql(&Type::Oid, &mut &**row[1].as_ref().unwrap(), &ctx) + .map_err(Error::Conversion)); (name, type_) }; let type_ = try!(self.get_type(type_)); diff --git a/src/rows.rs b/src/rows.rs index bd22b1b0..11ad9b13 100644 --- a/src/rows.rs +++ b/src/rows.rs @@ -240,7 +240,7 @@ impl<'a> Row<'a> { Some(ref data) => FromSql::from_sql(ty, &mut &**data, &SessionInfo::new(&*conn)), None => FromSql::from_sql_null(ty, &SessionInfo::new(&*conn)), }; - Some(value) + Some(value.map_err(Error::Conversion)) } /// Retrieves the specified field as a raw buffer of Postgres data. diff --git a/src/types/bit_vec.rs b/src/types/bit_vec.rs index ea50d813..2ed262c8 100644 --- a/src/types/bit_vec.rs +++ b/src/types/bit_vec.rs @@ -1,20 +1,16 @@ extern crate bit_vec; -use std::io::prelude::*; -use byteorder::{ReadBytesExt, WriteBytesExt, BigEndian}; +use postgres_protocol::types; use self::bit_vec::BitVec; +use std::error::Error; -use Result; -use types::{FromSql, ToSql, IsNull, Type, SessionInfo, downcast}; +use types::{FromSql, ToSql, IsNull, Type, SessionInfo}; impl FromSql for BitVec { - fn from_sql(_: &Type, raw: &mut R, _: &SessionInfo) -> Result { - let len = try!(raw.read_i32::()) as usize; - let mut bytes = vec![]; - try!(raw.take(((len + 7) / 8) as u64).read_to_end(&mut bytes)); - - let mut bitvec = BitVec::from_bytes(&bytes); - while bitvec.len() > len { + fn from_sql(_: &Type, raw: &[u8], _: &SessionInfo) -> Result> { + let varbit = try!(types::varbit_from_sql(raw)); + let mut bitvec = BitVec::from_bytes(varbit.bytes()); + while bitvec.len() > varbit.len() { bitvec.pop(); } @@ -25,14 +21,8 @@ impl FromSql for BitVec { } impl ToSql for BitVec { - fn to_sql(&self, - _: &Type, - mut out: &mut W, - _: &SessionInfo) - -> Result { - try!(out.write_i32::(try!(downcast(self.len())))); - try!(out.write_all(&self.to_bytes())); - + fn to_sql(&self, _: &Type, mut out: &mut Vec, _: &SessionInfo) -> Result> { + try!(types::varbit_to_sql(self.len(), self.to_bytes().into_iter(), out)); Ok(IsNull::No) } diff --git a/src/types/chrono.rs b/src/types/chrono.rs index 66f24e29..1945b682 100644 --- a/src/types/chrono.rs +++ b/src/types/chrono.rs @@ -1,12 +1,10 @@ extern crate chrono; -use std::io::prelude::*; -use byteorder::{ReadBytesExt, WriteBytesExt, BigEndian}; +use postgres_protocol::types; use self::chrono::{Duration, NaiveDate, NaiveTime, NaiveDateTime, DateTime, UTC, Local, FixedOffset}; +use std::error::Error; -use Result; -use error::Error; use types::{FromSql, ToSql, IsNull, Type, SessionInfo}; fn base() -> NaiveDateTime { @@ -14,8 +12,8 @@ fn base() -> NaiveDateTime { } impl FromSql for NaiveDateTime { - fn from_sql(_: &Type, raw: &mut R, _: &SessionInfo) -> Result { - let t = try!(raw.read_i64::()); + fn from_sql(_: &Type, raw: &[u8], _: &SessionInfo) -> Result> { + let t = try!(types::timestamp_from_sql(raw)); Ok(base() + Duration::microseconds(t)) } @@ -23,16 +21,12 @@ impl FromSql for NaiveDateTime { } impl ToSql for NaiveDateTime { - fn to_sql(&self, - _: &Type, - mut w: &mut W, - _: &SessionInfo) - -> Result { + fn to_sql(&self, _: &Type, w: &mut Vec, _: &SessionInfo) -> Result> { let time = match (*self - base()).num_microseconds() { Some(time) => time, - None => return Err(Error::Conversion("value too large to transmit".into())), + None => return Err("value too large to transmit".into()), }; - try!(w.write_i64::(time)); + types::timestamp_to_sql(time, w); Ok(IsNull::No) } @@ -41,7 +35,7 @@ impl ToSql for NaiveDateTime { } impl FromSql for DateTime { - fn from_sql(type_: &Type, raw: &mut R, info: &SessionInfo) -> Result> { + fn from_sql(type_: &Type, raw: &[u8], info: &SessionInfo) -> Result, Box> { let naive = try!(NaiveDateTime::from_sql(type_, raw, info)); Ok(DateTime::from_utc(naive, UTC)) } @@ -50,11 +44,7 @@ impl FromSql for DateTime { } impl ToSql for DateTime { - fn to_sql(&self, - type_: &Type, - mut w: &mut W, - info: &SessionInfo) - -> Result { + fn to_sql(&self, type_: &Type, w: &mut Vec, info: &SessionInfo) -> Result> { self.naive_utc().to_sql(type_, w, info) } @@ -63,7 +53,7 @@ impl ToSql for DateTime { } impl FromSql for DateTime { - fn from_sql(type_: &Type, raw: &mut R, info: &SessionInfo) -> Result> { + fn from_sql(type_: &Type, raw: &[u8], info: &SessionInfo) -> Result, Box> { let utc = try!(DateTime::::from_sql(type_, raw, info)); Ok(utc.with_timezone(&Local)) } @@ -72,11 +62,7 @@ impl FromSql for DateTime { } impl ToSql for DateTime { - fn to_sql(&self, - type_: &Type, - mut w: &mut W, - info: &SessionInfo) - -> Result { + fn to_sql(&self, type_: &Type, mut w: &mut Vec, info: &SessionInfo) -> Result> { self.with_timezone(&UTC).to_sql(type_, w, info) } @@ -85,10 +71,7 @@ impl ToSql for DateTime { } impl FromSql for DateTime { - fn from_sql(type_: &Type, - raw: &mut R, - info: &SessionInfo) - -> Result> { + fn from_sql(type_: &Type, raw: &[u8], info: &SessionInfo) -> Result, Box> { let utc = try!(DateTime::::from_sql(type_, raw, info)); Ok(utc.with_timezone(&FixedOffset::east(0))) } @@ -97,11 +80,7 @@ impl FromSql for DateTime { } impl ToSql for DateTime { - fn to_sql(&self, - type_: &Type, - mut w: &mut W, - info: &SessionInfo) - -> Result { + fn to_sql(&self, type_: &Type, w: &mut Vec, info: &SessionInfo) -> Result> { self.with_timezone(&UTC).to_sql(type_, w, info) } @@ -110,8 +89,8 @@ impl ToSql for DateTime { } impl FromSql for NaiveDate { - fn from_sql(_: &Type, raw: &mut R, _: &SessionInfo) -> Result { - let jd = try!(raw.read_i32::()); + fn from_sql(_: &Type, raw: &[u8], _: &SessionInfo) -> Result> { + let jd = try!(types::date_from_sql(raw)); Ok(base().date() + Duration::days(jd as i64)) } @@ -119,17 +98,13 @@ impl FromSql for NaiveDate { } impl ToSql for NaiveDate { - fn to_sql(&self, - _: &Type, - mut w: &mut W, - _: &SessionInfo) - -> Result { + fn to_sql(&self, _: &Type, w: &mut Vec, _: &SessionInfo) -> Result> { let jd = (*self - base().date()).num_days(); if jd > i32::max_value() as i64 || jd < i32::min_value() as i64 { - return Err(Error::Conversion("value too large to transmit".into())); + return Err("value too large to transmit".into()); } - try!(w.write_i32::(jd as i32)); + types::date_to_sql(jd as i32, w); Ok(IsNull::No) } @@ -138,8 +113,8 @@ impl ToSql for NaiveDate { } impl FromSql for NaiveTime { - fn from_sql(_: &Type, raw: &mut R, _: &SessionInfo) -> Result { - let usec = try!(raw.read_i64::()); + fn from_sql(_: &Type, raw: &[u8], _: &SessionInfo) -> Result> { + let usec = try!(types::time_from_sql(raw)); Ok(NaiveTime::from_hms(0, 0, 0) + Duration::microseconds(usec)) } @@ -147,17 +122,13 @@ impl FromSql for NaiveTime { } impl ToSql for NaiveTime { - fn to_sql(&self, - _: &Type, - mut w: &mut W, - _: &SessionInfo) - -> Result { + fn to_sql(&self, _: &Type, w: &mut Vec, _: &SessionInfo) -> Result> { let delta = *self - NaiveTime::from_hms(0, 0, 0); let time = match delta.num_microseconds() { Some(time) => time, - None => return Err(Error::Conversion("value too large to transmit".into())), + None => return Err("value too large to transmit".into()), }; - try!(w.write_i64::(time)); + types::time_to_sql(time, w); Ok(IsNull::No) } diff --git a/src/types/eui48.rs b/src/types/eui48.rs index 0aea4e98..534c62bb 100644 --- a/src/types/eui48.rs +++ b/src/types/eui48.rs @@ -1,16 +1,14 @@ extern crate eui48; -use std::io::prelude::*; - use self::eui48::MacAddress; +use std::error::Error; +use postgres_protocol::types; use types::{FromSql, ToSql, Type, IsNull, SessionInfo}; -use Result; impl FromSql for MacAddress { - fn from_sql(_: &Type, raw: &mut R, _: &SessionInfo) -> Result { - let mut bytes = [0; 6]; - try!(raw.read_exact(&mut bytes)); + fn from_sql(_: &Type, raw: &[u8], _: &SessionInfo) -> Result> { + let bytes = try!(types::macaddr_from_sql(raw)); Ok(MacAddress::new(bytes)) } @@ -18,8 +16,10 @@ impl FromSql for MacAddress { } impl ToSql for MacAddress { - fn to_sql(&self, _: &Type, w: &mut W, _: &SessionInfo) -> Result { - try!(w.write_all(self.as_bytes())); + fn to_sql(&self, _: &Type, w: &mut Vec, _: &SessionInfo) -> Result> { + let mut bytes = [0; 6]; + bytes.copy_from_slice(self.as_bytes()); + types::macaddr_to_sql(bytes, w); Ok(IsNull::No) } diff --git a/src/types/mod.rs b/src/types/mod.rs index a6383690..fa71a71d 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,19 +1,18 @@ //! Traits dealing with Postgres data types +use fallible_iterator::FallibleIterator; +use postgres_protocol::types::{self, ArrayDimension}; use std::collections::HashMap; -use std::error; +use std::error::Error; use std::fmt; -use std::io::prelude::*; use std::sync::Arc; -use byteorder::{ReadBytesExt, WriteBytesExt, BigEndian}; #[doc(inline)] -pub use postgres_protocol::message::Oid; +pub use postgres_protocol::Oid; -pub use self::types::Type; +pub use self::type_gen::Type; pub use self::special::{Date, Timestamp}; -use {Result, SessionInfoNew, InnerConnection, OtherNew, WrongTypeNew, FieldNew}; -use error::Error; +use {SessionInfoNew, InnerConnection, OtherNew, WrongTypeNew, FieldNew}; /// Generates a simple implementation of `ToSql::accepts` which accepts the /// types passed to it. @@ -37,9 +36,9 @@ macro_rules! to_sql_checked { () => { fn to_sql_checked(&self, ty: &$crate::types::Type, - out: &mut ::std::io::Write, + out: &mut ::std::vec::Vec, ctx: &$crate::types::SessionInfo) - -> $crate::Result<$crate::types::IsNull> { + -> ::std::result::Result<$crate::types::IsNull, Box<::std::error::Error + ::std::marker::Sync + ::std::marker::Send>> { $crate::types::__to_sql_checked(self, ty, out, ctx) } } @@ -48,11 +47,11 @@ macro_rules! to_sql_checked { // WARNING: this function is not considered part of this crate's public API. // It is subject to change at any time. #[doc(hidden)] -pub fn __to_sql_checked(v: &T, ty: &Type, out: &mut Write, ctx: &SessionInfo) -> Result +pub fn __to_sql_checked(v: &T, ty: &Type, out: &mut Vec, ctx: &SessionInfo) -> Result> where T: ToSql { if !T::accepts(ty) { - return Err(Error::Conversion(Box::new(WrongType(ty.clone())))); + return Err(Box::new(WrongType(ty.clone()))); } v.to_sql(ty, out, ctx) } @@ -73,7 +72,7 @@ mod chrono; mod eui48; mod special; -mod types; +mod type_gen; /// A structure providing information for conversion methods. pub struct SessionInfo<'a> { @@ -214,11 +213,11 @@ pub struct WasNull; impl fmt::Display for WasNull { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.write_str(error::Error::description(self)) + fmt.write_str(self.description()) } } -impl error::Error for WasNull { +impl Error for WasNull { fn description(&self) -> &str { "a Postgres value was `NULL`" } @@ -237,7 +236,7 @@ impl fmt::Display for WrongType { } } -impl error::Error for WrongType { +impl Error for WrongType { fn description(&self) -> &str { "cannot convert to or from a Postgres value" } @@ -303,12 +302,12 @@ impl WrongTypeNew for WrongType { /// `FromSql` is implemented for `Vec` where `T` implements `FromSql`, and /// corresponds to one-dimensional Postgres arrays. pub trait FromSql: Sized { - /// Creates a new value of this type from a `Read`er of the binary format - /// of the specified Postgres `Type`. + /// Creates a new value of this type from a buffer of data of the specified + /// Postgres `Type` in its binary format. /// /// The caller of this method is responsible for ensuring that this type /// is compatible with the Postgres `Type`. - fn from_sql(ty: &Type, raw: &mut R, ctx: &SessionInfo) -> Result; + fn from_sql(ty: &Type, raw: &[u8], ctx: &SessionInfo) -> Result>; /// Creates a new value of this type from a `NULL` SQL value. /// @@ -316,10 +315,10 @@ pub trait FromSql: Sized { /// is compatible with the Postgres `Type`. /// /// The default implementation returns - /// `Err(Error::Conversion(Box::new(WasNull))`. + /// `Err(Box::new(WasNull))`. #[allow(unused_variables)] - fn from_sql_null(ty: &Type, ctx: &SessionInfo) -> Result { - Err(Error::Conversion(Box::new(WasNull))) + fn from_sql_null(ty: &Type, ctx: &SessionInfo) -> Result> { + Err(Box::new(WasNull)) } /// Determines if a value of this type can be created from the specified @@ -328,11 +327,11 @@ pub trait FromSql: Sized { } impl FromSql for Option { - fn from_sql(ty: &Type, raw: &mut R, ctx: &SessionInfo) -> Result> { + fn from_sql(ty: &Type, raw: &[u8], ctx: &SessionInfo) -> Result, Box> { ::from_sql(ty, raw, ctx).map(Some) } - fn from_sql_null(_: &Type, _: &SessionInfo) -> Result> { + fn from_sql_null(_: &Type, _: &SessionInfo) -> Result, Box> { Ok(None) } @@ -341,49 +340,26 @@ impl FromSql for Option { } } -impl FromSql for bool { - fn from_sql(_: &Type, raw: &mut R, _: &SessionInfo) -> Result { - Ok(try!(raw.read_u8()) != 0) - } - - accepts!(Type::Bool); -} - impl FromSql for Vec { - fn from_sql(ty: &Type, raw: &mut R, info: &SessionInfo) -> Result> { + fn from_sql(ty: &Type, raw: &[u8], info: &SessionInfo) -> Result, Box> { let member_type = match *ty.kind() { Kind::Array(ref member) => member, _ => panic!("expected array type"), }; - let dimensions = try!(raw.read_i32::()); - if dimensions > 1 { - return Err(Error::Conversion("array contains too many dimensions".into())); + let array = try!(types::array_from_sql(raw)); + if try!(array.dimensions().count()) > 1 { + return Err("array contains too many dimensions".into()); } - let _has_nulls = try!(raw.read_i32::()); - let _member_oid = try!(raw.read_u32::()); - - if dimensions == 0 { - return Ok(vec![]); - } - - let count = try!(raw.read_i32::()); - let _index_offset = try!(raw.read_i32::()); - - let mut out = Vec::with_capacity(count as usize); - for _ in 0..count { - let len = try!(raw.read_i32::()); - let value = if len < 0 { - try!(T::from_sql_null(&member_type, info)) - } else { - let mut raw = raw.take(len as u64); - try!(T::from_sql(&member_type, &mut raw, info)) - }; - out.push(value) - } - - Ok(out) + array.values() + .and_then(|v| { + match v { + Some(v) => T::from_sql(&member_type, v, info), + None => T::from_sql_null(&member_type, info), + } + }) + .collect() } fn accepts(ty: &Type) -> bool { @@ -395,20 +371,16 @@ impl FromSql for Vec { } impl FromSql for Vec { - fn from_sql(_: &Type, raw: &mut R, _: &SessionInfo) -> Result> { - let mut buf = vec![]; - try!(raw.read_to_end(&mut buf)); - Ok(buf) + fn from_sql(_: &Type, raw: &[u8], _: &SessionInfo) -> Result, Box> { + Ok(types::bytea_from_sql(raw).to_owned()) } accepts!(Type::Bytea); } impl FromSql for String { - fn from_sql(_: &Type, raw: &mut R, _: &SessionInfo) -> Result { - let mut buf = vec![]; - try!(raw.read_to_end(&mut buf)); - String::from_utf8(buf).map_err(|err| Error::Conversion(Box::new(err))) + fn from_sql(_: &Type, raw: &[u8], _: &SessionInfo) -> Result> { + types::text_from_sql(raw).map(|b| b.to_owned()) } fn accepts(ty: &Type) -> bool { @@ -420,19 +392,11 @@ impl FromSql for String { } } -impl FromSql for i8 { - fn from_sql(_: &Type, raw: &mut R, _: &SessionInfo) -> Result { - Ok(try!(raw.read_i8())) - } - - accepts!(Type::Char); -} - -macro_rules! primitive_from { +macro_rules! simple_from { ($t:ty, $f:ident, $($expected:pat),+) => { impl FromSql for $t { - fn from_sql(_: &Type, raw: &mut R, _: &SessionInfo) -> Result<$t> { - Ok(try!(raw.$f::())) + fn from_sql(_: &Type, raw: &[u8], _: &SessionInfo) -> Result<$t, Box> { + types::$f(raw) } accepts!($($expected),+); @@ -440,47 +404,20 @@ macro_rules! primitive_from { } } -primitive_from!(i16, read_i16, Type::Int2); -primitive_from!(i32, read_i32, Type::Int4); -primitive_from!(u32, read_u32, Type::Oid); -primitive_from!(i64, read_i64, Type::Int8); -primitive_from!(f32, read_f32, Type::Float4); -primitive_from!(f64, read_f64, Type::Float8); +simple_from!(bool, bool_from_sql, Type::Bool); +simple_from!(i8, char_from_sql, Type::Char); +simple_from!(i16, int2_from_sql, Type::Int2); +simple_from!(i32, int4_from_sql, Type::Int4); +simple_from!(u32, oid_from_sql, Type::Oid); +simple_from!(i64, int8_from_sql, Type::Int8); +simple_from!(f32, float4_from_sql, Type::Float4); +simple_from!(f64, float8_from_sql, Type::Float8); impl FromSql for HashMap> { - fn from_sql(_: &Type, - raw: &mut R, - _: &SessionInfo) - -> Result>> { - let mut map = HashMap::new(); - - let count = try!(raw.read_i32::()); - - for _ in 0..count { - let key_len = try!(raw.read_i32::()); - let mut key = vec![0; key_len as usize]; - try!(raw.read_exact(&mut key)); - let key = match String::from_utf8(key) { - Ok(key) => key, - Err(err) => return Err(Error::Conversion(Box::new(err))), - }; - - let val_len = try!(raw.read_i32::()); - let val = if val_len < 0 { - None - } else { - let mut val = vec![0; val_len as usize]; - try!(raw.read_exact(&mut val)); - match String::from_utf8(val) { - Ok(val) => Some(val), - Err(err) => return Err(Error::Conversion(Box::new(err))), - } - }; - - map.insert(key, val); - } - - Ok(map) + fn from_sql(_: &Type, raw: &[u8], _: &SessionInfo) -> Result>, Box> { + try!(types::hstore_from_sql(raw)) + .map(|(k, v)| (k.to_owned(), v.map(|v| v.to_owned()))) + .collect() } fn accepts(ty: &Type) -> bool { @@ -556,7 +493,7 @@ pub enum IsNull { /// 1. pub trait ToSql: fmt::Debug { /// Converts the value of `self` into the binary format of the specified - /// Postgres `Type`, writing it to `out`. + /// Postgres `Type`, appending it to `out`. /// /// The caller of this method is responsible for ensuring that this type /// is compatible with the Postgres `Type`. @@ -564,9 +501,7 @@ pub trait ToSql: fmt::Debug { /// The return value indicates if this value should be represented as /// `NULL`. If this is the case, implementations **must not** write /// anything to `out`. - fn to_sql(&self, ty: &Type, out: &mut W, ctx: &SessionInfo) -> Result - where Self: Sized, - W: Write; + fn to_sql(&self, ty: &Type, out: &mut Vec, ctx: &SessionInfo) -> Result> where Self: Sized; /// Determines if a value of this type can be converted to the specified /// Postgres `Type`. @@ -576,35 +511,25 @@ pub trait ToSql: fmt::Debug { /// /// *All* implementations of this method should be generated by the /// `to_sql_checked!()` macro. - fn to_sql_checked(&self, ty: &Type, out: &mut Write, ctx: &SessionInfo) -> Result; + fn to_sql_checked(&self, ty: &Type, out: &mut Vec, ctx: &SessionInfo) -> Result>; } impl<'a, T> ToSql for &'a T where T: ToSql { - to_sql_checked!(); - - fn to_sql(&self, - ty: &Type, - out: &mut W, - ctx: &SessionInfo) - -> Result { + fn to_sql(&self, ty: &Type, out: &mut Vec, ctx: &SessionInfo) -> Result> { (*self).to_sql(ty, out, ctx) } fn accepts(ty: &Type) -> bool { T::accepts(ty) } + + to_sql_checked!(); } impl ToSql for Option { - to_sql_checked!(); - - fn to_sql(&self, - ty: &Type, - out: &mut W, - ctx: &SessionInfo) - -> Result { + fn to_sql(&self, ty: &Type, out: &mut Vec, ctx: &SessionInfo) -> Result> { match *self { Some(ref val) => val.to_sql(ty, out, ctx), None => Ok(IsNull::Yes), @@ -614,55 +539,35 @@ impl ToSql for Option { fn accepts(ty: &Type) -> bool { ::accepts(ty) } -} -impl ToSql for bool { to_sql_checked!(); - - fn to_sql(&self, - _: &Type, - mut w: &mut W, - _: &SessionInfo) - -> Result { - try!(w.write_u8(*self as u8)); - Ok(IsNull::No) - } - - accepts!(Type::Bool); } impl<'a, T: ToSql> ToSql for &'a [T] { - to_sql_checked!(); - - fn to_sql(&self, - ty: &Type, - mut w: &mut W, - ctx: &SessionInfo) - -> Result { + fn to_sql(&self, ty: &Type, w: &mut Vec, ctx: &SessionInfo) -> Result> { let member_type = match *ty.kind() { Kind::Array(ref member) => member, _ => panic!("expected array type"), }; - try!(w.write_i32::(1)); // number of dimensions - try!(w.write_i32::(1)); // has nulls - try!(w.write_u32::(member_type.oid())); - - try!(w.write_i32::(try!(downcast(self.len())))); - try!(w.write_i32::(1)); // index offset - - let mut inner_buf = vec![]; - for e in *self { - match try!(e.to_sql(&member_type, &mut inner_buf, ctx)) { - IsNull::No => { - try!(w.write_i32::(try!(downcast(inner_buf.len())))); - try!(w.write_all(&inner_buf)); - } - IsNull::Yes => try!(w.write_i32::(-1)), + let dimensions = [ + ArrayDimension { + len: try!(downcast(self.len())), + lower_bound: 1, } - inner_buf.clear(); - } + ]; + try!(types::array_to_sql(dimensions.iter().cloned(), + true, + member_type.oid(), + self.iter(), + |e, w| { + match try!(e.to_sql(member_type, w, ctx)) { + IsNull::No => Ok(types::IsNull::No), + IsNull::Yes => Ok(types::IsNull::Yes), + } + }, + w)); Ok(IsNull::No) } @@ -672,48 +577,48 @@ impl<'a, T: ToSql> ToSql for &'a [T] { _ => false, } } + + to_sql_checked!(); } impl<'a> ToSql for &'a [u8] { - to_sql_checked!(); - - fn to_sql(&self, _: &Type, w: &mut W, _: &SessionInfo) -> Result { - try!(w.write_all(*self)); + fn to_sql(&self, _: &Type, w: &mut Vec, _: &SessionInfo) -> Result> { + types::bytea_to_sql(*self, w); Ok(IsNull::No) } accepts!(Type::Bytea); + + to_sql_checked!(); } impl ToSql for Vec { - to_sql_checked!(); - - fn to_sql(&self, ty: &Type, w: &mut W, ctx: &SessionInfo) -> Result { + fn to_sql(&self, ty: &Type, w: &mut Vec, ctx: &SessionInfo) -> Result> { <&[T] as ToSql>::to_sql(&&**self, ty, w, ctx) } fn accepts(ty: &Type) -> bool { <&[T] as ToSql>::accepts(ty) } + + to_sql_checked!(); } impl ToSql for Vec { - to_sql_checked!(); - - fn to_sql(&self, ty: &Type, w: &mut W, ctx: &SessionInfo) -> Result { + fn to_sql(&self, ty: &Type, w: &mut Vec, ctx: &SessionInfo) -> Result> { <&[u8] as ToSql>::to_sql(&&**self, ty, w, ctx) } fn accepts(ty: &Type) -> bool { <&[u8] as ToSql>::accepts(ty) } + + to_sql_checked!(); } impl<'a> ToSql for &'a str { - to_sql_checked!(); - - fn to_sql(&self, _: &Type, w: &mut W, _: &SessionInfo) -> Result { - try!(w.write_all(self.as_bytes())); + fn to_sql(&self, _: &Type, w: &mut Vec, _: &SessionInfo) -> Result> { + types::text_to_sql(*self, w); Ok(IsNull::No) } @@ -724,81 +629,49 @@ impl<'a> ToSql for &'a str { _ => false, } } + + to_sql_checked!(); } impl ToSql for String { - to_sql_checked!(); - - fn to_sql(&self, ty: &Type, w: &mut W, ctx: &SessionInfo) -> Result { + fn to_sql(&self, ty: &Type, w: &mut Vec, ctx: &SessionInfo) -> Result> { <&str as ToSql>::to_sql(&&**self, ty, w, ctx) } fn accepts(ty: &Type) -> bool { <&str as ToSql>::accepts(ty) } -} -impl ToSql for i8 { to_sql_checked!(); - - fn to_sql(&self, - _: &Type, - mut w: &mut W, - _: &SessionInfo) - -> Result { - try!(w.write_i8(*self)); - Ok(IsNull::No) - } - - accepts!(Type::Char); } -macro_rules! to_primitive { +macro_rules! simple_to { ($t:ty, $f:ident, $($expected:pat),+) => { impl ToSql for $t { - to_sql_checked!(); - - fn to_sql(&self, _: &Type, mut w: &mut W, _: &SessionInfo) - -> Result { - try!(w.$f::(*self)); + fn to_sql(&self, _: &Type, w: &mut Vec, _: &SessionInfo) -> Result> { + types::$f(*self, w); Ok(IsNull::No) } accepts!($($expected),+); + + to_sql_checked!(); } } } -to_primitive!(i16, write_i16, Type::Int2); -to_primitive!(i32, write_i32, Type::Int4); -to_primitive!(u32, write_u32, Type::Oid); -to_primitive!(i64, write_i64, Type::Int8); -to_primitive!(f32, write_f32, Type::Float4); -to_primitive!(f64, write_f64, Type::Float8); +simple_to!(bool, bool_to_sql, Type::Bool); +simple_to!(i8, char_to_sql, Type::Char); +simple_to!(i16, int2_to_sql, Type::Int2); +simple_to!(i32, int4_to_sql, Type::Int4); +simple_to!(u32, oid_to_sql, Type::Oid); +simple_to!(i64, int8_to_sql, Type::Int8); +simple_to!(f32, float4_to_sql, Type::Float4); +simple_to!(f64, float8_to_sql, Type::Float8); impl ToSql for HashMap> { - to_sql_checked!(); - - fn to_sql(&self, - _: &Type, - mut w: &mut W, - _: &SessionInfo) - -> Result { - try!(w.write_i32::(try!(downcast(self.len())))); - - for (key, val) in self { - try!(w.write_i32::(try!(downcast(key.len())))); - try!(w.write_all(key.as_bytes())); - - match *val { - Some(ref val) => { - try!(w.write_i32::(try!(downcast(val.len())))); - try!(w.write_all(val.as_bytes())); - } - None => try!(w.write_i32::(-1)), - } - } - + fn to_sql(&self, _: &Type, w: &mut Vec, _: &SessionInfo) -> Result> { + try!(types::hstore_to_sql(self.iter().map(|(k, v)| (&**k, v.as_ref().map(|v| &**v))), w)); Ok(IsNull::No) } @@ -808,11 +681,13 @@ impl ToSql for HashMap> { _ => false, } } + + to_sql_checked!(); } -fn downcast(len: usize) -> Result { +fn downcast(len: usize) -> Result> { if len > i32::max_value() as usize { - Err(Error::Conversion("value too large to transmit".into())) + Err("value too large to transmit".into()) } else { Ok(len as i32) } diff --git a/src/types/rustc_serialize.rs b/src/types/rustc_serialize.rs index 812b46d7..82a21b1c 100644 --- a/src/types/rustc_serialize.rs +++ b/src/types/rustc_serialize.rs @@ -1,39 +1,33 @@ extern crate rustc_serialize; use self::rustc_serialize::json; -use std::io::prelude::*; -use byteorder::{ReadBytesExt, WriteBytesExt}; +use std::io::{Cursor, Write}; +use byteorder::ReadBytesExt; +use std::error::Error; -use Result; -use error::Error; use types::{FromSql, ToSql, IsNull, Type, SessionInfo}; impl FromSql for json::Json { - fn from_sql(ty: &Type, raw: &mut R, _: &SessionInfo) -> Result { + fn from_sql(ty: &Type, raw: &[u8], _: &SessionInfo) -> Result> { + let mut raw = Cursor::new(raw); if let Type::Jsonb = *ty { // We only support version 1 of the jsonb binary format if try!(raw.read_u8()) != 1 { - return Err(Error::Conversion("unsupported JSONB encoding version".into())); + return Err("unsupported JSONB encoding version".into()); } } - json::Json::from_reader(raw).map_err(|err| Error::Conversion(Box::new(err))) + json::Json::from_reader(&mut raw).map_err(Into::into) } accepts!(Type::Json, Type::Jsonb); } impl ToSql for json::Json { - fn to_sql(&self, - ty: &Type, - mut out: &mut W, - _: &SessionInfo) - -> Result { + fn to_sql(&self, ty: &Type, mut out: &mut Vec, _: &SessionInfo) -> Result> { if let Type::Jsonb = *ty { - try!(out.write_u8(1)); + out.push(1); } - try!(write!(out, "{}", self)); - Ok(IsNull::No) } diff --git a/src/types/serde_json.rs b/src/types/serde_json.rs index 6e73d361..709233ec 100644 --- a/src/types/serde_json.rs +++ b/src/types/serde_json.rs @@ -1,39 +1,32 @@ extern crate serde_json; -use std::io::prelude::*; -use byteorder::{ReadBytesExt, WriteBytesExt}; +use byteorder::ReadBytesExt; use self::serde_json::Value; +use std::error::Error; +use std::io::Write; -use Result; -use error::Error; use types::{FromSql, ToSql, IsNull, Type, SessionInfo}; impl FromSql for Value { - fn from_sql(ty: &Type, raw: &mut R, _: &SessionInfo) -> Result { + fn from_sql(ty: &Type, mut raw: &[u8], _: &SessionInfo) -> Result> { if let Type::Jsonb = *ty { // We only support version 1 of the jsonb binary format if try!(raw.read_u8()) != 1 { - return Err(Error::Conversion("unsupported JSONB encoding version".into())); + return Err("unsupported JSONB encoding version".into()); } } - serde_json::de::from_reader(raw).map_err(|err| Error::Conversion(Box::new(err))) + serde_json::de::from_reader(raw).map_err(Into::into) } accepts!(Type::Json, Type::Jsonb); } impl ToSql for Value { - fn to_sql(&self, - ty: &Type, - mut out: &mut W, - _: &SessionInfo) - -> Result { + fn to_sql(&self, ty: &Type, mut out: &mut Vec, _: &SessionInfo) -> Result> { if let Type::Jsonb = *ty { - try!(out.write_u8(1)); + out.push(1); } - try!(write!(out, "{:?}", self)); - Ok(IsNull::No) } diff --git a/src/types/special.rs b/src/types/special.rs index 63d4b3fd..79a1e554 100644 --- a/src/types/special.rs +++ b/src/types/special.rs @@ -1,10 +1,7 @@ -use std::io::prelude::*; +use postgres_protocol::types; use std::{i32, i64}; +use std::error::Error; -use byteorder::{ReadBytesExt, WriteBytesExt, BigEndian}; - -use Result; -use error::Error; use types::{Type, FromSql, ToSql, IsNull, SessionInfo}; /// A wrapper that can be used to represent infinity with `Type::Date` types. @@ -19,18 +16,11 @@ pub enum Date { } impl FromSql for Date { - fn from_sql(ty: &Type, raw: &mut R, ctx: &SessionInfo) -> Result { - if *ty != Type::Date { - return Err(Error::Conversion("expected date type".into())); - } - - let mut buf = [0; 4]; - try!(raw.read_exact(buf.as_mut())); - - match try!(buf.as_ref().read_i32::()) { + fn from_sql(ty: &Type, raw: &[u8], ctx: &SessionInfo) -> Result> { + match try!(types::date_from_sql(raw)) { i32::MAX => Ok(Date::PosInfinity), i32::MIN => Ok(Date::NegInfinity), - _ => T::from_sql(ty, &mut &mut buf.as_ref(), ctx).map(Date::Value), + _ => T::from_sql(ty, raw, ctx).map(Date::Value), } } @@ -39,22 +29,14 @@ impl FromSql for Date { } } impl ToSql for Date { - fn to_sql(&self, - ty: &Type, - out: &mut W, - ctx: &SessionInfo) - -> Result { - if *ty != Type::Date { - return Err(Error::Conversion("expected date type".into())); - } - + fn to_sql(&self, ty: &Type, out: &mut Vec, ctx: &SessionInfo) -> Result> { let value = match *self { Date::PosInfinity => i32::MAX, Date::NegInfinity => i32::MIN, Date::Value(ref v) => return v.to_sql(ty, out, ctx), }; - try!(out.write_i32::(value)); + types::date_to_sql(value, out); Ok(IsNull::No) } @@ -78,18 +60,11 @@ pub enum Timestamp { } impl FromSql for Timestamp { - fn from_sql(ty: &Type, raw: &mut R, ctx: &SessionInfo) -> Result { - if *ty != Type::Timestamp && *ty != Type::Timestamptz { - return Err(Error::Conversion("expected timestamp or timestamptz type".into())); - } - - let mut buf = [0; 8]; - try!(raw.read_exact(buf.as_mut())); - - match try!(buf.as_ref().read_i64::()) { + fn from_sql(ty: &Type, raw: &[u8], ctx: &SessionInfo) -> Result> { + match try!(types::timestamp_from_sql(raw)) { i64::MAX => Ok(Timestamp::PosInfinity), i64::MIN => Ok(Timestamp::NegInfinity), - _ => T::from_sql(ty, &mut &mut buf.as_ref(), ctx).map(Timestamp::Value), + _ => T::from_sql(ty, raw, ctx).map(Timestamp::Value), } } @@ -99,22 +74,14 @@ impl FromSql for Timestamp { } impl ToSql for Timestamp { - fn to_sql(&self, - ty: &Type, - out: &mut W, - ctx: &SessionInfo) - -> Result { - if *ty != Type::Timestamp && *ty != Type::Timestamptz { - return Err(Error::Conversion("expected timestamp or timestamptz type".into())); - } - + fn to_sql(&self, ty: &Type, out: &mut Vec, ctx: &SessionInfo) -> Result> { let value = match *self { Timestamp::PosInfinity => i64::MAX, Timestamp::NegInfinity => i64::MIN, Timestamp::Value(ref v) => return v.to_sql(ty, out, ctx), }; - try!(out.write_i64::(value)); + types::timestamp_to_sql(value, out); Ok(IsNull::No) } diff --git a/src/types/time.rs b/src/types/time.rs index ef5860d8..b38a2e27 100644 --- a/src/types/time.rs +++ b/src/types/time.rs @@ -1,10 +1,9 @@ extern crate time; -use byteorder::{ReadBytesExt, WriteBytesExt, BigEndian}; use self::time::Timespec; -use std::io::prelude::*; +use std::error::Error; +use postgres_protocol::types; -use Result; use types::{Type, FromSql, ToSql, IsNull, SessionInfo}; const USEC_PER_SEC: i64 = 1_000_000; @@ -14,8 +13,8 @@ const NSEC_PER_USEC: i64 = 1_000; const TIME_SEC_CONVERSION: i64 = 946684800; impl FromSql for Timespec { - fn from_sql(_: &Type, raw: &mut R, _: &SessionInfo) -> Result { - let t = try!(raw.read_i64::()); + fn from_sql(_: &Type, raw: &[u8], _: &SessionInfo) -> Result> { + let t = try!(types::timestamp_from_sql(raw)); let mut sec = t / USEC_PER_SEC + TIME_SEC_CONVERSION; let mut usec = t % USEC_PER_SEC; @@ -31,13 +30,9 @@ impl FromSql for Timespec { } impl ToSql for Timespec { - fn to_sql(&self, - _: &Type, - mut w: &mut W, - _: &SessionInfo) - -> Result { + fn to_sql(&self, _: &Type, w: &mut Vec, _: &SessionInfo) -> Result> { let t = (self.sec - TIME_SEC_CONVERSION) * USEC_PER_SEC + self.nsec as i64 / NSEC_PER_USEC; - try!(w.write_i64::(t)); + types::timestamp_to_sql(t, w); Ok(IsNull::No) } diff --git a/src/types/types.rs b/src/types/type_gen.rs similarity index 100% rename from src/types/types.rs rename to src/types/type_gen.rs diff --git a/src/types/uuid.rs b/src/types/uuid.rs index cbc6f9a1..9e616d77 100644 --- a/src/types/uuid.rs +++ b/src/types/uuid.rs @@ -1,15 +1,14 @@ extern crate uuid; -use std::io::prelude::*; - +use postgres_protocol::types; use self::uuid::Uuid; +use std::error::Error; + use types::{FromSql, ToSql, Type, IsNull, SessionInfo}; -use Result; impl FromSql for Uuid { - fn from_sql(_: &Type, raw: &mut R, _: &SessionInfo) -> Result { - let mut bytes = [0; 16]; - try!(raw.read_exact(&mut bytes)); + fn from_sql(_: &Type, raw: &[u8], _: &SessionInfo) -> Result> { + let bytes = try!(types::uuid_from_sql(raw)); Ok(Uuid::from_bytes(&bytes).unwrap()) } @@ -17,8 +16,8 @@ impl FromSql for Uuid { } impl ToSql for Uuid { - fn to_sql(&self, _: &Type, w: &mut W, _: &SessionInfo) -> Result { - try!(w.write_all(self.as_bytes())); + fn to_sql(&self, _: &Type, w: &mut Vec, _: &SessionInfo) -> Result> { + types::uuid_to_sql(*self.as_bytes(), w); Ok(IsNull::No) } diff --git a/tests/types/mod.rs b/tests/types/mod.rs index 62db4255..91a53f9e 100644 --- a/tests/types/mod.rs +++ b/tests/types/mod.rs @@ -1,8 +1,10 @@ use std::collections::HashMap; +use std::error; use std::f32; use std::f64; use std::fmt; use std::io::{Read, Write}; +use std::result; use postgres::{Connection, TlsMode, Result}; use postgres::error::Error; @@ -251,9 +253,7 @@ fn domain() { struct SessionId(Vec); impl ToSql for SessionId { - fn to_sql(&self, ty: &Type, out: &mut W, ctx: &SessionInfo) -> Result - where W: Write - { + fn to_sql(&self, ty: &Type, out: &mut Vec, ctx: &SessionInfo) -> result::Result> { let inner = match *ty.kind() { Kind::Domain(ref inner) => inner, _ => unreachable!(), @@ -269,7 +269,7 @@ fn domain() { } impl FromSql for SessionId { - fn from_sql(ty: &Type, raw: &mut R, ctx: &SessionInfo) -> Result { + fn from_sql(ty: &Type, raw: &[u8], ctx: &SessionInfo) -> result::Result> { Vec::::from_sql(ty, raw, ctx).map(SessionId) }