feat(postgres): point (#3583)
* feat: point * test: try if eq operator works for arrays of geometries * fix: re-introduce comparison * fix: test other geometry comparison * test: geometry array equality check * test: array match for geo arrays geo match for geo only * fix: prepare geometric array type * fix: update array comparison * fix: try another method of geometric array comparison * fix: one more geometry match tests * fix: correct query syntax * test: geometry test further
This commit is contained in:
parent
3e8952b0d4
commit
a7f2928a1b
6 changed files with 183 additions and 0 deletions
|
@ -32,6 +32,8 @@ impl_type_checking!(
|
|||
|
||||
sqlx::postgres::types::PgCube,
|
||||
|
||||
sqlx::postgres::types::PgPoint,
|
||||
|
||||
#[cfg(feature = "uuid")]
|
||||
sqlx::types::Uuid,
|
||||
|
||||
|
|
1
sqlx-postgres/src/types/geometry/mod.rs
Normal file
1
sqlx-postgres/src/types/geometry/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod point;
|
138
sqlx-postgres/src/types/geometry/point.rs
Normal file
138
sqlx-postgres/src/types/geometry/point.rs
Normal file
|
@ -0,0 +1,138 @@
|
|||
use crate::decode::Decode;
|
||||
use crate::encode::{Encode, IsNull};
|
||||
use crate::error::BoxDynError;
|
||||
use crate::types::Type;
|
||||
use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres};
|
||||
use sqlx_core::bytes::Buf;
|
||||
use sqlx_core::Error;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// ## Postgres Geometric Point type
|
||||
///
|
||||
/// Description: Point on a plane
|
||||
/// Representation: `(x, y)`
|
||||
///
|
||||
/// Points are the fundamental two-dimensional building block for geometric types. Values of type point are specified using either of the following syntaxes:
|
||||
/// ```text
|
||||
/// ( x , y )
|
||||
/// x , y
|
||||
/// ````
|
||||
/// where x and y are the respective coordinates, as floating-point numbers.
|
||||
///
|
||||
/// See https://www.postgresql.org/docs/16/datatype-geometric.html#DATATYPE-GEOMETRIC-POINTS
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct PgPoint {
|
||||
pub x: f64,
|
||||
pub y: f64,
|
||||
}
|
||||
|
||||
impl Type<Postgres> for PgPoint {
|
||||
fn type_info() -> PgTypeInfo {
|
||||
PgTypeInfo::with_name("point")
|
||||
}
|
||||
}
|
||||
|
||||
impl PgHasArrayType for PgPoint {
|
||||
fn array_type_info() -> PgTypeInfo {
|
||||
PgTypeInfo::with_name("_point")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> Decode<'r, Postgres> for PgPoint {
|
||||
fn decode(value: PgValueRef<'r>) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
||||
match value.format() {
|
||||
PgValueFormat::Text => Ok(PgPoint::from_str(value.as_str()?)?),
|
||||
PgValueFormat::Binary => Ok(PgPoint::from_bytes(value.as_bytes()?)?),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'q> Encode<'q, Postgres> for PgPoint {
|
||||
fn produces(&self) -> Option<PgTypeInfo> {
|
||||
Some(PgTypeInfo::with_name("point"))
|
||||
}
|
||||
|
||||
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
|
||||
self.serialize(buf)?;
|
||||
Ok(IsNull::No)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_float_from_str(s: &str, error_msg: &str) -> Result<f64, Error> {
|
||||
s.trim()
|
||||
.parse()
|
||||
.map_err(|_| Error::Decode(error_msg.into()))
|
||||
}
|
||||
|
||||
impl FromStr for PgPoint {
|
||||
type Err = BoxDynError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let (x_str, y_str) = s
|
||||
.trim_matches(|c| c == '(' || c == ')' || c == ' ')
|
||||
.split_once(',')
|
||||
.ok_or_else(|| format!("error decoding POINT: could not get x and y from {}", s))?;
|
||||
|
||||
let x = parse_float_from_str(x_str, "error decoding POINT: could not get x")?;
|
||||
let y = parse_float_from_str(y_str, "error decoding POINT: could not get x")?;
|
||||
|
||||
Ok(PgPoint { x, y })
|
||||
}
|
||||
}
|
||||
|
||||
impl PgPoint {
|
||||
fn from_bytes(mut bytes: &[u8]) -> Result<PgPoint, BoxDynError> {
|
||||
let x = bytes.get_f64();
|
||||
let y = bytes.get_f64();
|
||||
Ok(PgPoint { x, y })
|
||||
}
|
||||
|
||||
fn serialize(&self, buff: &mut PgArgumentBuffer) -> Result<(), BoxDynError> {
|
||||
buff.extend_from_slice(&self.x.to_be_bytes());
|
||||
buff.extend_from_slice(&self.y.to_be_bytes());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn serialize_to_vec(&self) -> Vec<u8> {
|
||||
let mut buff = PgArgumentBuffer::default();
|
||||
self.serialize(&mut buff).unwrap();
|
||||
buff.to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod point_tests {
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use super::PgPoint;
|
||||
|
||||
const POINT_BYTES: &[u8] = &[
|
||||
64, 0, 204, 204, 204, 204, 204, 205, 64, 20, 204, 204, 204, 204, 204, 205,
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn can_deserialise_point_type_bytes() {
|
||||
let point = PgPoint::from_bytes(POINT_BYTES).unwrap();
|
||||
assert_eq!(point, PgPoint { x: 2.1, y: 5.2 })
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_deserialise_point_type_str() {
|
||||
let point = PgPoint::from_str("(2, 3)").unwrap();
|
||||
assert_eq!(point, PgPoint { x: 2., y: 3. });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_deserialise_point_type_str_float() {
|
||||
let point = PgPoint::from_str("(2.5, 3.4)").unwrap();
|
||||
assert_eq!(point, PgPoint { x: 2.5, y: 3.4 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_serialise_point_type() {
|
||||
let point = PgPoint { x: 2.1, y: 5.2 };
|
||||
assert_eq!(point.serialize_to_vec(), POINT_BYTES,)
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@
|
|||
//! | [`PgLQuery`] | LQUERY |
|
||||
//! | [`PgCiText`] | CITEXT<sup>1</sup> |
|
||||
//! | [`PgCube`] | CUBE |
|
||||
//! | [`PgPoint] | POINT |
|
||||
//! | [`PgHstore`] | HSTORE |
|
||||
//!
|
||||
//! <sup>1</sup> SQLx generally considers `CITEXT` to be compatible with `String`, `&str`, etc.,
|
||||
|
@ -212,6 +213,8 @@ mod bigdecimal;
|
|||
|
||||
mod cube;
|
||||
|
||||
mod geometry;
|
||||
|
||||
#[cfg(any(feature = "bigdecimal", feature = "rust_decimal"))]
|
||||
mod numeric;
|
||||
|
||||
|
@ -242,6 +245,7 @@ mod bit_vec;
|
|||
pub use array::PgHasArrayType;
|
||||
pub use citext::PgCiText;
|
||||
pub use cube::PgCube;
|
||||
pub use geometry::point::PgPoint;
|
||||
pub use hstore::PgHstore;
|
||||
pub use interval::PgInterval;
|
||||
pub use lquery::PgLQuery;
|
||||
|
|
|
@ -51,6 +51,18 @@ macro_rules! test_type {
|
|||
}
|
||||
};
|
||||
|
||||
($name:ident<$ty:ty>($db:ident, $($text:literal ~= $value:expr),+ $(,)?)) => {
|
||||
paste::item! {
|
||||
$crate::__test_prepared_type!($name<$ty>($db, $crate::[< $db _query_for_test_prepared_geometric_type >]!(), $($text == $value),+));
|
||||
}
|
||||
};
|
||||
($name:ident<$ty:ty>($db:ident, $($text:literal @= $value:expr),+ $(,)?)) => {
|
||||
paste::item! {
|
||||
$crate::__test_prepared_type!($name<$ty>($db, $crate::[< $db _query_for_test_prepared_geometric_array_type >]!(), $($text == $value),+));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
($name:ident($db:ident, $($text:literal == $value:expr),+ $(,)?)) => {
|
||||
$crate::test_type!($name<$name>($db, $($text == $value),+));
|
||||
};
|
||||
|
@ -82,6 +94,7 @@ macro_rules! test_prepared_type {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
($name:ident($db:ident, $($text:literal == $value:expr),+ $(,)?)) => {
|
||||
$crate::__test_prepared_type!($name<$name>($db, $($text == $value),+));
|
||||
};
|
||||
|
@ -223,3 +236,17 @@ macro_rules! Postgres_query_for_test_prepared_type {
|
|||
"SELECT ({0} is not distinct from $1)::int4, {0}, $2"
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! Postgres_query_for_test_prepared_geometric_type {
|
||||
() => {
|
||||
"SELECT ({0} ~= $1)::int4, {0}, $2"
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! Postgres_query_for_test_prepared_geometric_array_type {
|
||||
() => {
|
||||
"SELECT (SELECT bool_and(geo1.geometry ~= geo2.geometry) FROM unnest({0}) WITH ORDINALITY AS geo1(geometry, idx) JOIN unnest($1) WITH ORDINALITY AS geo2(geometry, idx) ON geo1.idx = geo2.idx)::int4, {0}, $2"
|
||||
};
|
||||
}
|
||||
|
|
|
@ -492,6 +492,17 @@ test_type!(_cube<Vec<sqlx::postgres::types::PgCube>>(Postgres,
|
|||
"array[cube(2.2,-3.4)]" == vec![sqlx::postgres::types::PgCube::OneDimensionInterval(2.2, -3.4)],
|
||||
));
|
||||
|
||||
#[cfg(any(postgres_12, postgres_13, postgres_14, postgres_15))]
|
||||
test_type!(point<sqlx::postgres::types::PgPoint>(Postgres,
|
||||
"point(2.2,-3.4)" ~= sqlx::postgres::types::PgPoint { x: 2.2, y:-3.4 },
|
||||
));
|
||||
|
||||
#[cfg(any(postgres_12, postgres_13, postgres_14, postgres_15))]
|
||||
test_type!(_point<Vec<sqlx::postgres::types::PgPoint>>(Postgres,
|
||||
"array[point(2,3),point(2.1,3.4)]" @= vec![sqlx::postgres::types::PgPoint { x:2., y: 3. }, sqlx::postgres::types::PgPoint { x:2.1, y: 3.4 }],
|
||||
"array[point(2.2,-3.4)]" @= vec![sqlx::postgres::types::PgPoint { x: 2.2, y: -3.4 }],
|
||||
));
|
||||
|
||||
#[cfg(feature = "rust_decimal")]
|
||||
test_type!(decimal<sqlx::types::Decimal>(Postgres,
|
||||
"0::numeric" == sqlx::types::Decimal::from_str("0").unwrap(),
|
||||
|
|
Loading…
Add table
Reference in a new issue