diff --git a/geozero/Cargo.toml b/geozero/Cargo.toml index 4e524b86..dd18f801 100644 --- a/geozero/Cargo.toml +++ b/geozero/Cargo.toml @@ -25,6 +25,7 @@ with-gpkg = ["with-wkb", "sqlx/sqlite"] with-gpx = ["gpx"] with-postgis-sqlx = ["with-wkb", "sqlx/postgres"] with-postgis-postgres = ["with-wkb", "postgres-types", "bytes"] +with-postgis-diesel = ["with-wkb", "diesel", "byteorder"] with-mvt = ["prost", "prost-build"] with-tessellator = ["lyon"] @@ -42,6 +43,8 @@ lyon = { version = "1.0", optional = true } log = "0.4.17" scroll = { version = "0.11", optional = true } sqlx = { version = "0.6", default-features = false, optional = true } +diesel = { version = "2.0.2", default-features = false, optional = true } +byteorder = { version = "1.4.3", default-features = false, optional = true } postgres-types = { version = "0.2", optional = true } bytes = { version = "1.0", optional = true } prost = { version = "0.11.0", optional = true } @@ -59,6 +62,7 @@ flatgeobuf = "3.24.0" #flatgeobuf = { git = "https://github.com/pka/flatgeobuf", branch="geozero-0.9" } postgres = "0.19" sqlx = { version = "0.6", default-features = false, features = [ "runtime-tokio-native-tls", "macros", "time", "postgres", "sqlite" ] } +diesel = { version = "2.0.2", default-features = false, features = [ "postgres" ] } tokio = { version = "1.17.0", default-features = false, features = ["macros"] } [build-dependencies] diff --git a/geozero/src/lib.rs b/geozero/src/lib.rs index 92ffd595..9d1c7163 100644 --- a/geozero/src/lib.rs +++ b/geozero/src/lib.rs @@ -78,7 +78,11 @@ pub mod gpkg; #[cfg(feature = "with-gpx")] pub mod gpx; -#[cfg(any(feature = "with-postgis-postgres", feature = "with-postgis-sqlx"))] +#[cfg(any( + feature = "with-postgis-postgres", + feature = "with-postgis-sqlx", + feature = "with-postgis-diesel" +))] pub mod postgis; #[cfg(feature = "with-svg")] diff --git a/geozero/src/postgis/mod.rs b/geozero/src/postgis/mod.rs index 13431ff8..9ba68950 100644 --- a/geozero/src/postgis/mod.rs +++ b/geozero/src/postgis/mod.rs @@ -3,6 +3,8 @@ //! All geometry types implementing [GeozeroGeometry](crate::GeozeroGeometry) can be encoded as PostGIS EWKB geometry using [wkb::Encode](crate::wkb::Encode). //! //! Geometry types implementing [FromWkb](crate::wkb::FromWkb) can be decoded from PostGIS geometries using [wkb::Decode](crate::wkb::Decode). +#[cfg(feature = "with-postgis-diesel")] +mod postgis_diesel; #[cfg(feature = "with-postgis-postgres")] mod postgis_postgres; #[cfg(feature = "with-postgis-sqlx")] @@ -89,3 +91,67 @@ pub mod postgres { pub mod sqlx { pub use super::postgis_sqlx::*; } + +/// Postgis geometry type encoding for Diesel. +/// +/// # PostGIS usage example with Diesel +/// +/// Declare model and select Ewkb types directly with GeoZero and Diesel +/// +/// ``` +/// use diesel::pg::PgConnection; +/// use diesel::{Connection, QueryDsl, RunQueryDsl}; +/// use diesel::prelude::*; +/// +/// use geozero::wkb::Ewkb; +/// +/// diesel::table! { +/// use diesel::sql_types::*; +/// use geozero::postgis::diesel::sql_types::*; +/// +/// geometries (name) { +/// name -> Varchar, +/// geom -> Nullable, +/// } +/// } +/// +/// #[derive(Queryable, Debug, Insertable)] +/// #[diesel(table_name = geometries)] +/// pub struct Geom { +/// pub name: String, +/// pub geom: Option, +/// } +/// +/// pub fn establish_connection() -> PgConnection { +/// let database_url = std::env::var("DATABASE_URL").expect("Unable to find database url."); +/// PgConnection::establish(&database_url).unwrap() +/// } +/// +/// # async fn rust_geo_query() -> Result<(), diesel::result::Error> { +/// let conn = &mut establish_connection(); +/// +/// let wkb = Ewkb(vec![ +/// 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 64, 0, 0, 0, 0, 0, 0, 52, 192, +/// ]); +/// +/// let insert_geometry = Geom { +/// name: "GeoZeroTest".to_string(), +/// geom: Some(wkb), +/// }; +/// +/// let inserted: Geom = diesel::insert_into(geometries::table) +/// .values(&insert_geometry) +/// .get_result(conn) +/// .expect("Unable to insert into postgis"); +/// +/// let geometry_vec: Vec = geometries::dsl::geometries +/// .limit(10) +/// .load::(conn) +/// .expect("Error loading geometries"); +/// # Ok(()) +/// # } +/// ``` +#[cfg(feature = "with-postgis-diesel")] +pub mod diesel { + pub use super::postgis_diesel::*; +} diff --git a/geozero/src/postgis/postgis_diesel.rs b/geozero/src/postgis/postgis_diesel.rs new file mode 100644 index 00000000..c5d52afb --- /dev/null +++ b/geozero/src/postgis/postgis_diesel.rs @@ -0,0 +1,51 @@ +use crate::postgis::postgis_diesel::sql_types::*; +use crate::wkb::Ewkb; + +use byteorder::WriteBytesExt; + +use diesel::deserialize::{self, FromSql}; +use diesel::pg::{self, Pg}; +use diesel::serialize::{self, IsNull, Output, ToSql}; + +pub mod sql_types { + use diesel::query_builder::QueryId; + use diesel::sql_types::SqlType; + + #[derive(SqlType, QueryId)] + #[diesel(postgres_type(name = "geometry"))] + pub struct Geometry; + + #[derive(SqlType, QueryId)] + #[diesel(postgres_type(name = "geography"))] + pub struct Geography; +} + +impl ToSql for Ewkb { + fn to_sql(&self, out: &mut Output) -> serialize::Result { + for ewkb_byte in &self.0 { + out.write_u8(*ewkb_byte)?; + } + Ok(IsNull::No) + } +} + +impl ToSql for Ewkb { + fn to_sql(&self, out: &mut Output) -> serialize::Result { + for ewkb_byte in &self.0 { + out.write_u8(*ewkb_byte)?; + } + Ok(IsNull::No) + } +} + +impl FromSql for Ewkb { + fn from_sql(bytes: pg::PgValue) -> deserialize::Result { + Ok(Self(bytes.as_bytes().to_vec())) + } +} + +impl FromSql for Ewkb { + fn from_sql(bytes: pg::PgValue) -> deserialize::Result { + Ok(Self(bytes.as_bytes().to_vec())) + } +} diff --git a/geozero/src/wkb/wkb_reader.rs b/geozero/src/wkb/wkb_reader.rs index c2068d47..1130f7f1 100644 --- a/geozero/src/wkb/wkb_reader.rs +++ b/geozero/src/wkb/wkb_reader.rs @@ -4,6 +4,11 @@ use crate::{GeomProcessor, GeozeroGeometry}; use scroll::IOread; use std::io::Read; +#[cfg(feature = "with-postgis-diesel")] +use crate::postgis::diesel::sql_types::{Geography, Geometry}; +#[cfg(feature = "with-postgis-diesel")] +use diesel::expression::AsExpression; + /// WKB reader. pub struct Wkb(pub Vec); @@ -14,6 +19,12 @@ impl GeozeroGeometry for Wkb { } /// EWKB reader. +#[cfg_attr( + feature = "with-postgis-diesel", + derive(Debug, AsExpression, PartialEq) +)] +#[cfg_attr(feature = "with-postgis-diesel", diesel(sql_type = Geometry))] +#[cfg_attr(feature = "with-postgis-diesel", diesel(sql_type = Geography))] pub struct Ewkb(pub Vec); impl GeozeroGeometry for Ewkb {