Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support geometry #3449

Open
wants to merge 64 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
4248c94
fix: clean branch
jayy-lmao Aug 24, 2024
d16c98e
feat: points and boxes
jayy-lmao Aug 24, 2024
1c5387c
test: geo array test support
jayy-lmao Aug 24, 2024
d13fc3c
test: path tests
jayy-lmao Aug 24, 2024
01ea1fa
fix: imports
jayy-lmao Aug 24, 2024
1a64ebb
fix: impl point
jayy-lmao Aug 27, 2024
98dfbdf
fix: update point
jayy-lmao Aug 28, 2024
a69378c
fix: line and line seg
jayy-lmao Aug 28, 2024
845cadd
fix: box
jayy-lmao Aug 28, 2024
0993ef0
fix: path
jayy-lmao Aug 28, 2024
4e58b17
fix: path header length
jayy-lmao Aug 28, 2024
ffe6103
fix: line segment
jayy-lmao Aug 28, 2024
1154397
fix: split not just 4 for path
jayy-lmao Aug 28, 2024
409a50b
fix: test for boxes
jayy-lmao Aug 29, 2024
a5c7c00
fix: path link
jayy-lmao Aug 29, 2024
91ecaa1
fix: pg box name in tests
jayy-lmao Aug 29, 2024
2494a00
fix: syntax for box tests
jayy-lmao Aug 29, 2024
28b4a99
fix: polygon tests
jayy-lmao Aug 30, 2024
d67ddf5
fix: tests for paths and integration tests for polygons
jayy-lmao Aug 30, 2024
a9b0d57
docs: copy in some postgres docs
jayy-lmao Aug 30, 2024
4315174
docs: remove duplicate docs
jayy-lmao Aug 30, 2024
6e535e9
fix: polygon in docs
jayy-lmao Aug 30, 2024
afb00b5
docs: align md table
jayy-lmao Aug 30, 2024
c2f652f
docs: remove unused comments
jayy-lmao Aug 30, 2024
0a34651
test: make box ordering test explicit
jayy-lmao Aug 30, 2024
a1c8a64
feat: add circle
jayy-lmao Aug 30, 2024
b9ea489
test: circle tests
jayy-lmao Aug 30, 2024
5def297
test: import pgpoint to avoid repeat nesting
jayy-lmao Aug 30, 2024
9be8dd6
tests: polygon integ and circle unit
jayy-lmao Aug 30, 2024
1ddb3e9
fix: update sqlx-postgres/src/types/geometry/circle.rs
jayy-lmao Sep 2, 2024
2ea7d16
fix: circle
jayy-lmao Sep 2, 2024
c538aa4
test: fix type tests for polygon
jayy-lmao Sep 2, 2024
92a4bdb
fix: circle from bytes
jayy-lmao Sep 2, 2024
305e95c
fix: remove double decodes
jayy-lmao Sep 2, 2024
2d28514
fix: circle test
jayy-lmao Sep 3, 2024
fc6f3f3
test: boxes sort
jayy-lmao Sep 3, 2024
0662628
docs: update doc headerws
jayy-lmao Sep 3, 2024
65184f7
test: invalid box syntax
jayy-lmao Sep 3, 2024
5a165df
fix: circle syntax
jayy-lmao Sep 3, 2024
95ec809
test: box ordering in array test
jayy-lmao Sep 3, 2024
24486e2
docs: remove output comments
jayy-lmao Sep 3, 2024
d3082c8
fix: usize max in max lengths
jayy-lmao Sep 3, 2024
8207d89
fix: handle uneven number of points
jayy-lmao Sep 3, 2024
4dd55c3
fix: capacity
jayy-lmao Sep 3, 2024
8ec8142
fix: order of operations
jayy-lmao Sep 3, 2024
99d8368
fix: uneven pairs in polygons
jayy-lmao Sep 3, 2024
2ebb0fb
fix: throw on uneven points
jayy-lmao Sep 3, 2024
cffcec9
fix: unmatched pair errors
jayy-lmao Sep 3, 2024
341650c
fix: box dyn errors for from strings
jayy-lmao Sep 3, 2024
d559b07
fix: remove unused error imports
jayy-lmao Sep 8, 2024
a3c25f3
fix: radius variable circle
jayy-lmao Sep 8, 2024
5a9a6f6
fix: path import
jayy-lmao Sep 8, 2024
815c22c
fix: splitn str
jayy-lmao Sep 8, 2024
0b49de3
test: more circle tests
jayy-lmao Sep 8, 2024
bf0d599
test: lines
jayy-lmao Sep 8, 2024
782005e
fix: splits
jayy-lmao Sep 8, 2024
e343924
fix: splits
jayy-lmao Sep 8, 2024
79f18f1
fix: lower upper with box
jayy-lmao Sep 8, 2024
e67b195
fix: circle too many points
jayy-lmao Sep 8, 2024
bbda7d0
fix: line too many points
jayy-lmao Sep 8, 2024
89fd0b0
fix: too many
jayy-lmao Sep 8, 2024
896f44a
fix: variable names for box and lseg
jayy-lmao Sep 8, 2024
5e36b65
test: circle test radius
jayy-lmao Sep 8, 2024
963d1d4
test: fix upper right y
jayy-lmao Sep 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions sqlx-postgres/src/type_checking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,20 @@ impl_type_checking!(

sqlx::postgres::types::PgCube,

sqlx::postgres::types::PgPoint,

sqlx::postgres::types::PgLine,

sqlx::postgres::types::PgLSeg,

sqlx::postgres::types::PgBox,

sqlx::postgres::types::PgPath,

sqlx::postgres::types::PgPolygon,

sqlx::postgres::types::PgCircle,

#[cfg(feature = "uuid")]
sqlx::types::Uuid,

Expand Down
321 changes: 321 additions & 0 deletions sqlx-postgres/src/types/geometry/box.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,321 @@
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 std::str::FromStr;

const ERROR: &str = "error decoding BOX";

/// ## Postgres Geometric Box type
///
/// Description: Rectangular box
/// Representation: `((upper_right_x,upper_right_y),(lower_left_x,lower_left_y))`
///
/// Boxes are represented by pairs of points that are opposite corners of the box. Values of type box are specified using any of the following syntaxes:
///
/// ```text
/// ( ( upper_right_x , upper_right_y ) , ( lower_left_x , lower_left_y ) )
/// ( upper_right_x , upper_right_y ) , ( lower_left_x , lower_left_y )
/// upper_right_x , upper_right_y , lower_left_x , lower_left_y
/// ```
/// where `(upper_right_x,upper_right_y) and (lower_left_x,lower_left_y)` are any two opposite corners of the box.
/// Any two opposite corners can be supplied on input, but the values will be reordered as needed to store the upper right and lower left corners, in that order.
///
/// See https://www.postgresql.org/docs/16/datatype-geometric.html#DATATYPE-GEOMETRIC-BOXES
#[derive(Debug, Clone, PartialEq)]
pub struct PgBox {
pub upper_right_x: f64,
pub upper_right_y: f64,
pub lower_left_x: f64,
pub lower_left_y: f64,
}

impl Type<Postgres> for PgBox {
fn type_info() -> PgTypeInfo {
PgTypeInfo::with_name("box")
}
}

impl PgHasArrayType for PgBox {
fn array_type_info() -> PgTypeInfo {
PgTypeInfo::with_name("_box")
}
}

impl<'r> Decode<'r, Postgres> for PgBox {
fn decode(value: PgValueRef<'r>) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
match value.format() {
PgValueFormat::Text => Ok(PgBox::from_str(value.as_str()?)?),
PgValueFormat::Binary => Ok(PgBox::from_bytes(value.as_bytes()?)?),
}
}
}

impl<'q> Encode<'q, Postgres> for PgBox {
fn produces(&self) -> Option<PgTypeInfo> {
Some(PgTypeInfo::with_name("box"))
}

fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
self.serialize(buf)?;
Ok(IsNull::No)
}
}

impl FromStr for PgBox {
type Err = BoxDynError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let sanitised = s.replace(['(', ')', '[', ']', ' '], "");
let mut parts = sanitised.split(',');

let upper_right_x = parts
.next()
.and_then(|s| s.parse::<f64>().ok())
.ok_or_else(|| format!("{}: could not get upper_right_x from {}", ERROR, s))?;

let upper_right_y = parts
.next()
.and_then(|s| s.parse::<f64>().ok())
.ok_or_else(|| format!("{}: could not get upper_right_y from {}", ERROR, s))?;

let lower_left_x = parts
.next()
.and_then(|s| s.parse::<f64>().ok())
.ok_or_else(|| format!("{}: could not get lower_left_x from {}", ERROR, s))?;

let lower_left_y = parts
.next()
.and_then(|s| s.parse::<f64>().ok())
.ok_or_else(|| format!("{}: could not get lower_left_y from {}", ERROR, s))?;

if parts.next().is_some() {
return Err(format!("{}: too many numbers inputted in {}", ERROR, s).into());
}

Ok(PgBox {
upper_right_x,
upper_right_y,
lower_left_x,
lower_left_y,
})
}
}

impl PgBox {
fn from_bytes(mut bytes: &[u8]) -> Result<PgBox, BoxDynError> {
let upper_right_x = bytes.get_f64();
let upper_right_y = bytes.get_f64();
let lower_left_x = bytes.get_f64();
let lower_left_y = bytes.get_f64();

Ok(PgBox {
upper_right_x,
upper_right_y,
lower_left_x,
lower_left_y,
})
}

fn serialize(&self, buff: &mut PgArgumentBuffer) -> Result<(), String> {
let min_x = &self.upper_right_x.min(self.lower_left_x);
let min_y = &self.upper_right_y.min(self.lower_left_y);
let max_x = &self.upper_right_x.max(self.lower_left_x);
let max_y = &self.upper_right_y.max(self.lower_left_y);

buff.extend_from_slice(&max_x.to_be_bytes());
buff.extend_from_slice(&max_y.to_be_bytes());
buff.extend_from_slice(&min_x.to_be_bytes());
buff.extend_from_slice(&min_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 box_tests {

use std::str::FromStr;

use super::PgBox;

const BOX_BYTES: &[u8] = &[
64, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 0,
0, 0, 0, 0,
];

#[test]
fn can_deserialise_box_type_bytes_in_order() {
let pg_box = PgBox::from_bytes(BOX_BYTES).unwrap();
assert_eq!(
pg_box,
PgBox {
upper_right_x: 2.,
upper_right_y: 2.,
lower_left_x: -2.,
lower_left_y: -2.
}
)
}

#[test]
fn can_deserialise_box_type_str_first_syntax() {
let pg_box = PgBox::from_str("[( 1, 2), (3, 4 )]").unwrap();
assert_eq!(
pg_box,
PgBox {
upper_right_x: 1.,
upper_right_y: 2.,
lower_left_x: 3.,
lower_left_y: 4.
}
);
}
#[test]
fn can_deserialise_box_type_str_second_syntax() {
let pg_box = PgBox::from_str("(( 1, 2), (3, 4 ))").unwrap();
assert_eq!(
pg_box,
PgBox {
upper_right_x: 1.,
upper_right_y: 2.,
lower_left_x: 3.,
lower_left_y: 4.
}
);
}

#[test]
fn can_deserialise_box_type_str_third_syntax() {
let pg_box = PgBox::from_str("(1, 2), (3, 4 )").unwrap();
assert_eq!(
pg_box,
PgBox {
upper_right_x: 1.,
upper_right_y: 2.,
lower_left_x: 3.,
lower_left_y: 4.
}
);
}

#[test]
fn can_deserialise_box_type_str_fourth_syntax() {
let pg_box = PgBox::from_str("1, 2, 3, 4").unwrap();
assert_eq!(
pg_box,
PgBox {
upper_right_x: 1.,
upper_right_y: 2.,
lower_left_x: 3.,
lower_left_y: 4.
}
);
}

#[test]
fn cannot_deserialise_too_many_numbers() {
let input_str = "1, 2, 3, 4, 5";
let pg_box = PgBox::from_str(input_str);
assert!(pg_box.is_err());
if let Err(err) = pg_box {
assert_eq!(
err.to_string(),
format!("error decoding BOX: too many numbers inputted in {input_str}")
)
}
}

#[test]
fn cannot_deserialise_too_few_numbers() {
let input_str = "1, 2, 3 ";
let pg_box = PgBox::from_str(input_str);
assert!(pg_box.is_err());
if let Err(err) = pg_box {
assert_eq!(
err.to_string(),
format!("error decoding BOX: could not get lower_left_y from {input_str}")
)
}
}

#[test]
fn cannot_deserialise_invalid_numbers() {
let input_str = "1, 2, 3, FOUR";
let pg_box = PgBox::from_str(input_str);
assert!(pg_box.is_err());
if let Err(err) = pg_box {
assert_eq!(
err.to_string(),
format!("error decoding BOX: could not get lower_left_y from {input_str}")
)
}
}

#[test]
fn can_deserialise_box_type_str_float() {
let pg_box = PgBox::from_str("(1.1, 2.2), (3.3, 4.4)").unwrap();
assert_eq!(
pg_box,
PgBox {
upper_right_x: 1.1,
upper_right_y: 2.2,
lower_left_x: 3.3,
lower_left_y: 4.4
}
);
}

#[test]
fn can_serialise_box_type_in_order() {
let pg_box = PgBox {
upper_right_x: 2.,
lower_left_x: -2.,
upper_right_y: -2.,
lower_left_y: 2.,
};
assert_eq!(pg_box.serialize_to_vec(), BOX_BYTES,)
}

#[test]
fn can_serialise_box_type_out_of_order() {
let pg_box = PgBox {
upper_right_x: -2.,
lower_left_x: 2.,
upper_right_y: 2.,
lower_left_y: -2.,
};
assert_eq!(pg_box.serialize_to_vec(), BOX_BYTES,)
}

#[test]
fn can_order_box() {
let pg_box = PgBox {
upper_right_x: -2.,
lower_left_x: 2.,
upper_right_y: 2.,
lower_left_y: -2.,
};
let bytes = pg_box.serialize_to_vec();

let pg_box = PgBox::from_bytes(&bytes).unwrap();
assert_eq!(
pg_box,
PgBox {
upper_right_x: 2.,
upper_right_y: 2.,
lower_left_x: -2.,
lower_left_y: -2.
}
)
}
}
Loading