Skip to content

Commit

Permalink
feat(mysql): add GEOMETRY support
Browse files Browse the repository at this point in the history
  • Loading branch information
mirromutth committed Feb 22, 2024
1 parent 02f196b commit 2fa7ccd
Show file tree
Hide file tree
Showing 7 changed files with 317 additions and 0 deletions.
55 changes: 55 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ mac_address = ["sqlx-core/mac_address", "sqlx-macros?/mac_address", "sqlx-postgr
rust_decimal = ["sqlx-core/rust_decimal", "sqlx-macros?/rust_decimal", "sqlx-mysql?/rust_decimal", "sqlx-postgres?/rust_decimal"]
time = ["sqlx-core/time", "sqlx-macros?/time", "sqlx-mysql?/time", "sqlx-postgres?/time", "sqlx-sqlite?/time"]
uuid = ["sqlx-core/uuid", "sqlx-macros?/uuid", "sqlx-mysql?/uuid", "sqlx-postgres?/uuid", "sqlx-sqlite?/uuid"]
geometry = ["sqlx-mysql?/geometry"]
regexp = ["sqlx-sqlite?/regexp"]

[workspace.dependencies]
Expand All @@ -136,6 +137,8 @@ mac_address = "1.1.5"
rust_decimal = "1.26.1"
time = { version = "0.3.14", features = ["formatting", "parsing", "macros"] }
uuid = "1.1.2"
geozero = { version = "0.12.0", default-features = false }
geo-types = "0.7.12"

# Common utility crates
dotenvy = { version = "0.15.0", default-features = false }
Expand Down Expand Up @@ -170,6 +173,7 @@ sqlx-test = { path = "./sqlx-test" }
paste = "1.0.6"
serde = { version = "1.0.132", features = ["derive"] }
serde_json = "1.0.73"
geo-types = { workspace = true }
url = "2.2.2"
rand = "0.8.4"
rand_xoshiro = "0.6.0"
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@ be removed in the future.

- `json`: Add support for `JSON` and `JSONB` (in postgres) using the `serde_json` crate.

- `geometry`: Add support for `GEOMETRY` using the `geozero` crate, currently only available for MySQL.

- Offline mode is now always enabled. See [sqlx-cli/README.md][readme-offline].

[readme-offline]: sqlx-cli/README.md#enable-building-in-offline-mode-with-query
Expand Down
3 changes: 3 additions & 0 deletions sqlx-mysql/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ json = ["sqlx-core/json", "serde"]
any = ["sqlx-core/any"]
offline = ["sqlx-core/offline", "serde/derive"]
migrate = ["sqlx-core/migrate"]
geometry = ["geozero", "geo-types"]

[dependencies]
sqlx-core = { workspace = true }
Expand Down Expand Up @@ -41,6 +42,8 @@ chrono = { workspace = true, optional = true }
rust_decimal = { workspace = true, optional = true }
time = { workspace = true, optional = true }
uuid = { workspace = true, optional = true }
geozero = { workspace = true, features = ["with-geo", "with-wkb"], optional = true }
geo-types = { workspace = true, optional = true }

# Misc
atoi = "2.0"
Expand Down
50 changes: 50 additions & 0 deletions sqlx-mysql/src/types/geometry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use geo_types::Geometry;
use geozero::wkb::{FromWkb, WkbDialect};
use geozero::{GeozeroGeometry, ToWkb};

use crate::decode::Decode;
use crate::encode::{Encode, IsNull};
use crate::error::BoxDynError;
use crate::io::MySqlBufMutExt;
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)
}

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

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> {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
// Encoding is supposed to be infallible, so we don't have much choice but to panic here.
// However, in most cases, a geometry being unable to serialize to WKB is most likely due to user error.
let bytes = self.to_mysql_wkb(self.srid()).expect(ENCODE_ERR);

buf.put_bytes_lenenc(bytes.as_ref());

IsNull::No
}
}

impl Decode<'_, MySql> for Geometry<f64> {
fn decode(value: MySqlValueRef<'_>) -> Result<Self, BoxDynError> {
let mut bytes = value.as_bytes()?;

Ok(FromWkb::from_wkb(&mut bytes, WkbDialect::MySQL)?)
}
}
3 changes: 3 additions & 0 deletions sqlx-mysql/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,6 @@ mod time;

#[cfg(feature = "uuid")]
mod uuid;

#[cfg(feature = "geometry")]
mod geometry;
Loading

0 comments on commit 2fa7ccd

Please sign in to comment.