sqlx_postgres/types/geometry/
polygon.rs

1use crate::decode::Decode;
2use crate::encode::{Encode, IsNull};
3use crate::error::BoxDynError;
4use crate::types::{PgPoint, Type};
5use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres};
6use sqlx_core::bytes::Buf;
7use sqlx_core::Error;
8use std::mem;
9use std::str::FromStr;
10
11const BYTE_WIDTH: usize = mem::size_of::<f64>();
12
13/// ## Postgres Geometric Polygon type
14///
15/// Description: Polygon (similar to closed polygon)
16/// Representation: `((x1,y1),...)`
17///
18/// Polygons are represented by lists of points (the vertexes of the polygon). Polygons are very similar to closed paths; the essential semantic difference is that a polygon is considered to include the area within it, while a path is not.
19/// An important implementation difference between polygons and paths is that the stored representation of a polygon includes its smallest bounding box. This speeds up certain search operations, although computing the bounding box adds overhead while constructing new polygons.
20/// Values of type polygon are specified using any of the following syntaxes:
21///
22/// ```text
23/// ( ( x1 , y1 ) , ... , ( xn , yn ) )
24///   ( x1 , y1 ) , ... , ( xn , yn )
25///   ( x1 , y1   , ... ,   xn , yn )
26///     x1 , y1   , ... ,   xn , yn
27/// ```
28///
29/// where the points are the end points of the line segments comprising the boundary of the polygon.
30///
31/// See [Postgres Manual, Section 8.8.6, Geometric Types - Polygons][PG.S.8.8.6] for details.
32///
33/// [PG.S.8.8.6]: https://www.postgresql.org/docs/current/datatype-geometric.html#DATATYPE-POLYGON
34///
35#[derive(Debug, Clone, PartialEq)]
36pub struct PgPolygon {
37    pub points: Vec<PgPoint>,
38}
39
40#[derive(Copy, Clone, Debug, PartialEq, Eq)]
41struct Header {
42    length: usize,
43}
44
45impl Type<Postgres> for PgPolygon {
46    fn type_info() -> PgTypeInfo {
47        PgTypeInfo::with_name("polygon")
48    }
49}
50
51impl PgHasArrayType for PgPolygon {
52    fn array_type_info() -> PgTypeInfo {
53        PgTypeInfo::with_name("_polygon")
54    }
55}
56
57impl<'r> Decode<'r, Postgres> for PgPolygon {
58    fn decode(value: PgValueRef<'r>) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
59        match value.format() {
60            PgValueFormat::Text => Ok(PgPolygon::from_str(value.as_str()?)?),
61            PgValueFormat::Binary => Ok(PgPolygon::from_bytes(value.as_bytes()?)?),
62        }
63    }
64}
65
66impl<'q> Encode<'q, Postgres> for PgPolygon {
67    fn produces(&self) -> Option<PgTypeInfo> {
68        Some(PgTypeInfo::with_name("polygon"))
69    }
70
71    fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
72        self.serialize(buf)?;
73        Ok(IsNull::No)
74    }
75}
76
77impl FromStr for PgPolygon {
78    type Err = Error;
79
80    fn from_str(s: &str) -> Result<Self, Self::Err> {
81        let sanitised = s.replace(['(', ')', '[', ']', ' '], "");
82        let parts = sanitised.split(',').collect::<Vec<_>>();
83
84        let mut points = vec![];
85
86        if parts.len() % 2 != 0 {
87            return Err(Error::Decode(
88                format!("Unmatched pair in POLYGON: {}", s).into(),
89            ));
90        }
91
92        for chunk in parts.chunks_exact(2) {
93            if let [x_str, y_str] = chunk {
94                let x = parse_float_from_str(x_str, "could not get x")?;
95                let y = parse_float_from_str(y_str, "could not get y")?;
96
97                let point = PgPoint { x, y };
98                points.push(point);
99            }
100        }
101
102        if !points.is_empty() {
103            return Ok(PgPolygon { points });
104        }
105
106        Err(Error::Decode(
107            format!("could not get polygon from {}", s).into(),
108        ))
109    }
110}
111
112impl PgPolygon {
113    fn header(&self) -> Header {
114        Header {
115            length: self.points.len(),
116        }
117    }
118
119    fn from_bytes(mut bytes: &[u8]) -> Result<Self, BoxDynError> {
120        let header = Header::try_read(&mut bytes)?;
121
122        if bytes.len() != header.data_size() {
123            return Err(format!(
124                "expected {} bytes after header, got {}",
125                header.data_size(),
126                bytes.len()
127            )
128            .into());
129        }
130
131        if bytes.len() % BYTE_WIDTH * 2 != 0 {
132            return Err(format!(
133                "data length not divisible by pairs of {BYTE_WIDTH}: {}",
134                bytes.len()
135            )
136            .into());
137        }
138
139        let mut out_points = Vec::with_capacity(bytes.len() / (BYTE_WIDTH * 2));
140        while bytes.has_remaining() {
141            let point = PgPoint {
142                x: bytes.get_f64(),
143                y: bytes.get_f64(),
144            };
145            out_points.push(point)
146        }
147        Ok(PgPolygon { points: out_points })
148    }
149
150    fn serialize(&self, buff: &mut PgArgumentBuffer) -> Result<(), BoxDynError> {
151        let header = self.header();
152        buff.reserve(header.data_size());
153        header.try_write(buff)?;
154
155        for point in &self.points {
156            buff.extend_from_slice(&point.x.to_be_bytes());
157            buff.extend_from_slice(&point.y.to_be_bytes());
158        }
159        Ok(())
160    }
161
162    #[cfg(test)]
163    fn serialize_to_vec(&self) -> Vec<u8> {
164        let mut buff = PgArgumentBuffer::default();
165        self.serialize(&mut buff).unwrap();
166        buff.to_vec()
167    }
168}
169
170impl Header {
171    const HEADER_WIDTH: usize = mem::size_of::<i8>() + mem::size_of::<i32>();
172
173    fn data_size(&self) -> usize {
174        self.length * BYTE_WIDTH * 2
175    }
176
177    fn try_read(buf: &mut &[u8]) -> Result<Self, String> {
178        if buf.len() < Self::HEADER_WIDTH {
179            return Err(format!(
180                "expected polygon data to contain at least {} bytes, got {}",
181                Self::HEADER_WIDTH,
182                buf.len()
183            ));
184        }
185
186        let length = buf.get_i32();
187
188        let length = usize::try_from(length).ok().ok_or_else(|| {
189            format!(
190                "received polygon with length: {length}. Expected length between 0 and  {}",
191                usize::MAX
192            )
193        })?;
194
195        Ok(Self { length })
196    }
197
198    fn try_write(&self, buff: &mut PgArgumentBuffer) -> Result<(), String> {
199        let length = i32::try_from(self.length).map_err(|_| {
200            format!(
201                "polygon length exceeds allowed maximum ({} > {})",
202                self.length,
203                i32::MAX
204            )
205        })?;
206
207        buff.extend(length.to_be_bytes());
208
209        Ok(())
210    }
211}
212
213fn parse_float_from_str(s: &str, error_msg: &str) -> Result<f64, Error> {
214    s.parse().map_err(|_| Error::Decode(error_msg.into()))
215}
216
217#[cfg(test)]
218mod polygon_tests {
219
220    use std::str::FromStr;
221
222    use crate::types::PgPoint;
223
224    use super::PgPolygon;
225
226    const POLYGON_BYTES: &[u8] = &[
227        0, 0, 0, 12, 192, 0, 0, 0, 0, 0, 0, 0, 192, 8, 0, 0, 0, 0, 0, 0, 191, 240, 0, 0, 0, 0, 0,
228        0, 192, 8, 0, 0, 0, 0, 0, 0, 191, 240, 0, 0, 0, 0, 0, 0, 191, 240, 0, 0, 0, 0, 0, 0, 63,
229        240, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0, 64, 8, 0, 0,
230        0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 64, 8, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 192,
231        8, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0, 192, 8, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0,
232        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 191, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 191,
233        240, 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, 0,
234        0, 0, 0,
235    ];
236
237    #[test]
238    fn can_deserialise_polygon_type_bytes() {
239        let polygon = PgPolygon::from_bytes(POLYGON_BYTES).unwrap();
240        assert_eq!(
241            polygon,
242            PgPolygon {
243                points: vec![
244                    PgPoint { x: -2., y: -3. },
245                    PgPoint { x: -1., y: -3. },
246                    PgPoint { x: -1., y: -1. },
247                    PgPoint { x: 1., y: 1. },
248                    PgPoint { x: 1., y: 3. },
249                    PgPoint { x: 2., y: 3. },
250                    PgPoint { x: 2., y: -3. },
251                    PgPoint { x: 1., y: -3. },
252                    PgPoint { x: 1., y: 0. },
253                    PgPoint { x: -1., y: 0. },
254                    PgPoint { x: -1., y: -2. },
255                    PgPoint { x: -2., y: -2. }
256                ]
257            }
258        )
259    }
260
261    #[test]
262    fn can_deserialise_polygon_type_str_first_syntax() {
263        let polygon = PgPolygon::from_str("[( 1, 2), (3, 4 )]").unwrap();
264        assert_eq!(
265            polygon,
266            PgPolygon {
267                points: vec![PgPoint { x: 1., y: 2. }, PgPoint { x: 3., y: 4. }]
268            }
269        );
270    }
271
272    #[test]
273    fn can_deserialise_polygon_type_str_second_syntax() {
274        let polygon = PgPolygon::from_str("(( 1, 2), (3, 4 ))").unwrap();
275        assert_eq!(
276            polygon,
277            PgPolygon {
278                points: vec![PgPoint { x: 1., y: 2. }, PgPoint { x: 3., y: 4. }]
279            }
280        );
281    }
282
283    #[test]
284    fn cannot_deserialise_polygon_type_str_uneven_points_first_syntax() {
285        let input_str = "[( 1, 2), (3)]";
286        let polygon = PgPolygon::from_str(input_str);
287
288        assert!(polygon.is_err());
289
290        if let Err(err) = polygon {
291            assert_eq!(
292                err.to_string(),
293                format!("error occurred while decoding: Unmatched pair in POLYGON: {input_str}")
294            )
295        }
296    }
297
298    #[test]
299    fn cannot_deserialise_polygon_type_str_invalid_numbers() {
300        let input_str = "[( 1, 2), (2, three)]";
301        let polygon = PgPolygon::from_str(input_str);
302
303        assert!(polygon.is_err());
304
305        if let Err(err) = polygon {
306            assert_eq!(
307                err.to_string(),
308                format!("error occurred while decoding: could not get y")
309            )
310        }
311    }
312
313    #[test]
314    fn can_deserialise_polygon_type_str_third_syntax() {
315        let polygon = PgPolygon::from_str("(1, 2), (3, 4 )").unwrap();
316        assert_eq!(
317            polygon,
318            PgPolygon {
319                points: vec![PgPoint { x: 1., y: 2. }, PgPoint { x: 3., y: 4. }]
320            }
321        );
322    }
323
324    #[test]
325    fn can_deserialise_polygon_type_str_fourth_syntax() {
326        let polygon = PgPolygon::from_str("1, 2, 3, 4").unwrap();
327        assert_eq!(
328            polygon,
329            PgPolygon {
330                points: vec![PgPoint { x: 1., y: 2. }, PgPoint { x: 3., y: 4. }]
331            }
332        );
333    }
334
335    #[test]
336    fn can_deserialise_polygon_type_str_float() {
337        let polygon = PgPolygon::from_str("(1.1, 2.2), (3.3, 4.4)").unwrap();
338        assert_eq!(
339            polygon,
340            PgPolygon {
341                points: vec![PgPoint { x: 1.1, y: 2.2 }, PgPoint { x: 3.3, y: 4.4 }]
342            }
343        );
344    }
345
346    #[test]
347    fn can_serialise_polygon_type() {
348        let polygon = PgPolygon {
349            points: vec![
350                PgPoint { x: -2., y: -3. },
351                PgPoint { x: -1., y: -3. },
352                PgPoint { x: -1., y: -1. },
353                PgPoint { x: 1., y: 1. },
354                PgPoint { x: 1., y: 3. },
355                PgPoint { x: 2., y: 3. },
356                PgPoint { x: 2., y: -3. },
357                PgPoint { x: 1., y: -3. },
358                PgPoint { x: 1., y: 0. },
359                PgPoint { x: -1., y: 0. },
360                PgPoint { x: -1., y: -2. },
361                PgPoint { x: -2., y: -2. },
362            ],
363        };
364        assert_eq!(polygon.serialize_to_vec(), POLYGON_BYTES,)
365    }
366}