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: add geometry line #3623

Merged
merged 6 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 6 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions sqlx-postgres/src/type_checking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ impl_type_checking!(

sqlx::postgres::types::PgPoint,

sqlx::postgres::types::PgLine,

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

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

/// ## Postgres Geometric Line type
///
/// Description: Infinite line
/// Representation: `{A, B, C}`
///
/// Lines are represented by the linear equation Ax + By + C = 0, where A and B are not both zero.
///
/// See https://www.postgresql.org/docs/16/datatype-geometric.html#DATATYPE-LINE
#[derive(Debug, Clone, PartialEq)]
pub struct PgLine {
pub a: f64,
pub b: f64,
pub c: f64,
}

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

impl PgHasArrayType for PgLine {
fn array_type_info() -> PgTypeInfo {
PgTypeInfo::with_name("_line")
}
}

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

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

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

impl FromStr for PgLine {
type Err = BoxDynError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s
.trim_matches(|c| c == '{' || c == '}' || c == ' ')
.split(',');

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

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

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

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

Ok(PgLine { a, b, c })
}
}

impl PgLine {
fn from_bytes(mut bytes: &[u8]) -> Result<PgLine, BoxDynError> {
let a = bytes.get_f64();
let b = bytes.get_f64();
let c = bytes.get_f64();
Ok(PgLine { a, b, c })
}

fn serialize(&self, buff: &mut PgArgumentBuffer) -> Result<(), BoxDynError> {
buff.extend_from_slice(&self.a.to_be_bytes());
buff.extend_from_slice(&self.b.to_be_bytes());
buff.extend_from_slice(&self.c.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 line_tests {

use std::str::FromStr;

use super::PgLine;

const LINE_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,
];

#[test]
fn can_deserialise_line_type_bytes() {
let line = PgLine::from_bytes(LINE_BYTES).unwrap();
assert_eq!(
line,
PgLine {
a: 1.1,
b: 2.2,
c: 3.3
}
)
}

#[test]
fn can_deserialise_line_type_str() {
let line = PgLine::from_str("{ 1, 2, 3 }").unwrap();
assert_eq!(
line,
PgLine {
a: 1.0,
b: 2.0,
c: 3.0
}
);
}

#[test]
fn cannot_deserialise_line_too_few_numbers() {
let input_str = "{ 1, 2 }";
let line = PgLine::from_str(input_str);
assert!(line.is_err());
if let Err(err) = line {
assert_eq!(
err.to_string(),
format!("error decoding LINE: could not get c from {input_str}")
)
}
}

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

#[test]
fn cannot_deserialise_line_invalid_numbers() {
let input_str = "{ 1, 2, three }";
let line = PgLine::from_str(input_str);
assert!(line.is_err());
if let Err(err) = line {
assert_eq!(
err.to_string(),
format!("error decoding LINE: could not get c from {input_str}")
)
}
}

#[test]
fn can_deserialise_line_type_str_float() {
let line = PgLine::from_str("{1.1, 2.2, 3.3}").unwrap();
assert_eq!(
line,
PgLine {
a: 1.1,
b: 2.2,
c: 3.3
}
);
}

#[test]
fn can_serialise_line_type() {
let line = PgLine {
a: 1.1,
b: 2.2,
c: 3.3,
};
assert_eq!(line.serialize_to_vec(), LINE_BYTES,)
}
}
1 change: 1 addition & 0 deletions sqlx-postgres/src/types/geometry/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod line;
pub mod point;
2 changes: 2 additions & 0 deletions sqlx-postgres/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
//! | [`PgCiText`] | CITEXT<sup>1</sup> |
//! | [`PgCube`] | CUBE |
//! | [`PgPoint] | POINT |
//! | [`PgLine] | LINE |
//! | [`PgHstore`] | HSTORE |
//!
//! <sup>1</sup> SQLx generally considers `CITEXT` to be compatible with `String`, `&str`, etc.,
Expand Down Expand Up @@ -245,6 +246,7 @@ mod bit_vec;
pub use array::PgHasArrayType;
pub use citext::PgCiText;
pub use cube::PgCube;
pub use geometry::line::PgLine;
pub use geometry::point::PgPoint;
pub use hstore::PgHstore;
pub use interval::PgInterval;
Expand Down
6 changes: 6 additions & 0 deletions tests/postgres/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,12 @@ test_type!(_point<Vec<sqlx::postgres::types::PgPoint>>(Postgres,
"array[point(2.2,-3.4)]" @= vec![sqlx::postgres::types::PgPoint { x: 2.2, y: -3.4 }],
));

#[cfg(any(postgres_12, postgres_13, postgres_14, postgres_15))]
test_type!(line<sqlx::postgres::types::PgLine>(Postgres,
"line('{1.1, -2.2, 3.3}')" == sqlx::postgres::types::PgLine { a: 1.1, b:-2.2, c: 3.3 },
"line('((0.0, 0.0), (1.0,1.0))')" == sqlx::postgres::types::PgLine { a: 1., b: -1., c: 0. },
));

#[cfg(feature = "rust_decimal")]
test_type!(decimal<sqlx::types::Decimal>(Postgres,
"0::numeric" == sqlx::types::Decimal::from_str("0").unwrap(),
Expand Down
Loading