postgis/
postgis.rs

1//
2// Copyright (c) ShuYu Wang <andelf@gmail.com>, Feather Workshop and Pirmin Kalberer. All rights reserved.
3//
4
5use crate::{
6    ewkb::{
7        self, AsEwkbGeometry, AsEwkbGeometryCollection, AsEwkbLineString, AsEwkbMultiLineString,
8        AsEwkbMultiPoint, AsEwkbMultiPolygon, AsEwkbPoint, AsEwkbPolygon, EwkbRead, EwkbWrite,
9    },
10    twkb::{self, TwkbGeom},
11    types::{LineString, Point, Polygon},
12};
13use bytes::{BufMut, BytesMut};
14use postgres_types::{accepts, to_sql_checked, FromSql, IsNull, ToSql, Type};
15use std::error::Error;
16use std::io::Cursor;
17
18macro_rules! accepts_geography {
19    () => {
20        fn accepts(ty: &Type) -> bool {
21            match ty.name() {
22                "geography" | "geometry" => true,
23                _ => false,
24            }
25        }
26    };
27}
28
29impl<'a> ToSql for ewkb::EwkbPoint<'a> {
30    fn to_sql(&self, _: &Type, out: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
31        self.write_ewkb(&mut out.writer())?;
32        Ok(IsNull::No)
33    }
34
35    accepts_geography!();
36    to_sql_checked!();
37}
38
39macro_rules! impl_sql_for_point_type {
40    ($ptype:ident) => {
41        impl<'a> FromSql<'a> for ewkb::$ptype {
42            fn from_sql(ty: &Type, raw: &[u8]) -> Result<Self, Box<dyn Error + Sync + Send>> {
43                let mut rdr = Cursor::new(raw);
44                ewkb::$ptype::read_ewkb(&mut rdr)
45                    .map_err(|_| format!("cannot convert {} to {}", ty, stringify!($ptype)).into())
46            }
47
48            accepts_geography!();
49        }
50
51        impl ToSql for ewkb::$ptype {
52            fn to_sql(
53                &self,
54                _: &Type,
55                out: &mut BytesMut,
56            ) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
57                self.as_ewkb().write_ewkb(&mut out.writer())?;
58                Ok(IsNull::No)
59            }
60
61            to_sql_checked!();
62            accepts_geography!();
63        }
64    };
65}
66
67impl_sql_for_point_type!(Point);
68impl_sql_for_point_type!(PointZ);
69impl_sql_for_point_type!(PointM);
70impl_sql_for_point_type!(PointZM);
71
72macro_rules! impl_sql_for_geom_type {
73    ($geotype:ident) => {
74        impl<'a, T> FromSql<'a> for ewkb::$geotype<T>
75        where
76            T: 'a + Point + EwkbRead,
77        {
78            fn from_sql(ty: &Type, raw: &[u8]) -> Result<Self, Box<dyn Error + Sync + Send>> {
79                let mut rdr = Cursor::new(raw);
80                ewkb::$geotype::<T>::read_ewkb(&mut rdr).map_err(|_| {
81                    format!("cannot convert {} to {}", ty, stringify!($geotype)).into()
82                })
83            }
84
85            accepts_geography!();
86        }
87
88        impl<'a, T> ToSql for ewkb::$geotype<T>
89        where
90            T: 'a + Point + EwkbRead,
91        {
92            fn to_sql(
93                &self,
94                _: &Type,
95                out: &mut BytesMut,
96            ) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
97                self.as_ewkb().write_ewkb(&mut out.writer())?;
98                Ok(IsNull::No)
99            }
100
101            to_sql_checked!();
102            accepts_geography!();
103        }
104    };
105}
106
107impl_sql_for_geom_type!(LineStringT);
108impl_sql_for_geom_type!(PolygonT);
109impl_sql_for_geom_type!(MultiPointT);
110impl_sql_for_geom_type!(MultiLineStringT);
111impl_sql_for_geom_type!(MultiPolygonT);
112
113macro_rules! impl_sql_for_ewkb_type {
114    ($ewkbtype:ident contains points) => {
115        impl<'a, T, I> ToSql for ewkb::$ewkbtype<'a, T, I>
116        where
117            T: 'a + Point,
118            I: 'a + Iterator<Item = &'a T> + ExactSizeIterator<Item = &'a T>,
119        {
120            fn to_sql(
121                &self,
122                _: &Type,
123                out: &mut BytesMut,
124            ) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
125                self.write_ewkb(&mut out.writer())?;
126                Ok(IsNull::No)
127            }
128
129            to_sql_checked!();
130            accepts_geography!();
131        }
132    };
133    ($ewkbtype:ident contains $itemtypetrait:ident) => {
134        impl<'a, P, I, T, J> ToSql for ewkb::$ewkbtype<'a, P, I, T, J>
135        where
136            P: 'a + Point,
137            I: 'a + Iterator<Item = &'a P> + ExactSizeIterator<Item = &'a P>,
138            T: 'a + $itemtypetrait<'a, ItemType = P, Iter = I>,
139            J: 'a + Iterator<Item = &'a T> + ExactSizeIterator<Item = &'a T>,
140        {
141            fn to_sql(
142                &self,
143                _: &Type,
144                out: &mut BytesMut,
145            ) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
146                self.write_ewkb(&mut out.writer())?;
147                Ok(IsNull::No)
148            }
149
150            to_sql_checked!();
151            accepts_geography!();
152        }
153    };
154    (multipoly $ewkbtype:ident contains $itemtypetrait:ident) => {
155        impl<'a, P, I, L, K, T, J> ToSql for ewkb::$ewkbtype<'a, P, I, L, K, T, J>
156        where
157            P: 'a + Point,
158            I: 'a + Iterator<Item = &'a P> + ExactSizeIterator<Item = &'a P>,
159            L: 'a + LineString<'a, ItemType = P, Iter = I>,
160            K: 'a + Iterator<Item = &'a L> + ExactSizeIterator<Item = &'a L>,
161            T: 'a + $itemtypetrait<'a, ItemType = L, Iter = K>,
162            J: 'a + Iterator<Item = &'a T> + ExactSizeIterator<Item = &'a T>,
163        {
164            to_sql_checked!();
165            accepts_geography!();
166            fn to_sql(
167                &self,
168                _: &Type,
169                out: &mut BytesMut,
170            ) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
171                self.write_ewkb(&mut out.writer())?;
172                Ok(IsNull::No)
173            }
174        }
175    };
176}
177
178impl_sql_for_ewkb_type!(EwkbLineString contains points);
179impl_sql_for_ewkb_type!(EwkbPolygon contains LineString);
180impl_sql_for_ewkb_type!(EwkbMultiPoint contains points);
181impl_sql_for_ewkb_type!(EwkbMultiLineString contains LineString);
182impl_sql_for_ewkb_type!(multipoly EwkbMultiPolygon contains Polygon);
183
184impl<'a, P> FromSql<'a> for ewkb::GeometryT<P>
185where
186    P: Point + EwkbRead,
187{
188    fn from_sql(ty: &Type, raw: &[u8]) -> Result<Self, Box<dyn Error + Sync + Send>> {
189        let mut rdr = Cursor::new(raw);
190        ewkb::GeometryT::<P>::read_ewkb(&mut rdr)
191            .map_err(|_| format!("cannot convert {} to {}", ty, stringify!(P)).into())
192    }
193
194    accepts_geography!();
195}
196
197// NOTE: Implement once per point type because AsEwkbPoint<'a> doesn't live long enough for ToSql
198macro_rules! impl_geometry_to_sql {
199    ($ptype:path) => {
200        impl ToSql for ewkb::GeometryT<$ptype> {
201            fn to_sql(
202                &self,
203                _: &Type,
204                out: &mut BytesMut,
205            ) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
206                self.as_ewkb().write_ewkb(&mut out.writer())?;
207                Ok(IsNull::No)
208            }
209
210            to_sql_checked!();
211            accepts_geography!();
212        }
213    };
214}
215
216impl_geometry_to_sql!(ewkb::Point);
217impl_geometry_to_sql!(ewkb::PointZ);
218impl_geometry_to_sql!(ewkb::PointM);
219impl_geometry_to_sql!(ewkb::PointZM);
220
221impl<'a, P> FromSql<'a> for ewkb::GeometryCollectionT<P>
222where
223    P: Point + EwkbRead,
224{
225    fn from_sql(ty: &Type, raw: &[u8]) -> Result<Self, Box<dyn Error + Sync + Send>> {
226        let mut rdr = Cursor::new(raw);
227        ewkb::GeometryCollectionT::<P>::read_ewkb(&mut rdr)
228            .map_err(|_| format!("cannot convert {} to {}", ty, stringify!(P)).into())
229    }
230
231    accepts_geography!();
232}
233
234impl<'a, P> ToSql for ewkb::GeometryCollectionT<P>
235where
236    P: Point + EwkbRead,
237{
238    fn to_sql(&self, _: &Type, out: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
239        self.as_ewkb().write_ewkb(&mut out.writer())?;
240        Ok(IsNull::No)
241    }
242
243    to_sql_checked!();
244    accepts_geography!();
245}
246
247// --- TWKB ---
248
249impl<'a> FromSql<'a> for twkb::Point {
250    fn from_sql(ty: &Type, raw: &[u8]) -> Result<Self, Box<dyn Error + Sync + Send>> {
251        let mut rdr = Cursor::new(raw);
252        twkb::Point::read_twkb(&mut rdr)
253            .map_err(|_| format!("cannot convert {} to Point", ty).into())
254    }
255
256    accepts!(BYTEA);
257}
258
259impl<'a> FromSql<'a> for twkb::LineString {
260    fn from_sql(ty: &Type, raw: &[u8]) -> Result<Self, Box<dyn Error + Sync + Send>> {
261        let mut rdr = Cursor::new(raw);
262        twkb::LineString::read_twkb(&mut rdr)
263            .map_err(|_| format!("cannot convert {} to LineString", ty).into())
264    }
265
266    accepts!(BYTEA);
267}
268
269impl<'a> FromSql<'a> for twkb::Polygon {
270    fn from_sql(ty: &Type, raw: &[u8]) -> Result<Self, Box<dyn Error + Sync + Send>> {
271        let mut rdr = Cursor::new(raw);
272        twkb::Polygon::read_twkb(&mut rdr)
273            .map_err(|_| format!("cannot convert {} to Polygon", ty).into())
274    }
275
276    accepts!(BYTEA);
277}
278
279impl<'a> FromSql<'a> for twkb::MultiPoint {
280    accepts!(BYTEA);
281    fn from_sql(ty: &Type, raw: &[u8]) -> Result<Self, Box<dyn Error + Sync + Send>> {
282        let mut rdr = Cursor::new(raw);
283        twkb::MultiPoint::read_twkb(&mut rdr)
284            .map_err(|_| format!("cannot convert {} to MultiPoint", ty).into())
285    }
286}
287
288impl<'a> FromSql<'a> for twkb::MultiLineString {
289    fn from_sql(ty: &Type, raw: &[u8]) -> Result<Self, Box<dyn Error + Sync + Send>> {
290        let mut rdr = Cursor::new(raw);
291        twkb::MultiLineString::read_twkb(&mut rdr)
292            .map_err(|_| format!("cannot convert {} to MultiLineString", ty).into())
293    }
294
295    accepts!(BYTEA);
296}
297
298impl<'a> FromSql<'a> for twkb::MultiPolygon {
299    fn from_sql(ty: &Type, raw: &[u8]) -> Result<Self, Box<dyn Error + Sync + Send>> {
300        let mut rdr = Cursor::new(raw);
301        twkb::MultiPolygon::read_twkb(&mut rdr)
302            .map_err(|_| format!("cannot convert {} to MultiPolygon", ty).into())
303    }
304
305    accepts!(BYTEA);
306}
307
308#[cfg(test)]
309mod tests {
310    use crate::{
311        ewkb::{self, AsEwkbLineString, AsEwkbPoint},
312        twkb, types as postgis,
313    };
314    use postgres::{Client, NoTls};
315    use std::env;
316
317    macro_rules! or_panic {
318        ($e:expr) => {
319            match $e {
320                Ok(ok) => ok,
321                Err(err) => panic!("{:#?}", err),
322            }
323        };
324    }
325
326    fn connect() -> Client {
327        match env::var("DBCONN") {
328            Result::Ok(val) => Client::connect(&val as &str, NoTls),
329            Result::Err(err) => panic!("{:#?}", err),
330        }
331        .unwrap()
332    }
333
334    #[test]
335    #[ignore]
336    #[cfg_attr(rustfmt, rustfmt_skip)]
337    fn test_insert_point() {
338        let mut client = connect();
339        or_panic!(client.execute("CREATE TEMPORARY TABLE geomtests (geom geometry(Point))", &[]));
340
341        // 'POINT (10 -20)'
342        let point = ewkb::Point { x: 10.0, y: -20.0, srid: None };
343        or_panic!(client.execute("INSERT INTO geomtests (geom) VALUES ($1)", &[&point]));
344        let result = or_panic!(client.query("SELECT geom=ST_GeomFromEWKT('POINT(10 -20)') FROM geomtests", &[]));
345        assert!(result.iter().map(|r| r.get::<_, bool>(0)).last().unwrap());
346        or_panic!(client.execute("TRUNCATE geomtests", &[]));
347
348        // With SRID
349        let point = ewkb::Point { x: 10.0, y: -20.0, srid: Some(4326) };
350        or_panic!(client.execute("INSERT INTO geomtests (geom) VALUES ($1)", &[&point]));
351        let result = or_panic!(client.query("SELECT geom=ST_GeomFromEWKT('SRID=4326;POINT(10 -20)') FROM geomtests", &[]));
352        assert!(result.iter().map(|r| r.get::<_, bool>(0)).last().unwrap());
353        or_panic!(client.execute("TRUNCATE geomtests", &[]));
354
355        let mut client = connect();
356        or_panic!(client.execute("CREATE TEMPORARY TABLE geomtests (geom geometry(Point, 4326))", &[]));
357
358        let point = ewkb::Point { x: 10.0, y: -20.0, srid: Some(4326) };
359        or_panic!(client.execute("INSERT INTO geomtests (geom) VALUES ($1)", &[&point]));
360        let result = or_panic!(client.query("SELECT geom=ST_GeomFromEWKT('SRID=4326;POINT(10 -20)') FROM geomtests", &[]));
361        assert!(result.iter().map(|r| r.get::<_, bool>(0)).last().unwrap());
362        or_panic!(client.execute("TRUNCATE geomtests", &[]));
363
364        // Missing SRID
365        let point = ewkb::Point { x: 10.0, y: -20.0, srid: None };
366        let result = client.execute("INSERT INTO geomtests (geom) VALUES ($1)", &[&point]);
367        assert!(format!("{}", result.err().unwrap()).starts_with("db error"));
368    }
369
370    #[test]
371    #[ignore]
372    #[cfg_attr(rustfmt, rustfmt_skip)]
373    fn test_insert_line() {
374        let mut client = connect();
375        or_panic!(client.execute("CREATE TEMPORARY TABLE geomtests (geom geometry(LineString))", &[]));
376
377        let p = |x, y| ewkb::Point { x: x, y: y, srid: None };
378        // 'LINESTRING (10 -20, -0 -0.5)'
379        let line = ewkb::LineString {srid: None, points: vec![p(10.0, -20.0), p(0., -0.5)]};
380        or_panic!(client.execute("INSERT INTO geomtests (geom) VALUES ($1)", &[&line]));
381        let result = or_panic!(client.query("SELECT geom=ST_GeomFromEWKT('LINESTRING(10 -20, 0 -0.5)') FROM geomtests", &[]));
382        assert!(result.iter().map(|r| r.get::<_, bool>(0)).last().unwrap());
383        or_panic!(client.execute("TRUNCATE geomtests", &[]));
384
385        let mut client = connect();
386        or_panic!(client.execute("CREATE TEMPORARY TABLE geomtests (geom geometry(LineString, 4326))", &[]));
387
388        // 'SRID=4326;LINESTRING (10 -20, -0 -0.5)'
389        let line = ewkb::LineString {srid: Some(4326), points: vec![p(10.0, -20.0), p(0., -0.5)]};
390        or_panic!(client.execute("INSERT INTO geomtests (geom) VALUES ($1)", &[&line]));
391        let result = or_panic!(client.query("SELECT geom=ST_GeomFromEWKT('SRID=4326;LINESTRING(10 -20, 0 -0.5)') FROM geomtests", &[]));
392        assert!(result.iter().map(|r| r.get::<_, bool>(0)).last().unwrap());
393        or_panic!(client.execute("TRUNCATE geomtests", &[]));
394
395        let mut client = connect();
396        or_panic!(client.execute("CREATE TEMPORARY TABLE geomtests (geom geometry(LineStringZ, 4326))", &[]));
397
398        let p = |x, y, z| ewkb::PointZ { x: x, y: y, z: z, srid: Some(4326) };
399        // 'SRID=4326;LINESTRING (10 -20 100, -0 -0.5 101)'
400        let line = ewkb::LineStringZ {srid: Some(4326), points: vec![p(10.0, -20.0, 100.0), p(0., -0.5, 101.0)]};
401        or_panic!(client.execute("INSERT INTO geomtests (geom) VALUES ($1)", &[&line]));
402        let result = or_panic!(client.query("SELECT geom=ST_GeomFromEWKT('SRID=4326;LINESTRING (10 -20 100, 0 -0.5 101)') FROM geomtests", &[]));
403        assert!(result.iter().map(|r| r.get::<_, bool>(0)).last().unwrap());
404        or_panic!(client.execute("TRUNCATE geomtests", &[]));
405    }
406
407    #[test]
408    #[ignore]
409    #[cfg_attr(rustfmt, rustfmt_skip)]
410    fn test_insert_polygon() {
411        let mut client = connect();
412        or_panic!(client.execute("CREATE TEMPORARY TABLE geomtests (geom geometry(Polygon))", &[]));
413        let p = |x, y| ewkb::Point { x: x, y: y, srid: Some(4326) };
414        // SELECT 'SRID=4326;POLYGON ((0 0, 2 0, 2 2, 0 2, 0 0))'::geometry
415        let line = ewkb::LineString {srid: Some(4326), points: vec![p(0., 0.), p(2., 0.), p(2., 2.), p(0., 2.), p(0., 0.)]};
416        let poly = ewkb::Polygon {srid: Some(4326), rings: vec![line]};
417        or_panic!(client.execute("INSERT INTO geomtests (geom) VALUES ($1)", &[&poly]));
418        let result = or_panic!(client.query("SELECT geom=ST_GeomFromEWKT('SRID=4326;POLYGON ((0 0, 2 0, 2 2, 0 2, 0 0))') FROM geomtests", &[]));
419        assert!(result.iter().map(|r| r.get::<_, bool>(0)).last().unwrap());
420    }
421
422    #[test]
423    #[ignore]
424    #[cfg_attr(rustfmt, rustfmt_skip)]
425    fn test_insert_multipoint() {
426        let mut client = connect();
427        or_panic!(client.execute("CREATE TEMPORARY TABLE geomtests (geom geometry(MultiPointZ))", &[]));
428        let p = |x, y, z| ewkb::PointZ { x: x, y: y, z: z, srid: Some(4326) };
429        // SELECT 'SRID=4326;MULTIPOINT ((10 -20 100), (0 -0.5 101))'::geometry
430        let points = ewkb::MultiPointZ {srid: Some(4326), points: vec![p(10.0, -20.0, 100.0), p(0., -0.5, 101.0)]};
431        or_panic!(client.execute("INSERT INTO geomtests (geom) VALUES ($1)", &[&points]));
432        let result = or_panic!(client.query("SELECT geom=ST_GeomFromEWKT('SRID=4326;MULTIPOINT ((10 -20 100), (0 -0.5 101))') FROM geomtests", &[]));
433        assert!(result.iter().map(|r| r.get::<_, bool>(0)).last().unwrap());
434    }
435
436    #[test]
437    #[ignore]
438    #[cfg_attr(rustfmt, rustfmt_skip)]
439    fn test_insert_multiline() {
440        let mut client = connect();
441        or_panic!(client.execute("CREATE TEMPORARY TABLE geomtests (geom geometry(MultiLineString))", &[]));
442        let p = |x, y| ewkb::Point { x: x, y: y, srid: Some(4326) };
443        // SELECT 'SRID=4326;MULTILINESTRING ((10 -20, 0 -0.5), (0 0, 2 0))'::geometry
444        let line1 = ewkb::LineString {srid: Some(4326), points: vec![p(10.0, -20.0), p(0., -0.5)]};
445        let line2 = ewkb::LineString {srid: Some(4326), points: vec![p(0., 0.), p(2., 0.)]};
446        let multiline = ewkb::MultiLineString {srid: Some(4326),lines: vec![line1, line2]};
447        or_panic!(client.execute("INSERT INTO geomtests (geom) VALUES ($1)", &[&multiline]));
448        let result = or_panic!(client.query("SELECT geom=ST_GeomFromEWKT('SRID=4326;MULTILINESTRING ((10 -20, 0 -0.5), (0 0, 2 0))') FROM geomtests", &[]));
449        assert!(result.iter().map(|r| r.get::<_, bool>(0)).last().unwrap());
450    }
451
452    #[test]
453    #[ignore]
454    #[cfg_attr(rustfmt, rustfmt_skip)]
455    fn test_insert_multipolygon() {
456        let mut client = connect();
457        or_panic!(client.execute("CREATE TEMPORARY TABLE geomtests (geom geometry(MultiPolygon))", &[]));
458        let p = |x, y| ewkb::Point { x: x, y: y, srid: Some(4326) };
459        // SELECT 'SRID=4326;MULTIPOLYGON (((0 0, 2 0, 2 2, 0 2, 0 0)), ((10 10, -2 10, -2 -2, 10 -2, 10 10)))'::geometry
460        let line = ewkb::LineString {srid: Some(4326), points: vec![p(0., 0.), p(2., 0.), p(2., 2.), p(0., 2.), p(0., 0.)]};
461        let poly1 = ewkb::Polygon {srid: Some(4326), rings: vec![line]};
462        let line = ewkb::LineString {srid: Some(4326), points: vec![p(10., 10.), p(-2., 10.), p(-2., -2.), p(10., -2.), p(10., 10.)]};
463        let poly2 = ewkb::Polygon {srid: Some(4326), rings: vec![line]};
464        let multipoly = ewkb::MultiPolygon {srid: Some(4326), polygons: vec![poly1, poly2]};
465        or_panic!(client.execute("INSERT INTO geomtests (geom) VALUES ($1)", &[&multipoly]));
466        let result = or_panic!(client.query("SELECT geom=ST_GeomFromEWKT('SRID=4326;MULTIPOLYGON (((0 0, 2 0, 2 2, 0 2, 0 0)), ((10 10, -2 10, -2 -2, 10 -2, 10 10)))') FROM geomtests", &[]));
467        assert!(result.iter().map(|r| r.get::<_, bool>(0)).last().unwrap());
468    }
469
470    #[test]
471    #[ignore]
472    #[cfg_attr(rustfmt, rustfmt_skip)]
473    fn test_insert_geometry() {
474        let mut client = connect();
475        or_panic!(client.execute("CREATE TEMPORARY TABLE geomtests (geom geometry)", &[]));
476        let p = |x, y| ewkb::Point { x: x, y: y, srid: Some(4326) };
477        // SELECT 'SRID=4326;MULTIPOLYGON (((0 0, 2 0, 2 2, 0 2, 0 0)), ((10 10, -2 10, -2 -2, 10 -2, 10 10)))'::geometry
478        let multipoly = {
479            let line = ewkb::LineString {srid: Some(4326), points: vec![p(0., 0.), p(2., 0.), p(2., 2.), p(0., 2.), p(0., 0.)]};
480            let poly1 = ewkb::Polygon {srid: Some(4326), rings: vec![line]};
481            let line = ewkb::LineString {srid: Some(4326), points: vec![p(10., 10.), p(-2., 10.), p(-2., -2.), p(10., -2.), p(10., 10.)]};
482            let poly2 = ewkb::Polygon {srid: Some(4326), rings: vec![line]};
483            ewkb::MultiPolygon {srid: Some(4326), polygons: vec![poly1, poly2]}
484        };
485        let geometry = ewkb::GeometryT::MultiPolygon(multipoly);
486        or_panic!(client.execute("INSERT INTO geomtests (geom) VALUES ($1)", &[&geometry]));
487        let result = or_panic!(client.query("SELECT geom=ST_GeomFromEWKT('SRID=4326;MULTIPOLYGON (((0 0, 2 0, 2 2, 0 2, 0 0)), ((10 10, -2 10, -2 -2, 10 -2, 10 10)))') FROM geomtests", &[]));
488        assert!(result.iter().map(|r| r.get::<_, bool>(0)).last().unwrap());
489    }
490
491    #[test]
492    #[ignore]
493    #[cfg_attr(rustfmt, rustfmt_skip)]
494    fn test_insert_geometrycollection() {
495        let mut client = connect();
496        or_panic!(client.execute("CREATE TEMPORARY TABLE geomtests (geom geometry(GeometryCollection))", &[]));
497        let p = |x, y| ewkb::Point { x: x, y: y, srid: Some(4326) };
498        // SELECT 'SRID=4326;LINESTRING (10 -20, -0 -0.5)'
499        let line = ewkb::LineString {srid: Some(4326), points: vec![p(10.0, -20.0), p(0., -0.5)]};
500        // SELECT 'SRID=4326;MULTIPOLYGON (((0 0, 2 0, 2 2, 0 2, 0 0)), ((10 10, -2 10, -2 -2, 10 -2, 10 10)))'::geometry
501        let multipoly = {
502            let line = ewkb::LineString {srid: Some(4326), points: vec![p(0., 0.), p(2., 0.), p(2., 2.), p(0., 2.), p(0., 0.)]};
503            let poly1 = ewkb::Polygon {srid: Some(4326), rings: vec![line]};
504            let line = ewkb::LineString {srid: Some(4326), points: vec![p(10., 10.), p(-2., 10.), p(-2., -2.), p(10., -2.), p(10., 10.)]};
505            let poly2 = ewkb::Polygon {srid: Some(4326), rings: vec![line]};
506            ewkb::MultiPolygon {srid: Some(4326), polygons: vec![poly1, poly2]}
507        };
508        // SELECT 'SRID=4326;GEOMETRYCOLLECTION (LINESTRING (10 -20,0 -0.5), MULTIPOLYGON (((0 0,2 0,2 2,0 2,0 0)),((10 10,-2 10,-2 -2,10 -2,10 10))))'::geometry
509        let collection = ewkb::GeometryCollection{
510            srid: Some(4326),
511            geometries: vec![
512                ewkb::GeometryT::LineString(line),
513                ewkb::GeometryT::MultiPolygon(multipoly),
514            ],
515        };
516        or_panic!(client.execute("INSERT INTO geomtests (geom) VALUES ($1)", &[&collection]));
517        let result = or_panic!(client.query("SELECT geom=ST_GeomFromEWKT('SRID=4326;GEOMETRYCOLLECTION (LINESTRING (10 -20,0 -0.5), MULTIPOLYGON (((0 0,2 0,2 2,0 2,0 0)),((10 10,-2 10,-2 -2,10 -2,10 10))))') FROM geomtests", &[]));
518        assert!(result.iter().map(|r| r.get::<_, bool>(0)).last().unwrap());
519    }
520
521    #[test]
522    #[ignore]
523    #[cfg_attr(rustfmt, rustfmt_skip)]
524    fn test_select_point() {
525        let mut client = connect();
526        let result = or_panic!(client.query("SELECT ('POINT(10 -20)')::geometry", &[]));
527        let point = result.iter().map(|r| r.get::<_, ewkb::Point>(0)).last().unwrap();
528        assert_eq!(point, ewkb::Point { x: 10.0, y: -20.0, srid: None });
529
530        let result = or_panic!(client.query("SELECT 'SRID=4326;POINT(10 -20)'::geometry", &[]));
531        let point = result.iter().map(|r| r.get::<_, ewkb::Point>(0)).last().unwrap();
532        assert_eq!(point, ewkb::Point { x: 10.0, y: -20.0, srid: Some(4326) });
533
534        let result = or_panic!(client.query("SELECT 'SRID=4326;POINT(10 -20 99)'::geometry", &[]));
535        let point = result.iter().map(|r| r.get::<_, ewkb::PointZ>(0)).last().unwrap();
536        assert_eq!(point, ewkb::PointZ { x: 10.0, y: -20.0, z: 99.0, srid: Some(4326) });
537
538        let result = or_panic!(client.query("SELECT 'POINT EMPTY'::geometry", &[]));
539        let point = result.iter().map(|r| r.get::<_, ewkb::Point>(0)).last().unwrap();
540        assert_eq!(&format!("{:?}", point), "Point { x: NaN, y: NaN, srid: None }");
541
542        let result = or_panic!(client.query("SELECT NULL::geometry(Point)", &[]));
543        let point = result.iter().map(|r| r.try_get::<_, ewkb::Point>(0)).last().unwrap();
544        assert_eq!(&format!("{:?}", point), "Err(Error { kind: FromSql(0), cause: Some(WasNull) })");
545    }
546
547    #[test]
548    #[ignore]
549    #[cfg_attr(rustfmt, rustfmt_skip)]
550    fn test_select_line() {
551        let mut client = connect();
552        let p = |x, y| ewkb::Point { x: x, y: y, srid: None };
553        let result = or_panic!(client.query("SELECT ('LINESTRING (10 -20, -0 -0.5)')::geometry", &[]));
554        let line = result.iter().map(|r| r.get::<_, ewkb::LineString>(0)).last().unwrap();
555        assert_eq!(line, ewkb::LineString {srid: None, points: vec![p(10.0, -20.0), p(0., -0.5)]});
556
557        let p = |x, y| ewkb::Point { x: x, y: y, srid: Some(4326) };
558        let result = or_panic!(client.query("SELECT ('SRID=4326;LINESTRING (10 -20, -0 -0.5)')::geometry", &[]));
559        let line = result.iter().map(|r| r.get::<_, ewkb::LineString>(0)).last().unwrap();
560        assert_eq!(line, ewkb::LineString {srid: Some(4326), points: vec![p(10.0, -20.0), p(0., -0.5)]});
561
562        let p = |x, y| ewkb::Point { x: x, y: y, srid: Some(4326) };
563        let result = or_panic!(client.query("SELECT ('SRID=4326;LINESTRINGZ (10 -20 1, -0 -0.5 1)')::geometry", &[]));
564        let line = result.iter().map(|r| r.get::<_, ewkb::LineString>(0)).last().unwrap();
565        assert_eq!(line, ewkb::LineString {srid: Some(4326), points: vec![p(10.0, -20.0), p(0., -0.5)]});
566
567        let result = or_panic!(client.query("SELECT 'LINESTRING EMPTY'::geometry", &[]));
568        let line = result.iter().map(|r| r.get::<_, ewkb::LineString>(0)).last().unwrap();
569        assert_eq!(&format!("{:?}", line), "LineStringT { points: [], srid: None }");
570    }
571
572    #[test]
573    #[ignore]
574    #[cfg_attr(rustfmt, rustfmt_skip)]
575    fn test_select_polygon() {
576        let mut client = connect();
577        let result = or_panic!(client.query("SELECT 'SRID=4326;POLYGON ((0 0, 2 0, 2 2, 0 2, 0 0))'::geometry", &[]));
578        let poly = result.iter().map(|r| r.get::<_, ewkb::Polygon>(0)).last().unwrap();
579        assert_eq!(format!("{:.0?}", poly), "PolygonT { rings: [LineStringT { points: [Point { x: 0, y: 0, srid: Some(4326) }, Point { x: 2, y: 0, srid: Some(4326) }, Point { x: 2, y: 2, srid: Some(4326) }, Point { x: 0, y: 2, srid: Some(4326) }, Point { x: 0, y: 0, srid: Some(4326) }], srid: Some(4326) }], srid: Some(4326) }");
580    }
581
582    #[test]
583    #[ignore]
584    #[cfg_attr(rustfmt, rustfmt_skip)]
585    fn test_select_multipoint() {
586        let mut client = connect();
587        let result = or_panic!(client.query("SELECT 'SRID=4326;MULTIPOINT ((10 -20 100), (0 -0.5 101))'::geometry", &[]));
588        let points = result.iter().map(|r| r.get::<_, ewkb::MultiPointZ>(0)).last().unwrap();
589        assert_eq!(format!("{:.1?}", points), "MultiPointT { points: [PointZ { x: 10.0, y: -20.0, z: 100.0, srid: None }, PointZ { x: 0.0, y: -0.5, z: 101.0, srid: None }], srid: Some(4326) }");
590    }
591
592    #[test]
593    #[ignore]
594    #[cfg_attr(rustfmt, rustfmt_skip)]
595    fn test_select_multiline() {
596        let mut client = connect();
597        let result = or_panic!(client.query("SELECT 'SRID=4326;MULTILINESTRING ((10 -20, 0 -0.5), (0 0, 2 0))'::geometry", &[]));
598        let multiline = result.iter().map(|r| r.get::<_, ewkb::MultiLineString>(0)).last().unwrap();
599        assert_eq!(format!("{:.1?}", multiline), "MultiLineStringT { lines: [LineStringT { points: [Point { x: 10.0, y: -20.0, srid: None }, Point { x: 0.0, y: -0.5, srid: None }], srid: None }, LineStringT { points: [Point { x: 0.0, y: 0.0, srid: None }, Point { x: 2.0, y: 0.0, srid: None }], srid: None }], srid: Some(4326) }");
600    }
601
602    #[test]
603    #[ignore]
604    #[cfg_attr(rustfmt, rustfmt_skip)]
605    fn test_select_multipolygon() {
606        let mut client = connect();
607        let result = or_panic!(client.query("SELECT 'SRID=4326;MULTIPOLYGON (((0 0, 2 0, 2 2, 0 2, 0 0)), ((10 10, -2 10, -2 -2, 10 -2, 10 10)))'::geometry", &[]));
608        let multipoly = result.iter().map(|r| r.get::<_, ewkb::MultiPolygon>(0)).last().unwrap();
609        assert_eq!(format!("{:.0?}", multipoly), "MultiPolygonT { polygons: [PolygonT { rings: [LineStringT { points: [Point { x: 0, y: 0, srid: None }, Point { x: 2, y: 0, srid: None }, Point { x: 2, y: 2, srid: None }, Point { x: 0, y: 2, srid: None }, Point { x: 0, y: 0, srid: None }], srid: None }], srid: None }, PolygonT { rings: [LineStringT { points: [Point { x: 10, y: 10, srid: None }, Point { x: -2, y: 10, srid: None }, Point { x: -2, y: -2, srid: None }, Point { x: 10, y: -2, srid: None }, Point { x: 10, y: 10, srid: None }], srid: None }], srid: None }], srid: Some(4326) }");
610    }
611
612    #[test]
613    #[ignore]
614    #[cfg_attr(rustfmt, rustfmt_skip)]
615    fn test_select_geometrycollection() {
616        let mut client = connect();
617        let result = or_panic!(client.query("SELECT 'GeometryCollection(POINT (10 10),POINT (30 30),LINESTRING (15 15, 20 20))'::geometry", &[]));
618        let geom = result.iter().map(|r| r.get::<_, ewkb::GeometryCollection>(0)).last().unwrap();
619        assert_eq!(format!("{:.0?}", geom), "GeometryCollectionT { geometries: [Point(Point { x: 10, y: 10, srid: None }), Point(Point { x: 30, y: 30, srid: None }), LineString(LineStringT { points: [Point { x: 15, y: 15, srid: None }, Point { x: 20, y: 20, srid: None }], srid: None })], srid: None }");
620    }
621
622    #[test]
623    #[ignore]
624    #[cfg_attr(rustfmt, rustfmt_skip)]
625    fn test_select_geometry() {
626        let mut client = connect();
627        or_panic!(client.execute("CREATE TEMPORARY TABLE geomtests (geom geometry)", &[]));
628        or_panic!(client.execute("INSERT INTO geomtests VALUES('SRID=4326;POINT(10 -20 99)'::geometry)", &[]));
629        let result = or_panic!(client.query("SELECT geom FROM geomtests", &[]));
630        let geom = result.iter().map(|r| r.get::<_, ewkb::GeometryZ>(0)).last().unwrap();
631        assert_eq!(format!("{:.0?}", geom), "Point(PointZ { x: 10, y: -20, z: 99, srid: Some(4326) })");
632    }
633
634    #[test]
635    #[ignore]
636    #[cfg_attr(rustfmt, rustfmt_skip)]
637    fn test_select_type_error() {
638        let mut client = connect();
639        let result = or_panic!(client.query("SELECT ('LINESTRING (10 -20, -0 -0.5)')::geometry", &[]));
640        let poly = result.iter().map(|r| r.try_get::<_, ewkb::Polygon>(0)).last().unwrap();
641        assert_eq!(format!("{:?}", poly), "Err(Error { kind: FromSql(0), cause: Some(\"cannot convert geometry to PolygonT\") })");
642    }
643
644    #[test]
645    #[ignore]
646    #[cfg_attr(rustfmt, rustfmt_skip)]
647    fn test_twkb() {
648        let mut client = connect();
649        let result = or_panic!(client.query("SELECT ST_AsTWKB('POINT(10 -20)'::geometry)", &[]));
650        let point = result.iter().map(|r| r.get::<_, twkb::Point>(0)).last().unwrap();
651        assert_eq!(point, twkb::Point {x: 10.0, y: -20.0});
652
653        let result = or_panic!(client.query("SELECT ST_AsTWKB('SRID=4326;POINT(10 -20)'::geometry)", &[]));
654        let point = result.iter().map(|r| r.get::<_, twkb::Point>(0)).last().unwrap();
655        assert_eq!(point, twkb::Point {x: 10.0, y: -20.0});
656
657        let result = or_panic!(client.query("SELECT ST_AsTWKB('POINT EMPTY'::geometry)", &[]));
658        let point = result.iter().map(|r| r.get::<_, twkb::Point>(0)).last().unwrap();
659        assert_eq!(&format!("{:?}", point), "Point { x: NaN, y: NaN }");
660        let point = &point as &dyn postgis::Point;
661        assert!(point.x().is_nan());
662
663        let result = or_panic!(client.query("SELECT ST_AsTWKB(NULL::geometry(Point))", &[]));
664        let point = result.iter().map(|r| r.try_get::<_, twkb::Point>(0)).last().unwrap();
665        assert_eq!(&format!("{:?}", point), "Err(Error { kind: FromSql(0), cause: Some(WasNull) })");
666
667        let result = or_panic!(client.query("SELECT ST_AsTWKB('LINESTRING (10 -20, -0 -0.5)'::geometry, 1)", &[]));
668        let line = result.iter().map(|r| r.get::<_, twkb::LineString>(0)).last().unwrap();
669        assert_eq!(&format!("{:.1?}", line), "LineString { points: [Point { x: 10.0, y: -20.0 }, Point { x: 0.0, y: -0.5 }] }");
670    }
671
672    #[test]
673    #[ignore]
674    #[cfg_attr(rustfmt, rustfmt_skip)]
675    fn test_twkb_insert() {
676        let mut client = connect();
677        or_panic!(client.execute("CREATE TEMPORARY TABLE geomtests (geom geometry(Point))", &[]));
678
679        let result = or_panic!(client.query("SELECT ST_AsTWKB('POINT(10 -20)'::geometry)", &[]));
680        let point = result.iter().map(|r| r.get::<_, twkb::Point>(0)).last().unwrap();
681
682        or_panic!(client.execute("INSERT INTO geomtests (geom) VALUES ($1)", &[&point.as_ewkb()]));
683        let result = or_panic!(client.query("SELECT geom=ST_GeomFromEWKT('POINT(10 -20)') FROM geomtests", &[]));
684        assert!(result.iter().map(|r| r.get::<_, bool>(0)).last().unwrap());
685        or_panic!(client.execute("TRUNCATE geomtests", &[]));
686
687        let mut client = connect();
688        or_panic!(client.execute("CREATE TEMPORARY TABLE geomtests (geom geometry(LineString))", &[]));
689
690        let result = or_panic!(client.query("SELECT ST_AsTWKB('LINESTRING (10 -20, 0 -0.5)'::geometry, 1)", &[]));
691        let line = result.iter().map(|r| r.get::<_, twkb::LineString>(0)).last().unwrap();
692
693        or_panic!(client.execute("INSERT INTO geomtests (geom) VALUES ($1)", &[&line.as_ewkb()]));
694        let result = or_panic!(client.query("SELECT geom=ST_GeomFromEWKT('LINESTRING (10 -20, 0 -0.5)') FROM geomtests", &[]));
695        assert!(result.iter().map(|r| r.get::<_, bool>(0)).last().unwrap());
696        or_panic!(client.execute("TRUNCATE geomtests", &[]));
697    }
698
699    #[test]
700    #[ignore]
701    #[cfg_attr(rustfmt, rustfmt_skip)]
702    #[allow(unused_imports,unused_variables)]
703    fn test_examples() {
704        use postgres::{Client, NoTls};
705        //use postgis::ewkb;
706        //use postgis::LineString;
707
708        fn main() {
709            //
710            use crate::{
711                ewkb,
712                types::LineString,
713                twkb
714            };
715            let mut client = connect();
716            or_panic!(client.execute("CREATE TEMPORARY TABLE busline (route geometry(LineString))", &[]));
717            or_panic!(client.execute("CREATE TEMPORARY TABLE stops (stop geometry(Point))", &[]));
718            or_panic!(client.execute("INSERT INTO busline (route) VALUES ('LINESTRING(10 -20, -0 -0.5)'::geometry)", &[]));
719            //
720
721            // conn ....
722            for row in &client.query("SELECT * FROM busline", &[]).unwrap() {
723                let route: ewkb::LineString = row.get("route");
724                let last_stop = route.points().last().unwrap();
725                let _ = client.execute("INSERT INTO stops (stop) VALUES ($1)", &[&last_stop]);
726            }
727
728            for row in &client.query("SELECT * FROM busline", &[]).unwrap() {
729                let route = row.try_get::<_, Option<ewkb::LineString>>("route");
730                match route {
731                    Ok(Some(geom)) => { println!("{:?}", geom) }
732                    Ok(None) => { /* Handle NULL value */ }
733                    Err(err) => { println!("Error: {}", err) }
734                }
735            }
736
737        //use postgis::twkb;
738
739            for row in &client.query("SELECT ST_AsTWKB(route) FROM busline", &[]).unwrap() {
740                let route: twkb::LineString = row.get(0);
741                let last_stop = route.points().last().unwrap();
742                let _ = client.execute("INSERT INTO stops (stop) VALUES ($1)", &[&last_stop.as_ewkb()]);
743            }
744        }
745
746        main();
747    }
748}