diff --git a/sqlx-core/src/postgres/types/geo.rs b/sqlx-core/src/postgres/types/geo.rs index c3b519f21..7731076b3 100644 --- a/sqlx-core/src/postgres/types/geo.rs +++ b/sqlx-core/src/postgres/types/geo.rs @@ -1,60 +1,136 @@ -use crate::decode::Decode; -use crate::encode::Encode; -use crate::types::Type; -use crate::postgres::protocol::TypeId; -use crate::postgres::{ PgData, PgValue, PgRawBuffer, PgTypeInfo, Postgres }; -use crate::io::Buf; -use std::mem; -use geo::Coordinate; -use byteorder::BigEndian; +use crate::{ + decode::Decode, + encode::{Encode, IsNull}, + error::BoxDynError, + postgres::{PgArgumentBuffer, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}, + types::Type, +}; +use byteorder::{BigEndian, ByteOrder}; +use geo::{Line, Coordinate}; +use std::{mem, num::ParseFloatError}; // impl Type for Coordinate { fn type_info() -> PgTypeInfo { - PgTypeInfo::new(TypeId::POINT, "POINT") + PgTypeInfo::POINT } } -impl<'de> Decode<'de, Postgres> for Coordinate { - fn decode(value: PgValue<'de>) -> crate::Result { - match value.try_get()? { - PgData::Binary(mut buf) => { - let x = buf.get_f64::()?; - println!("we have what is hopefully x: {}", x); - - let y = buf.get_f64::()?; - println!("is this a y? {}", y); +impl Decode<'_, Postgres> for Coordinate { + fn decode(value: PgValueRef<'_>) -> Result { + match value.format() { + PgValueFormat::Binary => { + let buf = value.as_bytes()?; - Ok((x, y).into()) + decode_coordinate_binary(buf) } - PgData::Text(s) => { + PgValueFormat::Text => { + let s = value.as_str()?; + let parens: &[_] = &['(', ')']; let mut s = s.trim_matches(parens).split(','); match (s.next(), s.next()) { (Some(x), Some(y)) => { - let x = x.parse().map_err(crate::Error::decode)?; - let y = y.parse().map_err(crate::Error::decode)?; + let x = x + .parse() + .map_err(|e: ParseFloatError| crate::error::Error::Decode(e.into()))?; + let y = y + .parse() + .map_err(|e: ParseFloatError| crate::error::Error::Decode(e.into()))?; Ok((x, y).into()) } - _ => Err(crate::Error::Decode(format!("expecting a value with the format \"(x,y)\"").into())) + _ => Err(Box::new(crate::error::Error::Decode( + format!("expecting a value with the format \"(x,y)\"").into(), + ))), } } } } } -impl Encode for Coordinate { - fn encode(&self, buf: &mut PgRawBuffer) { - Encode::::encode(&self.x, buf); - Encode::::encode(&self.y, buf); +fn decode_coordinate_binary(buf: &[u8]) -> Result, BoxDynError> { + let x = BigEndian::read_f64(buf); + + let y = BigEndian::read_f64(buf); + + Ok((x, y).into()) +} + +impl Encode<'_, Postgres> for Coordinate { + fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull { + let _ = Encode::::encode(self.x, buf); + let _ = Encode::::encode(self.y, buf); + + IsNull::No } fn size_hint(&self) -> usize { 2 * mem::size_of::() } } + +impl Type for Line { + fn type_info() -> PgTypeInfo { + PgTypeInfo::LSEG + } +} + +impl Decode<'_, Postgres> for Line { + fn decode(value: PgValueRef<'_>) -> Result { + match value.format() { + PgValueFormat::Binary => { + let buf = value.as_bytes()?; + let start = decode_coordinate_binary(buf)?; + // buf.advance(Encode::::size_hint(&start)); + let end = decode_coordinate_binary(buf)?; + + Ok(Line::new(start, end)) + } + + // TODO: is there no way to make this make use of the Decode for Coordinate? + PgValueFormat::Text => { + let brackets: &[_] = &['[', ']']; + let mut s = value.as_str()? + .trim_matches(brackets) + .split(|c| c == '(' || c == ')' || c == ',') + .filter_map(|part| if part == "" { None } else { Some(part) }); + + match (s.next(), s.next(), s.next(), s.next()) { + (Some(x1), Some(y1), Some(x2), Some(y2)) => { + let x1 = x1.parse().map_err(|e: ParseFloatError| crate::error::Error::Decode(e.into()))?; + let y1 = y1.parse().map_err(|e: ParseFloatError| crate::error::Error::Decode(e.into()))?; + let x2 = x2.parse().map_err(|e: ParseFloatError| crate::error::Error::Decode(e.into()))?; + let y2 = y2.parse().map_err(|e: ParseFloatError| crate::error::Error::Decode(e.into()))?; + + let start = Coordinate::from((x1, y1)); + let end = Coordinate::from((x2, y2)); + + Ok(Line::new(start, end)) + } + + _ => Err(Box::new(crate::error::Error::Decode( + format!("expecting a value with the format \"[(x,y),(x,y)]\"").into(), + ))), + } + } + } + } +} + +impl Encode<'_, Postgres> for Line { + fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull { + let _ = Encode::::encode_by_ref(&self.start, buf); + let _ = Encode::::encode_by_ref(&self.end, buf); + + IsNull::No + } + + fn size_hint(&self) -> usize { + 2 * Encode::::size_hint(&self.start) + } +} diff --git a/sqlx-core/src/postgres/types/mod.rs b/sqlx-core/src/postgres/types/mod.rs index b41f56468..268499436 100644 --- a/sqlx-core/src/postgres/types/mod.rs +++ b/sqlx-core/src/postgres/types/mod.rs @@ -94,7 +94,7 @@ //! [`Json`] can be used for structured JSON data with Postgres. //! //! [`Json`]: crate::types::Json -//! +//! //! ### [`geo`](https://crates.io/crates/geo) //! //! Requires the `geo` Cargo feature flag. @@ -102,7 +102,7 @@ //! | Rust type | Postgres type(s) | //! |---------------------------------------|------------------------------------------------------| //! | `geo::Coordinate` | POINT | -//! | `geo::Line` | LINE, LSEG | +//! | `geo::Line` | LSEG | //! | `geo::Rect` | BOX | //! | `geo::LineString` | PATH | //! | `geo::Polygon` | POLYGON |