sqlx_postgres/types/geometry/
point.rs

1use crate::decode::Decode;
2use crate::encode::{Encode, IsNull};
3use crate::error::BoxDynError;
4use crate::types::Type;
5use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres};
6use sqlx_core::bytes::Buf;
7use sqlx_core::Error;
8use std::str::FromStr;
9
10/// ## Postgres Geometric Point type
11///
12/// Description: Point on a plane
13/// Representation: `(x, y)`
14///
15/// Points are the fundamental two-dimensional building block for geometric types. Values of type point are specified using either of the following syntaxes:
16/// ```text
17/// ( x , y )
18///  x , y
19/// ````
20/// where x and y are the respective coordinates, as floating-point numbers.
21///
22/// See [Postgres Manual, Section 8.8.1, Geometric Types - Points][PG.S.8.8.1] for details.
23///
24/// [PG.S.8.8.1]: https://www.postgresql.org/docs/current/datatype-geometric.html#DATATYPE-GEOMETRIC-POINTS
25///
26#[derive(Debug, Clone, PartialEq)]
27pub struct PgPoint {
28    pub x: f64,
29    pub y: f64,
30}
31
32impl Type<Postgres> for PgPoint {
33    fn type_info() -> PgTypeInfo {
34        PgTypeInfo::with_name("point")
35    }
36}
37
38impl PgHasArrayType for PgPoint {
39    fn array_type_info() -> PgTypeInfo {
40        PgTypeInfo::with_name("_point")
41    }
42}
43
44impl<'r> Decode<'r, Postgres> for PgPoint {
45    fn decode(value: PgValueRef<'r>) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
46        match value.format() {
47            PgValueFormat::Text => Ok(PgPoint::from_str(value.as_str()?)?),
48            PgValueFormat::Binary => Ok(PgPoint::from_bytes(value.as_bytes()?)?),
49        }
50    }
51}
52
53impl<'q> Encode<'q, Postgres> for PgPoint {
54    fn produces(&self) -> Option<PgTypeInfo> {
55        Some(PgTypeInfo::with_name("point"))
56    }
57
58    fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
59        self.serialize(buf)?;
60        Ok(IsNull::No)
61    }
62}
63
64fn parse_float_from_str(s: &str, error_msg: &str) -> Result<f64, Error> {
65    s.trim()
66        .parse()
67        .map_err(|_| Error::Decode(error_msg.into()))
68}
69
70impl FromStr for PgPoint {
71    type Err = BoxDynError;
72
73    fn from_str(s: &str) -> Result<Self, Self::Err> {
74        let (x_str, y_str) = s
75            .trim_matches(|c| c == '(' || c == ')' || c == ' ')
76            .split_once(',')
77            .ok_or_else(|| format!("error decoding POINT: could not get x and y from {}", s))?;
78
79        let x = parse_float_from_str(x_str, "error decoding POINT: could not get x")?;
80        let y = parse_float_from_str(y_str, "error decoding POINT: could not get y")?;
81
82        Ok(PgPoint { x, y })
83    }
84}
85
86impl PgPoint {
87    fn from_bytes(mut bytes: &[u8]) -> Result<PgPoint, BoxDynError> {
88        let x = bytes.get_f64();
89        let y = bytes.get_f64();
90        Ok(PgPoint { x, y })
91    }
92
93    fn serialize(&self, buff: &mut PgArgumentBuffer) -> Result<(), BoxDynError> {
94        buff.extend_from_slice(&self.x.to_be_bytes());
95        buff.extend_from_slice(&self.y.to_be_bytes());
96        Ok(())
97    }
98
99    #[cfg(test)]
100    fn serialize_to_vec(&self) -> Vec<u8> {
101        let mut buff = PgArgumentBuffer::default();
102        self.serialize(&mut buff).unwrap();
103        buff.to_vec()
104    }
105}
106
107#[cfg(test)]
108mod point_tests {
109
110    use std::str::FromStr;
111
112    use super::PgPoint;
113
114    const POINT_BYTES: &[u8] = &[
115        64, 0, 204, 204, 204, 204, 204, 205, 64, 20, 204, 204, 204, 204, 204, 205,
116    ];
117
118    #[test]
119    fn can_deserialise_point_type_bytes() {
120        let point = PgPoint::from_bytes(POINT_BYTES).unwrap();
121        assert_eq!(point, PgPoint { x: 2.1, y: 5.2 })
122    }
123
124    #[test]
125    fn can_deserialise_point_type_str() {
126        let point = PgPoint::from_str("(2, 3)").unwrap();
127        assert_eq!(point, PgPoint { x: 2., y: 3. });
128    }
129
130    #[test]
131    fn can_deserialise_point_type_str_float() {
132        let point = PgPoint::from_str("(2.5, 3.4)").unwrap();
133        assert_eq!(point, PgPoint { x: 2.5, y: 3.4 });
134    }
135
136    #[test]
137    fn can_serialise_point_type() {
138        let point = PgPoint { x: 2.1, y: 5.2 };
139        assert_eq!(point.serialize_to_vec(), POINT_BYTES,)
140    }
141}