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