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 17 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
10 changes: 10 additions & 0 deletions sqlx-postgres/src/type_checking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ 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,

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

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

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

/// Postgres Geometric Box type
///
/// Storage size: 32 bytes
/// Description: Rectangular box
/// Representation: ((x1,y1),(x2,y2))
///
/// See https://www.postgresql.org/docs/16/datatype-geometric.html#DATATYPE-GEOMETRIC-BOXES
#[derive(Debug, Clone, PartialEq)]
pub struct PgBox {
pub x1: f64,
pub y1: f64,
pub x2: f64,
pub y2: 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 = Error;

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

let x1 = parts
.next()
.and_then(|s| s.parse::<f64>().ok())
.ok_or(Error::Decode(
format!("{}: could not get x1 from {}", ERROR, s).into(),
))?;
jayy-lmao marked this conversation as resolved.
Show resolved Hide resolved

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

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

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

Ok(PgBox { x1, y1, x2, y2 })
}
}

impl PgBox {
fn from_bytes(mut bytes: &[u8]) -> Result<PgBox, Error> {
Copy link
Contributor Author

@jayy-lmao jayy-lmao Aug 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PgCube uses a BoxDynError here, anyone know if that's essential here?

https://github.com/jayy-lmao/sqlx/blob/feat/support-geometry-postgres/sqlx-postgres/src/types/cube.rs#L138

Copy link
Collaborator

@abonander abonander Aug 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any error returned from Decode::decode() gets wrapped in sqlx::Error::Decode().

Thus, if you return sqlx::Error it'll turn into sqlx::Error::Decode(Box::new(sqlx::Error::Decode(...))) which is redundant.

String is directly convertible to Box<dyn Error> with .into() or the ? operator, so you don't need to wrap it in anything: https://doc.rust-lang.org/stable/std/error/trait.Error.html#impl-From%3CString%3E-for-Box%3Cdyn+Error+%2B+Send+%2B+Sync%3E

It'd probably be better to switch to a real error type that captures more context, but that's orthogonal to this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the explanation

let x1 = bytes.get_f64();
let y1 = bytes.get_f64();
let x2 = bytes.get_f64();
let y2 = bytes.get_f64();

Ok(PgBox { x1, y1, x2, y2 })
}

fn serialize(&self, buff: &mut PgArgumentBuffer) -> Result<(), Error> {
buff.extend_from_slice(&self.x1.to_be_bytes());
buff.extend_from_slice(&self.y1.to_be_bytes());
buff.extend_from_slice(&self.x2.to_be_bytes());
buff.extend_from_slice(&self.y2.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] = &[
63, 241, 153, 153, 153, 153, 153, 154, 64, 1, 153, 153, 153, 153, 153, 154, 64, 10, 102,
102, 102, 102, 102, 102, 64, 17, 153, 153, 153, 153, 153, 154,
];

#[test]
fn can_deserialise_box_type_bytes() {
let pg_box = PgBox::from_bytes(BOX_BYTES).unwrap();
assert_eq!(
pg_box,
PgBox {
x1: 1.1,
y1: 2.2,
x2: 3.3,
y2: 4.4
}
)
}

#[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 {
x1: 1.,
y1: 2.,
x2: 3.,
y2: 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 {
x1: 1.,
y1: 2.,
x2: 3.,
y2: 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 {
x1: 1.,
y1: 2.,
x2: 3.,
y2: 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 {
x1: 1.,
y1: 2.,
x2: 3.,
y2: 4.
}
);
}

#[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 {
x1: 1.1,
y1: 2.2,
x2: 3.3,
y2: 4.4
}
);
}

#[test]
fn can_serialise_box_type() {
let pg_box = PgBox {
x1: 1.1,
y1: 2.2,
x2: 3.3,
y2: 4.4,
};
assert_eq!(pg_box.serialize_to_vec(), BOX_BYTES,)
}
}
Loading
Loading