Skip to content

Commit

Permalink
feat(mysql): add support for Geometry subtypes
Browse files Browse the repository at this point in the history
  • Loading branch information
mirromutth committed Feb 22, 2024
1 parent 2fa7ccd commit 00185b6
Show file tree
Hide file tree
Showing 2 changed files with 261 additions and 85 deletions.
115 changes: 100 additions & 15 deletions sqlx-mysql/src/types/geometry.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
use geo_types::Geometry;
use geo_types::{
Error, Geometry, GeometryCollection, Line, LineString, MultiLineString, MultiPoint,
MultiPolygon, Point, Polygon, Rect, Triangle,
};
use geozero::wkb::{FromWkb, WkbDialect};
use geozero::{GeozeroGeometry, ToWkb};
use std::any::type_name;

use crate::decode::Decode;
use crate::encode::{Encode, IsNull};
Expand All @@ -10,23 +14,26 @@ use crate::protocol::text::ColumnType;
use crate::types::Type;
use crate::{MySql, MySqlTypeInfo, MySqlValueRef};

/// Define a type that can be used to represent a `GEOMETRY` field.
///
/// Note: Only `Geometry<f64>` is implemented with geozero::GeozeroGeometry.
impl Type<MySql> for Geometry<f64> {
fn type_info() -> MySqlTypeInfo {
// MySQL does not allow to execute with a Geometry parameter for now.
// MySQL reports: 1210 (HY000): Incorrect arguments to mysqld_stmt_execute
// MariaDB does not report errors but does not work properly.
// So we use the `Blob` type to pass Geometry parameters.
MySqlTypeInfo::binary(ColumnType::Blob)
}
macro_rules! impl_mysql_type {
($name:ident) => {
impl Type<MySql> for $name<f64> {
fn type_info() -> MySqlTypeInfo {
// MySQL does not allow to execute with a Geometry parameter for now.
// MySQL reports: 1210 (HY000): Incorrect arguments to mysqld_stmt_execute
// MariaDB does not report errors but does not work properly.
// So we use the `Blob` type to pass Geometry parameters.
MySqlTypeInfo::binary(ColumnType::Blob)
}

fn compatible(ty: &MySqlTypeInfo) -> bool {
ty.r#type == ColumnType::Geometry || <&[u8] as Type<MySql>>::compatible(ty)
}
fn compatible(ty: &MySqlTypeInfo) -> bool {
ty.r#type == ColumnType::Geometry || <&[u8] as Type<MySql>>::compatible(ty)
}
}
};
}

impl_mysql_type!(Geometry);

const ENCODE_ERR: &str = "failed to encode value as Geometry to WKB; the most likely cause is that the value is not a valid geometry";

impl Encode<'_, MySql> for Geometry<f64> {
Expand All @@ -48,3 +55,81 @@ impl Decode<'_, MySql> for Geometry<f64> {
Ok(FromWkb::from_wkb(&mut bytes, WkbDialect::MySQL)?)
}
}

macro_rules! impl_encode_subtype {
($name:ident) => {
impl Encode<'_, MySql> for $name<f64> {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
Geometry::<f64>::$name(self.clone()).encode(buf)
}
}
};
}

macro_rules! impl_decode_subtype {
($name:ident) => {
impl Decode<'_, MySql> for $name<f64> {
fn decode(value: MySqlValueRef<'_>) -> Result<Self, BoxDynError> {
Ok(<Geometry<f64> as Decode<'_, MySql>>::decode(value)?.try_into()?)
}
}
};
}

macro_rules! impls_subtype {
($name:ident) => {
impl_mysql_type!($name);
impl_encode_subtype!($name);
impl_decode_subtype!($name);
};
($name:ident, $n:ident<$decode:ident> => $($c:tt)+) => {
impl_mysql_type!($name);
impl_encode_subtype!($name);

impl Decode<'_, MySql> for $name<f64> {
fn decode(value: MySqlValueRef<'_>) -> Result<Self, BoxDynError> {
let $n = <$decode<f64> as Decode<'_, MySql>>::decode(value)?;

$($c)+
}
}
};
}

// All geometry types in MySQL:
// GEOMETRY, POINT, LINESTRING, POLYGON, MULTIPOINT, MULTILINESTRING, MULTIPOLYGON, GEOMETRYCOLLECTION.
// Line, Rect, Triangle can be encoded, but MySQL has no corresponding types so their decoding is unreachable.

impls_subtype!(Point);
impls_subtype!(LineString);
impls_subtype!(Polygon);
impls_subtype!(MultiPoint);
impls_subtype!(MultiLineString);
impls_subtype!(MultiPolygon);

// GeometryCollection is a special case
// Deprecated GeometryCollection::from(single_geom) produces unexpected results
// TODO: remove following when GeometryCollection::from(single_geom) is removed

macro_rules! geometry_collection_mismatch {
($name:ident) => {
Err(Error::MismatchedGeometry {
expected: type_name::<GeometryCollection<f64>>(),
found: type_name::<$name<f64>>(),
}
.into())
};
}

impls_subtype!(GeometryCollection, g<Geometry> => match g {
Geometry::GeometryCollection(gc) => Ok(gc),
Geometry::Point(_) => geometry_collection_mismatch!(Point),
Geometry::Line(_) => geometry_collection_mismatch!(Line),
Geometry::LineString(_) => geometry_collection_mismatch!(LineString),
Geometry::Polygon(_) => geometry_collection_mismatch!(Polygon),
Geometry::MultiPoint(_) => geometry_collection_mismatch!(MultiPoint),
Geometry::MultiLineString(_) => geometry_collection_mismatch!(MultiLineString),
Geometry::MultiPolygon(_) => geometry_collection_mismatch!(MultiPolygon),
Geometry::Rect(_) => geometry_collection_mismatch!(Rect),
Geometry::Triangle(_) => geometry_collection_mismatch!(Triangle),
});
Loading

0 comments on commit 00185b6

Please sign in to comment.