From 2c83c50eb058eb615a8c038facaa8bfeb594d2d5 Mon Sep 17 00:00:00 2001 From: Sindri Frostason Date: Thu, 17 Jul 2025 13:58:11 +0000 Subject: [PATCH] feat: Add support for postgres 'point' type --- Cargo.toml | 2 + sea-query-binder/Cargo.toml | 5 ++- sea-query-binder/src/sqlx_postgres.rs | 4 ++ sea-query-postgres/Cargo.toml | 1 + sea-query-postgres/src/lib.rs | 2 + src/backend/mysql/table.rs | 1 + src/backend/postgres/table.rs | 1 + src/backend/query_builder.rs | 4 ++ src/backend/sqlite/table.rs | 1 + src/table/column.rs | 2 + src/value.rs | 64 +++++++++++++++++++++++++++ 11 files changed, 85 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7c44f442d..f8d0997d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ time = { version = "0.3.36", default-features = false, optional = true, features ipnetwork = { version = "0.20", default-features = false, optional = true } mac_address = { version = "1.1", default-features = false, optional = true } ordered-float = { version = "4.6", default-features = false, optional = true } +sqlx = { version = "0.8", default-features = false, optional = true } [dev-dependencies] sea-query = { path = ".", features = ["tests-cfg"] } @@ -68,6 +69,7 @@ with-uuid = ["uuid"] with-time = ["time"] with-ipnetwork = ["ipnetwork"] with-mac_address = ["mac_address"] +with-postgres-point = ["sqlx", "sqlx/postgres"] tests-cfg = [] all-features = [ "backend-mysql", diff --git a/sea-query-binder/Cargo.toml b/sea-query-binder/Cargo.toml index 44ed4d853..68ad606a6 100644 --- a/sea-query-binder/Cargo.toml +++ b/sea-query-binder/Cargo.toml @@ -3,9 +3,9 @@ [package] name = "sea-query-binder" -version = "0.7.0" +version = "0.8.0-rc.2" authors = [ "Valentin Tolmer ", "Ivan Krivosheev " ] -edition = "2021" +edition = "2024" description = "Driver library for using SeaQuery with SQLx" license = "MIT OR Apache-2.0" documentation = "https://docs.rs/sea-query" @@ -42,6 +42,7 @@ with-uuid = ["sqlx?/uuid", "sea-query/with-uuid", "uuid"] with-time = ["sqlx?/time", "sea-query/with-time", "time"] with-ipnetwork = ["sqlx?/ipnetwork", "sea-query/with-ipnetwork", "ipnetwork"] with-mac_address = ["sqlx?/mac_address", "sea-query/with-mac_address", "mac_address"] +with-postgres-point = ["sea-query/with-postgres-point"] postgres-array = ["sea-query/postgres-array"] postgres-vector = ["sea-query/postgres-vector", "pgvector/sqlx"] runtime-async-std = ["sqlx?/runtime-async-std"] diff --git a/sea-query-binder/src/sqlx_postgres.rs b/sea-query-binder/src/sqlx_postgres.rs index 1e91ccad5..bd46d3680 100644 --- a/sea-query-binder/src/sqlx_postgres.rs +++ b/sea-query-binder/src/sqlx_postgres.rs @@ -129,6 +129,10 @@ impl sqlx::IntoArguments<'_, sqlx::postgres::Postgres> for SqlxValues { Value::MacAddress(mac) => { let _ = args.add(mac.as_deref()); } + #[cfg(feature = "with-postgres-point")] + Value::Point(point) => { + let _ = args.add(point.as_deref()); + } #[cfg(feature = "postgres-array")] Value::Array(ty, v) => match ty { ArrayType::Bool => { diff --git a/sea-query-postgres/Cargo.toml b/sea-query-postgres/Cargo.toml index 5cb41a7ce..987e5bc15 100644 --- a/sea-query-postgres/Cargo.toml +++ b/sea-query-postgres/Cargo.toml @@ -39,3 +39,4 @@ postgres-array = ["postgres-types/array-impls", "sea-query/postgres-array"] postgres-vector = ["sea-query/postgres-vector", "pgvector/postgres"] with-ipnetwork = ["postgres-types/with-cidr-0_2", "sea-query/with-ipnetwork", "ipnetwork", "cidr"] with-mac_address = ["postgres-types/with-eui48-1", "sea-query/with-mac_address", "mac_address", "eui48"] +with-postgres-point = ["sea-query/with-postgres-point"] diff --git a/sea-query-postgres/src/lib.rs b/sea-query-postgres/src/lib.rs index 130567457..a1ec71a59 100644 --- a/sea-query-postgres/src/lib.rs +++ b/sea-query-postgres/src/lib.rs @@ -137,6 +137,8 @@ impl ToSql for PostgresValue { .map(|v| MacAddress::new(v.bytes())) .to_sql(ty, out) } + #[cfg(feature = "with-postgres-point")] + Value::Point(v) => v.as_deref().to_sql(ty, out), } } diff --git a/src/backend/mysql/table.rs b/src/backend/mysql/table.rs index 0da5ef304..ca1661f03 100644 --- a/src/backend/mysql/table.rs +++ b/src/backend/mysql/table.rs @@ -95,6 +95,7 @@ impl TableBuilder for MysqlQueryBuilder { ColumnType::Inet => unimplemented!("Inet is not available in MySQL."), ColumnType::MacAddr => unimplemented!("MacAddr is not available in MySQL."), ColumnType::LTree => unimplemented!("LTree is not available in MySQL."), + ColumnType::Point => unimplemented!("Point is not available in MySQL."), } ) .unwrap(); diff --git a/src/backend/postgres/table.rs b/src/backend/postgres/table.rs index bf2f5c22f..1a9b13d05 100644 --- a/src/backend/postgres/table.rs +++ b/src/backend/postgres/table.rs @@ -82,6 +82,7 @@ impl TableBuilder for PostgresQueryBuilder { ColumnType::MacAddr => "macaddr".into(), ColumnType::Year => unimplemented!("Year is not available in Postgres."), ColumnType::LTree => "ltree".into(), + ColumnType::Point => "point".into(), } ) .unwrap() diff --git a/src/backend/query_builder.rs b/src/backend/query_builder.rs index 7709f4456..b90f1c37d 100644 --- a/src/backend/query_builder.rs +++ b/src/backend/query_builder.rs @@ -1110,6 +1110,8 @@ pub trait QueryBuilder: Value::Array(_, None) => write!(s, "NULL").unwrap(), #[cfg(feature = "postgres-vector")] Value::Vector(None) => write!(s, "NULL").unwrap(), + #[cfg(feature = "with-postgres-point")] + Value::Point(None) => write!(s, "NULL").unwrap(), Value::Bool(Some(b)) => write!(s, "{}", if *b { "TRUE" } else { "FALSE" }).unwrap(), Value::TinyInt(Some(v)) => write!(s, "{v}").unwrap(), Value::SmallInt(Some(v)) => write!(s, "{v}").unwrap(), @@ -1204,6 +1206,8 @@ pub trait QueryBuilder: Value::IpNetwork(Some(v)) => write!(s, "'{v}'").unwrap(), #[cfg(feature = "with-mac_address")] Value::MacAddress(Some(v)) => write!(s, "'{v}'").unwrap(), + #[cfg(feature = "with-postgres-point")] + Value::Point(Some(v)) => write!(s, "'({},{})'", v.x, v.y).unwrap(), }; s } diff --git a/src/backend/sqlite/table.rs b/src/backend/sqlite/table.rs index 429a4572e..ea9500f8c 100644 --- a/src/backend/sqlite/table.rs +++ b/src/backend/sqlite/table.rs @@ -192,6 +192,7 @@ impl SqliteQueryBuilder { ColumnType::Bit(_) => unimplemented!("Bit is not available in Sqlite."), ColumnType::VarBit(_) => unimplemented!("VarBit is not available in Sqlite."), ColumnType::LTree => unimplemented!("LTree is not available in Sqlite."), + ColumnType::Point => unimplemented!("Point is not available in MySQL."), } ) .unwrap() diff --git a/src/table/column.rs b/src/table/column.rs index 97677c524..05d833d86 100644 --- a/src/table/column.rs +++ b/src/table/column.rs @@ -57,6 +57,7 @@ pub trait IntoColumnDef { /// | Inet | N/A | inet | N/A | /// | MacAddr | N/A | macaddr | N/A | /// | LTree | N/A | ltree | N/A | +/// | Point | N/A | point | N/A | #[non_exhaustive] #[derive(Debug, Clone)] pub enum ColumnType { @@ -102,6 +103,7 @@ pub enum ColumnType { Inet, MacAddr, LTree, + Point, } /// Length for var-char/binary; default to 255 diff --git a/src/value.rs b/src/value.rs index f4ba0b8cb..9261e5e5b 100644 --- a/src/value.rs +++ b/src/value.rs @@ -31,6 +31,9 @@ use std::net::IpAddr; #[cfg(feature = "with-mac_address")] use mac_address::MacAddress; +#[cfg(feature = "with-postgres-point")] +use sqlx::postgres::types::PgPoint; + use crate::{ColumnType, CommonSqlQueryBuilder, QueryBuilder, StringLen}; /// [`Value`] types variant for Postgres array @@ -213,6 +216,10 @@ pub enum Value { #[cfg(feature = "with-mac_address")] #[cfg_attr(docsrs, doc(cfg(feature = "with-mac_address")))] MacAddress(Option>), + + #[cfg(feature = "with-postgres-point")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-postgres-point")))] + Point(Option>), } impl std::fmt::Display for Value { @@ -400,6 +407,10 @@ impl Value { #[cfg(feature = "with-mac_address")] #[cfg_attr(docsrs, doc(cfg(feature = "with-mac_address")))] Self::MacAddress(_) => Self::MacAddress(None), + + #[cfg(feature = "with-postgres-point")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-postgres-point")))] + Self::Point(_) => Self::Point(None), } } @@ -504,6 +515,10 @@ impl Value { #[cfg(feature = "with-mac_address")] #[cfg_attr(docsrs, doc(cfg(feature = "with-mac_address")))] Self::MacAddress(_) => Self::MacAddress(Some(Default::default())), + + #[cfg(feature = "with-postgres-point")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-postgres-point")))] + Self::Point(_) => Self::Point(Some(Box::new(PgPoint { x: 0., y: 0. }))), } } } @@ -1124,6 +1139,45 @@ pub mod with_vector { } } +#[cfg(feature = "with-postgres-point")] +#[cfg_attr(docsrs, doc(cfg(feature = "with-postgres-point")))] +mod with_postgres_point { + use super::*; + + impl From for Value { + fn from(x: PgPoint) -> Value { + Value::Point(Some(Box::new(x))) + } + } + + impl Nullable for PgPoint { + fn null() -> Value { + Value::Point(None) + } + } + + impl ValueType for PgPoint { + fn try_from(v: Value) -> Result { + match v { + Value::Point(Some(x)) => Ok(*x), + _ => Err(ValueTypeErr), + } + } + + fn type_name() -> String { + stringify!(Point).to_owned() + } + + fn array_type() -> ArrayType { + unimplemented!() + } + + fn column_type() -> ColumnType { + ColumnType::Point + } + } +} + #[allow(unused_macros)] macro_rules! box_to_opt_ref { ( $v: expr ) => { @@ -1629,6 +1683,8 @@ pub fn sea_value_to_json_value(value: &Value) -> Json { Value::IpNetwork(None) => Json::Null, #[cfg(feature = "with-mac_address")] Value::MacAddress(None) => Json::Null, + #[cfg(feature = "with-postgres-point")] + Value::Point(None) => Json::Null, Value::Bool(Some(b)) => Json::Bool(*b), Value::TinyInt(Some(v)) => (*v).into(), Value::SmallInt(Some(v)) => (*v).into(), @@ -1686,6 +1742,8 @@ pub fn sea_value_to_json_value(value: &Value) -> Json { Value::IpNetwork(Some(_)) => CommonSqlQueryBuilder.value_to_string(value).into(), #[cfg(feature = "with-mac_address")] Value::MacAddress(Some(_)) => CommonSqlQueryBuilder.value_to_string(value).into(), + #[cfg(feature = "with-postgres-point")] + Value::Point(Some(_)) => CommonSqlQueryBuilder.value_to_string(value).into(), } } @@ -2300,6 +2358,12 @@ mod hashable_value { #[cfg(feature = "with-mac_address")] Value::MacAddress(mac_address) => mac_address.hash(state), + + #[cfg(feature = "with-postgres-point")] + Value::Point(point) => { + hash_f64(&point.as_ref().map(|p| p.x), state); + hash_f64(&point.as_ref().map(|p| p.y), state); + } } } }