diff --git a/postgres-protocol/src/types.rs b/postgres-protocol/src/types.rs index 47d6af2f..d7f2f9a2 100644 --- a/postgres-protocol/src/types.rs +++ b/postgres-protocol/src/types.rs @@ -3,6 +3,7 @@ use byteorder::{ReadBytesExt, WriteBytesExt, BigEndian}; use fallible_iterator::FallibleIterator; use std::error::Error; use std::str; +use std::boxed::Box as StdBox; use {Oid, IsNull, write_nullable, FromUsize}; @@ -20,7 +21,7 @@ pub fn bool_to_sql(v: bool, buf: &mut Vec) { /// Deserializes a `BOOL` value. #[inline] -pub fn bool_from_sql(buf: &[u8]) -> Result> { +pub fn bool_from_sql(buf: &[u8]) -> Result> { if buf.len() != 1 { return Err("invalid buffer size".into()); } @@ -48,7 +49,7 @@ pub fn text_to_sql(v: &str, buf: &mut Vec) { /// Deserializes a `TEXT`, `VARCHAR`, `CHAR(n)`, `NAME`, or `CITEXT` value. #[inline] -pub fn text_from_sql(buf: &[u8]) -> Result<&str, Box> { +pub fn text_from_sql(buf: &[u8]) -> Result<&str, StdBox> { Ok(try!(str::from_utf8(buf))) } @@ -60,7 +61,7 @@ pub fn char_to_sql(v: i8, buf: &mut Vec) { /// Deserializes a `"char"` value. #[inline] -pub fn char_from_sql(mut buf: &[u8]) -> Result> { +pub fn char_from_sql(mut buf: &[u8]) -> Result> { let v = try!(buf.read_i8()); if !buf.is_empty() { return Err("invalid buffer size".into()); @@ -76,7 +77,7 @@ pub fn int2_to_sql(v: i16, buf: &mut Vec) { /// Deserializes an `INT2` value. #[inline] -pub fn int2_from_sql(mut buf: &[u8]) -> Result> { +pub fn int2_from_sql(mut buf: &[u8]) -> Result> { let v = try!(buf.read_i16::()); if !buf.is_empty() { return Err("invalid buffer size".into()); @@ -92,7 +93,7 @@ pub fn int4_to_sql(v: i32, buf: &mut Vec) { /// Deserializes an `INT4` value. #[inline] -pub fn int4_from_sql(mut buf: &[u8]) -> Result> { +pub fn int4_from_sql(mut buf: &[u8]) -> Result> { let v = try!(buf.read_i32::()); if !buf.is_empty() { return Err("invalid buffer size".into()); @@ -108,7 +109,7 @@ pub fn oid_to_sql(v: Oid, buf: &mut Vec) { /// Deserializes an `OID` value. #[inline] -pub fn oid_from_sql(mut buf: &[u8]) -> Result> { +pub fn oid_from_sql(mut buf: &[u8]) -> Result> { let v = try!(buf.read_u32::()); if !buf.is_empty() { return Err("invalid buffer size".into()); @@ -124,7 +125,7 @@ pub fn int8_to_sql(v: i64, buf: &mut Vec) { /// Deserializes an `INT8` value. #[inline] -pub fn int8_from_sql(mut buf: &[u8]) -> Result> { +pub fn int8_from_sql(mut buf: &[u8]) -> Result> { let v = try!(buf.read_i64::()); if !buf.is_empty() { return Err("invalid buffer size".into()); @@ -140,7 +141,7 @@ pub fn float4_to_sql(v: f32, buf: &mut Vec) { /// Deserializes a `FLOAT4` value. #[inline] -pub fn float4_from_sql(mut buf: &[u8]) -> Result> { +pub fn float4_from_sql(mut buf: &[u8]) -> Result> { let v = try!(buf.read_f32::()); if !buf.is_empty() { return Err("invalid buffer size".into()); @@ -156,7 +157,7 @@ pub fn float8_to_sql(v: f64, buf: &mut Vec) { /// Deserializes a `FLOAT8` value. #[inline] -pub fn float8_from_sql(mut buf: &[u8]) -> Result> { +pub fn float8_from_sql(mut buf: &[u8]) -> Result> { let v = try!(buf.read_f64::()); if !buf.is_empty() { return Err("invalid buffer size".into()); @@ -166,7 +167,7 @@ pub fn float8_from_sql(mut buf: &[u8]) -> Result> /// Serializes an `HSTORE` value. #[inline] -pub fn hstore_to_sql<'a, I>(values: I, buf: &mut Vec) -> Result<(), Box> +pub fn hstore_to_sql<'a, I>(values: I, buf: &mut Vec) -> Result<(), StdBox> where I: IntoIterator)> { let base = buf.len(); @@ -192,7 +193,7 @@ pub fn hstore_to_sql<'a, I>(values: I, buf: &mut Vec) -> Result<(), Box) -> Result<(), Box> { +fn write_pascal_string(s: &str, buf: &mut Vec) -> Result<(), StdBox> { let size = try!(i32::from_usize(s.len())); buf.write_i32::(size).unwrap(); buf.extend_from_slice(s.as_bytes()); @@ -202,7 +203,7 @@ fn write_pascal_string(s: &str, buf: &mut Vec) -> Result<(), Box(mut buf: &'a [u8]) - -> Result, Box> { + -> Result, StdBox> { let count = try!(buf.read_i32::()); if count < 0 { return Err("invalid entry count".into()); @@ -222,10 +223,10 @@ pub struct HstoreEntries<'a> { impl<'a> FallibleIterator for HstoreEntries<'a> { type Item = (&'a str, Option<&'a str>); - type Error = Box; + type Error = StdBox; #[inline] - fn next(&mut self) -> Result)>, Box> { + fn next(&mut self) -> Result)>, StdBox> { if self.remaining == 0 { if !self.buf.is_empty() { return Err("invalid buffer size".into()); @@ -265,7 +266,10 @@ impl<'a> FallibleIterator for HstoreEntries<'a> { /// Serializes a `VARBIT` or `BIT` value. #[inline] -pub fn varbit_to_sql(len: usize, v: I, buf: &mut Vec) -> Result<(), Box> +pub fn varbit_to_sql(len: usize, + v: I, + buf: &mut Vec) + -> Result<(), StdBox> where I: Iterator { let len = try!(i32::from_usize(len)); @@ -280,7 +284,7 @@ pub fn varbit_to_sql(len: usize, v: I, buf: &mut Vec) -> Result<(), Box(mut buf: &'a [u8]) -> Result, Box> { +pub fn varbit_from_sql<'a>(mut buf: &'a [u8]) -> Result, StdBox> { let len = try!(buf.read_i32::()); if len < 0 { return Err("invalid varbit length".into()); @@ -328,7 +332,7 @@ pub fn timestamp_to_sql(v: i64, buf: &mut Vec) { /// /// The value represents the number of microseconds since midnight, January 1st, 2000. #[inline] -pub fn timestamp_from_sql(mut buf: &[u8]) -> Result> { +pub fn timestamp_from_sql(mut buf: &[u8]) -> Result> { let v = try!(buf.read_i64::()); if !buf.is_empty() { return Err("invalid message length".into()); @@ -348,7 +352,7 @@ pub fn date_to_sql(v: i32, buf: &mut Vec) { /// /// The value represents the number of days since January 1st, 2000. #[inline] -pub fn date_from_sql(mut buf: &[u8]) -> Result> { +pub fn date_from_sql(mut buf: &[u8]) -> Result> { let v = try!(buf.read_i32::()); if !buf.is_empty() { return Err("invalid message length".into()); @@ -368,7 +372,7 @@ pub fn time_to_sql(v: i64, buf: &mut Vec) { /// /// The value represents the number of microseconds since midnight. #[inline] -pub fn time_from_sql(mut buf: &[u8]) -> Result> { +pub fn time_from_sql(mut buf: &[u8]) -> Result> { let v = try!(buf.read_i64::()); if !buf.is_empty() { return Err("invalid message length".into()); @@ -384,7 +388,7 @@ pub fn macaddr_to_sql(v: [u8; 6], buf: &mut Vec) { /// Deserializes a `MACADDR` value. #[inline] -pub fn macaddr_from_sql(buf: &[u8]) -> Result<[u8; 6], Box> { +pub fn macaddr_from_sql(buf: &[u8]) -> Result<[u8; 6], StdBox> { if buf.len() != 6 { return Err("invalid message length".into()); } @@ -401,7 +405,7 @@ pub fn uuid_to_sql(v: [u8; 16], buf: &mut Vec) { /// Deserializes a `UUID` value. #[inline] -pub fn uuid_from_sql(buf: &[u8]) -> Result<[u8; 16], Box> { +pub fn uuid_from_sql(buf: &[u8]) -> Result<[u8; 16], StdBox> { if buf.len() != 16 { return Err("invalid message length".into()); } @@ -418,10 +422,10 @@ pub fn array_to_sql(dimensions: I, elements: J, mut serializer: F, buf: &mut Vec) - -> Result<(), Box> + -> Result<(), StdBox> where I: IntoIterator, J: IntoIterator, - F: FnMut(T, &mut Vec) -> Result> + F: FnMut(T, &mut Vec) -> Result> { let dimensions_idx = buf.len(); buf.extend_from_slice(&[0; 4]); @@ -449,7 +453,7 @@ pub fn array_to_sql(dimensions: I, /// Deserializes an array value. #[inline] -pub fn array_from_sql<'a>(mut buf: &'a [u8]) -> Result, Box> { +pub fn array_from_sql<'a>(mut buf: &'a [u8]) -> Result, StdBox> { let dimensions = try!(buf.read_i32::()); if dimensions < 0 { return Err("invalid dimension count".into()); @@ -527,10 +531,10 @@ pub struct ArrayDimensions<'a>(&'a [u8]); impl<'a> FallibleIterator for ArrayDimensions<'a> { type Item = ArrayDimension; - type Error = Box; + type Error = StdBox; #[inline] - fn next(&mut self) -> Result, Box> { + fn next(&mut self) -> Result, StdBox> { if self.0.is_empty() { return Ok(None); } @@ -569,10 +573,10 @@ pub struct ArrayValues<'a> { impl<'a> FallibleIterator for ArrayValues<'a> { type Item = Option<&'a [u8]>; - type Error = Box; + type Error = StdBox; #[inline] - fn next(&mut self) -> Result>, Box> { + fn next(&mut self) -> Result>, StdBox> { if self.remaining == 0 { if !self.buf.is_empty() { return Err("invalid message length".into()); @@ -613,9 +617,9 @@ pub fn empty_range_to_sql(buf: &mut Vec) { pub fn range_to_sql(lower: F, upper: G, buf: &mut Vec) - -> Result<(), Box> - where F: FnOnce(&mut Vec) -> Result, Box>, - G: FnOnce(&mut Vec) -> Result, Box> + -> Result<(), StdBox> + where F: FnOnce(&mut Vec) -> Result, StdBox>, + G: FnOnce(&mut Vec) -> Result, StdBox> { let tag_idx = buf.len(); buf.push(0); @@ -638,8 +642,10 @@ pub fn range_to_sql(lower: F, Ok(()) } -fn write_bound(bound: F, buf: &mut Vec) -> Result, Box> - where F: FnOnce(&mut Vec) -> Result, Box> +fn write_bound(bound: F, + buf: &mut Vec) + -> Result, StdBox> + where F: FnOnce(&mut Vec) -> Result, StdBox> { let base = buf.len(); buf.extend_from_slice(&[0; 4]); @@ -676,7 +682,7 @@ pub enum RangeBound { /// Deserializes a range value. #[inline] -pub fn range_from_sql<'a>(mut buf: &'a [u8]) -> Result, Box> { +pub fn range_from_sql<'a>(mut buf: &'a [u8]) -> Result, StdBox> { let tag = try!(buf.read_u8()); if tag == RANGE_EMPTY { @@ -701,7 +707,7 @@ fn read_bound<'a>(buf: &mut &'a [u8], tag: u8, unbounded: u8, inclusive: u8) - -> Result>, Box> { + -> Result>, StdBox> { if tag & unbounded != 0 { Ok(RangeBound::Unbounded) } else { @@ -734,6 +740,191 @@ pub enum Range<'a> { Nonempty(RangeBound>, RangeBound>), } +/// Serializes a point value. +#[inline] +pub fn point_to_sql(x: f64, y: f64, buf: &mut Vec) { + buf.write_f64::(x).unwrap(); + buf.write_f64::(y).unwrap(); +} + +/// Deserializes a point value. +#[inline] +pub fn point_from_sql(mut buf: &[u8]) -> Result> { + let x = try!(buf.read_f64::()); + let y = try!(buf.read_f64::()); + if !buf.is_empty() { + return Err("invalid buffer size".into()); + } + Ok(Point { x: x, y: y }) +} + +/// A Postgres point. +#[derive(Copy, Clone)] +pub struct Point { + x: f64, + y: f64, +} + +impl Point { + /// Returns the x coordinate of the point. + #[inline] + pub fn x(&self) -> f64 { + self.x + } + + /// Returns the y coordinate of the point. + #[inline] + pub fn y(&self) -> f64 { + self.y + } +} + +/// Serializes a box value. +#[inline] +pub fn box_to_sql(x1: f64, y1: f64, x2: f64, y2: f64, buf: &mut Vec) { + buf.write_f64::(x1).unwrap(); + buf.write_f64::(y1).unwrap(); + buf.write_f64::(x2).unwrap(); + buf.write_f64::(y2).unwrap(); +} + +/// Deserializes a box value. +#[inline] +pub fn box_from_sql(mut buf: &[u8]) -> Result> { + let x1 = try!(buf.read_f64::()); + let y1 = try!(buf.read_f64::()); + let x2 = try!(buf.read_f64::()); + let y2 = try!(buf.read_f64::()); + if !buf.is_empty() { + return Err("invalid buffer size".into()); + } + Ok(Box { + upper_right: Point { x: x1, y: y1 }, + lower_left: Point { x: x2, y: y2 }, + }) +} + +/// A Postgres box. +#[derive(Copy, Clone)] +pub struct Box { + upper_right: Point, + lower_left: Point, +} + +impl Box { + /// Returns the upper right corner of the box. + #[inline] + pub fn upper_right(&self) -> Point { + self.upper_right + } + + /// Returns the lower left corner of the box. + #[inline] + pub fn lower_left(&self) -> Point { + self.lower_left + } +} + +/// Serializes a Postgres path. +#[inline] +pub fn path_to_sql(closed: bool, + points: I, + buf: &mut Vec) + -> Result<(), StdBox> + where I: IntoIterator +{ + buf.push(closed as u8); + let points_idx = buf.len(); + buf.extend_from_slice(&[0; 4]); + + let mut num_points = 0; + for (x, y) in points { + num_points += 1; + buf.write_f64::(x).unwrap(); + buf.write_f64::(y).unwrap(); + } + + let num_points = try!(i32::from_usize(num_points)); + (&mut buf[points_idx..]) + .write_i32::(num_points) + .unwrap(); + + Ok(()) +} + +/// Deserializes a Postgres path. +#[inline] +pub fn path_from_sql<'a>(mut buf: &'a [u8]) -> Result, StdBox> { + let closed = try!(buf.read_u8()) != 0; + let points = try!(buf.read_i32::()); + + Ok(Path { + closed: closed, + points: points, + buf: buf, + }) +} + +/// A Postgres point. +pub struct Path<'a> { + closed: bool, + points: i32, + buf: &'a [u8], +} + +impl<'a> Path<'a> { + /// Determines if the path is closed or open. + #[inline] + pub fn closed(&self) -> bool { + self.closed + } + + /// Returns an iterator over the points in the path. + #[inline] + pub fn points(&self) -> PathPoints<'a> { + PathPoints { + remaining: self.points, + buf: self.buf, + } + } +} + +/// An iterator over the points of a Postgres path. +pub struct PathPoints<'a> { + remaining: i32, + buf: &'a [u8], +} + +impl<'a> FallibleIterator for PathPoints<'a> { + type Item = Point; + type Error = StdBox; + + #[inline] + fn next(&mut self) -> Result, StdBox> { + if self.remaining == 0 { + if !self.buf.is_empty() { + return Err("invalid message length".into()); + } + return Ok(None); + } + self.remaining -= 1; + + let x = try!(self.buf.read_f64::()); + let y = try!(self.buf.read_f64::()); + + Ok(Some(Point { + x: x, + y: y, + })) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let len = self.remaining as usize; + (len, Some(len)) + } +} + #[cfg(test)] mod test { use std::collections::HashMap; @@ -829,15 +1020,13 @@ mod test { true, 10, values.iter().cloned(), - |v, buf| { - match v { - Some(v) => { - buf.extend_from_slice(v); - Ok(IsNull::No) - } - None => Ok(IsNull::Yes), - } - }, + |v, buf| match v { + Some(v) => { + buf.extend_from_slice(v); + Ok(IsNull::No) + } + None => Ok(IsNull::Yes), + }, &mut buf) .unwrap(); diff --git a/postgres-shared/src/types/geo.rs b/postgres-shared/src/types/geo.rs index 13080495..de7e6be4 100644 --- a/postgres-shared/src/types/geo.rs +++ b/postgres-shared/src/types/geo.rs @@ -3,18 +3,14 @@ extern crate geo; use postgres_protocol::types; use self::geo::{Bbox, LineString, Point}; use std::error::Error; +use fallible_iterator::FallibleIterator; use types::{FromSql, ToSql, IsNull, Type}; impl FromSql for Point { fn from_sql(_: &Type, raw: &[u8]) -> Result> { - if raw.len() != 16 { - return Err("invalid message length".into()); - } - - let x = types::float8_from_sql(&raw[0..8])?; - let y = types::float8_from_sql(&raw[8..16])?; - Ok(Point::new(x, y)) + let point = types::point_from_sql(raw)?; + Ok(Point::new(point.x(), point.y())) } accepts!(Type::Point); @@ -22,8 +18,7 @@ impl FromSql for Point { impl ToSql for Point { fn to_sql(&self, _: &Type, out: &mut Vec) -> Result> { - types::float8_to_sql(self.x(), out); - types::float8_to_sql(self.y(), out); + types::point_to_sql(self.x(), self.y(), out); Ok(IsNull::No) } @@ -33,15 +28,13 @@ impl ToSql for Point { impl FromSql for Bbox { fn from_sql(_: &Type, raw: &[u8]) -> Result> { - if raw.len() != 32 { - return Err("invalid message length".into()); - } - - let xmax = types::float8_from_sql(&raw[0..8])?; - let ymax = types::float8_from_sql(&raw[8..16])?; - let xmin = types::float8_from_sql(&raw[16..24])?; - let ymin = types::float8_from_sql(&raw[24..32])?; - Ok(Bbox{xmax: xmax, ymax: ymax, xmin: xmin, ymin: ymin}) + let bbox = types::box_from_sql(raw)?; + Ok(Bbox { + xmin: bbox.lower_left().x(), + xmax: bbox.upper_right().x(), + ymin: bbox.lower_left().y(), + ymax: bbox.upper_right().y(), + }) } accepts!(Type::Box); @@ -49,10 +42,7 @@ impl FromSql for Bbox { impl ToSql for Bbox { fn to_sql(&self, _: &Type, out: &mut Vec) -> Result> { - types::float8_to_sql(self.xmax, out); - types::float8_to_sql(self.ymax, out); - types::float8_to_sql(self.xmin, out); - types::float8_to_sql(self.ymin, out); + types::box_to_sql(self.xmin, self.ymin, self.xmax, self.ymax, out); Ok(IsNull::No) } @@ -62,25 +52,8 @@ impl ToSql for Bbox { impl FromSql for LineString { fn from_sql(_: &Type, raw: &[u8]) -> Result> { - if raw.len() < 5 { - return Err("invalid message length".into()); - } - - // let _ = types::bool_from_sql(&raw[0..1])?; // is path open or closed - let n_points = types::int4_from_sql(&raw[1..5])? as usize; - let raw_points = &raw[5..raw.len()]; - if raw_points.len() != 16 * n_points { - return Err("invalid message length".into()); - } - - let mut offset = 0; - let mut points = Vec::with_capacity(n_points); - for _ in 0..n_points { - let x = types::float8_from_sql(&raw_points[offset..offset+8])?; - let y = types::float8_from_sql(&raw_points[offset+8..offset+16])?; - points.push(Point::new(x, y)); - offset += 16; - } + let path = types::path_from_sql(raw)?; + let points = path.points().map(|p| Point::new(p.x(), p.y())).collect()?; Ok(LineString(points)) } @@ -90,12 +63,7 @@ impl FromSql for LineString { impl ToSql for LineString { fn to_sql(&self, _: &Type, out: &mut Vec) -> Result> { let closed = false; // always encode an open path from LineString - types::bool_to_sql(closed, out); - types::int4_to_sql(self.0.len() as i32, out); - for point in &self.0 { - types::float8_to_sql(point.x(), out); - types::float8_to_sql(point.y(), out); - } + types::path_to_sql(closed, self.0.iter().map(|p| (p.x(), p.y())), out)?; Ok(IsNull::No) }