diesel_mysql_spatial/
data_types.rs

1use super::sql_types;
2use crate::GeometryError;
3use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
4use diesel::deserialize;
5use diesel::deserialize::FromSql;
6use diesel::mysql::Mysql;
7use diesel::serialize;
8use diesel::serialize::{IsNull, Output, ToSql};
9use geo_types;
10use std::io::{Cursor, Write};
11use std::ops::{Deref, DerefMut};
12use wkb::{WKBReadExt, WKBWriteExt};
13
14/// Spatial Reference Identifier
15///
16/// `0` is the unitless Cartesian plane, other values are EPSG SRS IDs like `4326` for WGS 84 or
17/// identify custom reference systems registered with `CREATE SPATIAL REFERENCE SYSTEM`.
18pub type SRID = u32;
19
20/// SRID for the unitless Cartesian plane
21pub const SRID_CARTESIAN: SRID = 0;
22
23/// Defines a Diesel-compatible wrapper struct for a geo type
24///
25/// Also implements the most important conversion traits.
26macro_rules! mysql_type {
27	($type:ident, $geom:ident, $sql_name:ty, $doc:literal) => {
28		#[derive(Debug, Clone, PartialEq, FromSqlRow, AsExpression)]
29		#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30		#[sql_type = stringify!($sql_name)]
31		#[doc=$doc]
32		///
33		/// MySQL extension: The spatial reference system identifier (SRID) may identify the used
34		/// coordinate system.
35		pub struct $type {
36			pub srid: SRID,
37			#[cfg_attr(feature = "serde", serde(flatten))]
38			pub geom: geo_types::$geom<f64>,
39		}
40
41		impl From<geo_types::$geom<f64>> for $type {
42			#[inline]
43			fn from(geom: geo_types::$geom<f64>) -> Self {
44				Self {
45					srid: 0,
46					geom: geom.into(),
47				}
48			}
49		}
50
51		impl AsRef<geo_types::$geom<f64>> for $type {
52			#[inline]
53			fn as_ref(&self) -> &geo_types::$geom<f64> {
54				&self.geom
55			}
56		}
57
58		impl AsMut<geo_types::$geom<f64>> for $type {
59			#[inline]
60			fn as_mut(&mut self) -> &mut geo_types::$geom<f64> {
61				&mut self.geom
62			}
63		}
64
65		impl Deref for $type {
66			type Target = geo_types::$geom<f64>;
67
68			#[inline]
69			fn deref(&self) -> &Self::Target {
70				&self.geom
71			}
72		}
73
74		impl DerefMut for $type {
75			#[inline]
76			fn deref_mut(&mut self) -> &mut Self::Target {
77				&mut self.geom
78			}
79		}
80	};
81}
82
83/// Implements common SQL conversion functions for a geo type and its wrapper.
84macro_rules! impl_traits {
85	($type:ident, $geom:ident, $sql_name:ty) => {
86		impl FromSql<$sql_name, Mysql> for $type {
87			fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
88				let mut bytes = not_none!(bytes);
89				let srid = bytes.read_u32::<LittleEndian>()?;
90				let mut bytes_cursor = Cursor::new(bytes);
91				let geom = bytes_cursor
92					.read_wkb()
93					.map_err(|e| GeometryError::from(e))?;
94				if let geo_types::Geometry::$geom(p) = geom {
95					Ok(Self { srid, geom: p })
96				} else {
97					Err(Box::new(GeometryError::WrongType))
98				}
99			}
100		}
101
102		impl FromSql<$sql_name, Mysql> for Geometry {
103			fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
104				let mut bytes = not_none!(bytes);
105				let srid = bytes.read_u32::<LittleEndian>()?;
106				let mut bytes_cursor = Cursor::new(bytes);
107				let geom = bytes_cursor.read_wkb().map_err(GeometryError::from)?;
108				Ok(Self { srid, geom })
109			}
110		}
111
112		impl FromSql<$sql_name, Mysql> for geo_types::$geom<f64> {
113			fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
114				let wrapped = $type::from_sql(bytes)?;
115				Ok(wrapped.geom)
116			}
117		}
118
119		impl FromSql<$sql_name, Mysql> for geo_types::Geometry<f64> {
120			fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
121				let wrapped = <Geometry as FromSql<$sql_name, Mysql>>::from_sql(bytes)?;
122				Ok(wrapped.geom)
123			}
124		}
125
126		impl ToSql<$sql_name, Mysql> for $type {
127			fn to_sql<W: Write>(&self, out: &mut Output<W, Mysql>) -> serialize::Result {
128				out.write_u32::<LittleEndian>(self.srid)?;
129				out.write_wkb(&geo_types::Geometry::$geom(self.geom.clone()))
130					.map_err(GeometryError::from)?;
131				Ok(IsNull::No)
132			}
133		}
134
135		impl ToSql<sql_types::Geometry, Mysql> for $type {
136			fn to_sql<W: Write>(&self, out: &mut Output<W, Mysql>) -> serialize::Result {
137				out.write_u32::<LittleEndian>(self.srid)?;
138				out.write_wkb(&geo_types::Geometry::$geom(self.geom.clone()))
139					.map_err(GeometryError::from)?;
140				Ok(IsNull::No)
141			}
142		}
143
144		impl ToSql<$sql_name, Mysql> for geo_types::$geom<f64> {
145			fn to_sql<W: Write>(&self, out: &mut Output<W, Mysql>) -> serialize::Result {
146				out.write_u32::<LittleEndian>(0)?;
147				out.write_wkb(&geo_types::Geometry::$geom(self.clone()))
148					.map_err(GeometryError::from)?;
149				Ok(IsNull::No)
150			}
151		}
152
153		impl ToSql<$sql_name, Mysql> for geo_types::Geometry<f64> {
154			fn to_sql<W: Write>(&self, out: &mut Output<W, Mysql>) -> serialize::Result {
155				out.write_u32::<LittleEndian>(0)?;
156				out.write_wkb(self).map_err(GeometryError::from)?;
157				Ok(IsNull::No)
158			}
159		}
160
161		impl From<$type> for Geometry {
162			fn from(other: $type) -> Self {
163				Self {
164					srid: other.srid,
165					geom: geo_types::Geometry::$geom(other.geom),
166				}
167			}
168		}
169
170		impl TryFrom<Geometry> for $type {
171			type Error = <geo_types::$geom<f64> as TryFrom<geo_types::Geometry<f64>>>::Error;
172
173			fn try_from(other: Geometry) -> Result<Self, Self::Error> {
174				Ok(Self {
175					srid: other.srid,
176					geom: other.geom.try_into()?,
177				})
178			}
179		}
180	};
181}
182
183mysql_type!(
184	Point,
185	Point,
186	sql_types::Point,
187	"A single point in 2D space."
188);
189mysql_type!(
190	Polygon,
191	Polygon,
192	sql_types::Polygon,
193	"A bounded two-dimensional area."
194);
195mysql_type!(
196	LineString,
197	LineString,
198	sql_types::LineString,
199	"A linearly interpolated path between locations."
200);
201mysql_type!(
202	MultiPoint,
203	MultiPoint,
204	sql_types::MultiPoint,
205	"A collection of [`Point`][geo_types::Point]s."
206);
207mysql_type!(
208	MultiLineString,
209	MultiLineString,
210	sql_types::MultiLineString,
211	"A collection of [`LineString`][geo_types::LineString]s."
212);
213mysql_type!(
214	MultiPolygon,
215	MultiPolygon,
216	sql_types::MultiPolygon,
217	"A collection of [`Polygon`][geo_types::Polygon]s."
218);
219mysql_type!(
220	GeometryCollection,
221	GeometryCollection,
222	sql_types::GeometryCollection,
223	"A collection of [`Geometry`][geo_types::Geometry] types."
224);
225mysql_type!(
226	Geometry,
227	Geometry,
228	sql_types::Geometry,
229	"An arbitrary geometry."
230);
231mysql_type!(
232	BoundingBox,
233	Rect,
234	sql_types::Polygon,
235	"An axis-aligned bounding rectangle.
236
237MySQL doesn't support native `Rect`s. Corresponding functions like `ST_Envelope()` return
238a `Polygon` of the shape `POLYGON((MINX MINY, MAXX MINY, MAXX MAXY, MINX MAXY, MINX MINY))`
239instead. This struct exists to provide more convenient access to the coordinates if your
240column always contains such polygons.
241
242**Warning**: This can only be unserialized from a [`Polygon`][sql_types::Polygon]
243column. If polygons in other shapes are unserialized, the process may either fail
244or silently result in wrong bounds.
245"
246);
247
248impl_traits!(Point, Point, sql_types::Point);
249impl_traits!(Polygon, Polygon, sql_types::Polygon);
250impl_traits!(LineString, LineString, sql_types::LineString);
251impl_traits!(MultiPoint, MultiPoint, sql_types::MultiPoint);
252impl_traits!(MultiLineString, MultiLineString, sql_types::MultiLineString);
253impl_traits!(MultiPolygon, MultiPolygon, sql_types::MultiPolygon);
254impl_traits!(
255	GeometryCollection,
256	GeometryCollection,
257	sql_types::GeometryCollection
258);
259
260impl FromSql<sql_types::Geometry, Mysql> for Geometry {
261	fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
262		let mut bytes = not_none!(bytes);
263		let srid = bytes.read_u32::<LittleEndian>()?;
264		let mut bytes_cursor = Cursor::new(bytes);
265		let geom = bytes_cursor.read_wkb().map_err(GeometryError::from)?;
266		Ok(Self { srid, geom })
267	}
268}
269
270impl ToSql<sql_types::Geometry, Mysql> for Geometry {
271	fn to_sql<W: Write>(&self, out: &mut Output<W, Mysql>) -> serialize::Result {
272		out.write_u32::<LittleEndian>(self.srid)?;
273		out.write_wkb(&self.geom).map_err(GeometryError::from)?;
274		Ok(IsNull::No)
275	}
276}
277
278impl FromSql<sql_types::Polygon, Mysql> for BoundingBox {
279	fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
280		let polygon = <Polygon as FromSql<sql_types::Polygon, Mysql>>::from_sql(bytes)?;
281		let mut coords = polygon.exterior().coords();
282		let min = match coords.next() {
283			None => return Err(Box::new(GeometryError::InvalidValue)),
284			Some(c) => *c,
285		};
286		if coords.next().is_none() {
287			return Err(Box::new(GeometryError::InvalidValue));
288		}
289		let max = match coords.next() {
290			None => return Err(Box::new(GeometryError::InvalidValue)),
291			Some(c) => *c,
292		};
293		Ok(Self {
294			srid: polygon.srid,
295			geom: geo_types::Rect::new(min, max),
296		})
297	}
298}
299
300impl ToSql<sql_types::Polygon, Mysql> for BoundingBox {
301	fn to_sql<W: Write>(&self, out: &mut Output<W, Mysql>) -> serialize::Result {
302		out.write_u32::<LittleEndian>(self.srid)?;
303		out.write_wkb(&geo_types::Geometry::Polygon(self.to_polygon()))
304			.map_err(GeometryError::from)?;
305		Ok(IsNull::No)
306	}
307}
308
309impl From<BoundingBox> for Geometry {
310	fn from(other: BoundingBox) -> Self {
311		Self {
312			srid: other.srid,
313			geom: geo_types::Geometry::Polygon(other.geom.to_polygon()),
314		}
315	}
316}
317
318impl From<BoundingBox> for Polygon {
319	fn from(other: BoundingBox) -> Self {
320		Self {
321			srid: other.srid,
322			geom: other.geom.to_polygon(),
323		}
324	}
325}
326
327impl Geometry {
328	/// Construct an empty geometry
329	///
330	/// As MySQL only supports empty geometry collections this is basically equivalent to
331	/// `Geometry::from(GeometryCollection::empty())`.
332	#[inline]
333	#[must_use]
334	pub const fn empty(srid: SRID) -> Self {
335		Self {
336			srid,
337			geom: geo_types::Geometry::GeometryCollection(geo_types::GeometryCollection(vec![])),
338		}
339	}
340}
341
342impl GeometryCollection {
343	/// Construct an empty geometry collection
344	#[inline]
345	#[must_use]
346	pub const fn empty(srid: SRID) -> Self {
347		Self {
348			srid,
349			geom: geo_types::GeometryCollection(vec![]),
350		}
351	}
352}
353
354impl Default for GeometryCollection {
355	fn default() -> Self {
356		Self::empty(0)
357	}
358}
359
360impl Default for Geometry {
361	fn default() -> Self {
362		Self::empty(0)
363	}
364}