Skip to content

Commit 1678b19

Browse files
authored
feat: add geometry line (#3623)
* feat: add geometry line * fix: point vs line * fix: try regular comparison for line * fix: undo point comparison change * fix: regular comparison for array lines * fix: remove line array test
1 parent 2b82ce3 commit 1678b19

File tree

5 files changed

+222
-0
lines changed

5 files changed

+222
-0
lines changed

sqlx-postgres/src/type_checking.rs

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ impl_type_checking!(
3434

3535
sqlx::postgres::types::PgPoint,
3636

37+
sqlx::postgres::types::PgLine,
38+
3739
#[cfg(feature = "uuid")]
3840
sqlx::types::Uuid,
3941

+211
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
use crate::decode::Decode;
2+
use crate::encode::{Encode, IsNull};
3+
use crate::error::BoxDynError;
4+
use crate::types::Type;
5+
use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres};
6+
use sqlx_core::bytes::Buf;
7+
use std::str::FromStr;
8+
9+
const ERROR: &str = "error decoding LINE";
10+
11+
/// ## Postgres Geometric Line type
12+
///
13+
/// Description: Infinite line
14+
/// Representation: `{A, B, C}`
15+
///
16+
/// Lines are represented by the linear equation Ax + By + C = 0, where A and B are not both zero.
17+
///
18+
/// See https://www.postgresql.org/docs/16/datatype-geometric.html#DATATYPE-LINE
19+
#[derive(Debug, Clone, PartialEq)]
20+
pub struct PgLine {
21+
pub a: f64,
22+
pub b: f64,
23+
pub c: f64,
24+
}
25+
26+
impl Type<Postgres> for PgLine {
27+
fn type_info() -> PgTypeInfo {
28+
PgTypeInfo::with_name("line")
29+
}
30+
}
31+
32+
impl PgHasArrayType for PgLine {
33+
fn array_type_info() -> PgTypeInfo {
34+
PgTypeInfo::with_name("_line")
35+
}
36+
}
37+
38+
impl<'r> Decode<'r, Postgres> for PgLine {
39+
fn decode(value: PgValueRef<'r>) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
40+
match value.format() {
41+
PgValueFormat::Text => Ok(PgLine::from_str(value.as_str()?)?),
42+
PgValueFormat::Binary => Ok(PgLine::from_bytes(value.as_bytes()?)?),
43+
}
44+
}
45+
}
46+
47+
impl<'q> Encode<'q, Postgres> for PgLine {
48+
fn produces(&self) -> Option<PgTypeInfo> {
49+
Some(PgTypeInfo::with_name("line"))
50+
}
51+
52+
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
53+
self.serialize(buf)?;
54+
Ok(IsNull::No)
55+
}
56+
}
57+
58+
impl FromStr for PgLine {
59+
type Err = BoxDynError;
60+
61+
fn from_str(s: &str) -> Result<Self, Self::Err> {
62+
let mut parts = s
63+
.trim_matches(|c| c == '{' || c == '}' || c == ' ')
64+
.split(',');
65+
66+
let a = parts
67+
.next()
68+
.and_then(|s| s.trim().parse::<f64>().ok())
69+
.ok_or_else(|| format!("{}: could not get a from {}", ERROR, s))?;
70+
71+
let b = parts
72+
.next()
73+
.and_then(|s| s.trim().parse::<f64>().ok())
74+
.ok_or_else(|| format!("{}: could not get b from {}", ERROR, s))?;
75+
76+
let c = parts
77+
.next()
78+
.and_then(|s| s.trim().parse::<f64>().ok())
79+
.ok_or_else(|| format!("{}: could not get c from {}", ERROR, s))?;
80+
81+
if parts.next().is_some() {
82+
return Err(format!("{}: too many numbers inputted in {}", ERROR, s).into());
83+
}
84+
85+
Ok(PgLine { a, b, c })
86+
}
87+
}
88+
89+
impl PgLine {
90+
fn from_bytes(mut bytes: &[u8]) -> Result<PgLine, BoxDynError> {
91+
let a = bytes.get_f64();
92+
let b = bytes.get_f64();
93+
let c = bytes.get_f64();
94+
Ok(PgLine { a, b, c })
95+
}
96+
97+
fn serialize(&self, buff: &mut PgArgumentBuffer) -> Result<(), BoxDynError> {
98+
buff.extend_from_slice(&self.a.to_be_bytes());
99+
buff.extend_from_slice(&self.b.to_be_bytes());
100+
buff.extend_from_slice(&self.c.to_be_bytes());
101+
Ok(())
102+
}
103+
104+
#[cfg(test)]
105+
fn serialize_to_vec(&self) -> Vec<u8> {
106+
let mut buff = PgArgumentBuffer::default();
107+
self.serialize(&mut buff).unwrap();
108+
buff.to_vec()
109+
}
110+
}
111+
112+
#[cfg(test)]
113+
mod line_tests {
114+
115+
use std::str::FromStr;
116+
117+
use super::PgLine;
118+
119+
const LINE_BYTES: &[u8] = &[
120+
63, 241, 153, 153, 153, 153, 153, 154, 64, 1, 153, 153, 153, 153, 153, 154, 64, 10, 102,
121+
102, 102, 102, 102, 102,
122+
];
123+
124+
#[test]
125+
fn can_deserialise_line_type_bytes() {
126+
let line = PgLine::from_bytes(LINE_BYTES).unwrap();
127+
assert_eq!(
128+
line,
129+
PgLine {
130+
a: 1.1,
131+
b: 2.2,
132+
c: 3.3
133+
}
134+
)
135+
}
136+
137+
#[test]
138+
fn can_deserialise_line_type_str() {
139+
let line = PgLine::from_str("{ 1, 2, 3 }").unwrap();
140+
assert_eq!(
141+
line,
142+
PgLine {
143+
a: 1.0,
144+
b: 2.0,
145+
c: 3.0
146+
}
147+
);
148+
}
149+
150+
#[test]
151+
fn cannot_deserialise_line_too_few_numbers() {
152+
let input_str = "{ 1, 2 }";
153+
let line = PgLine::from_str(input_str);
154+
assert!(line.is_err());
155+
if let Err(err) = line {
156+
assert_eq!(
157+
err.to_string(),
158+
format!("error decoding LINE: could not get c from {input_str}")
159+
)
160+
}
161+
}
162+
163+
#[test]
164+
fn cannot_deserialise_line_too_many_numbers() {
165+
let input_str = "{ 1, 2, 3, 4 }";
166+
let line = PgLine::from_str(input_str);
167+
assert!(line.is_err());
168+
if let Err(err) = line {
169+
assert_eq!(
170+
err.to_string(),
171+
format!("error decoding LINE: too many numbers inputted in {input_str}")
172+
)
173+
}
174+
}
175+
176+
#[test]
177+
fn cannot_deserialise_line_invalid_numbers() {
178+
let input_str = "{ 1, 2, three }";
179+
let line = PgLine::from_str(input_str);
180+
assert!(line.is_err());
181+
if let Err(err) = line {
182+
assert_eq!(
183+
err.to_string(),
184+
format!("error decoding LINE: could not get c from {input_str}")
185+
)
186+
}
187+
}
188+
189+
#[test]
190+
fn can_deserialise_line_type_str_float() {
191+
let line = PgLine::from_str("{1.1, 2.2, 3.3}").unwrap();
192+
assert_eq!(
193+
line,
194+
PgLine {
195+
a: 1.1,
196+
b: 2.2,
197+
c: 3.3
198+
}
199+
);
200+
}
201+
202+
#[test]
203+
fn can_serialise_line_type() {
204+
let line = PgLine {
205+
a: 1.1,
206+
b: 2.2,
207+
c: 3.3,
208+
};
209+
assert_eq!(line.serialize_to_vec(), LINE_BYTES,)
210+
}
211+
}
+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
pub mod line;
12
pub mod point;

sqlx-postgres/src/types/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
//! | [`PgCiText`] | CITEXT<sup>1</sup> |
2323
//! | [`PgCube`] | CUBE |
2424
//! | [`PgPoint] | POINT |
25+
//! | [`PgLine] | LINE |
2526
//! | [`PgHstore`] | HSTORE |
2627
//!
2728
//! <sup>1</sup> SQLx generally considers `CITEXT` to be compatible with `String`, `&str`, etc.,
@@ -245,6 +246,7 @@ mod bit_vec;
245246
pub use array::PgHasArrayType;
246247
pub use citext::PgCiText;
247248
pub use cube::PgCube;
249+
pub use geometry::line::PgLine;
248250
pub use geometry::point::PgPoint;
249251
pub use hstore::PgHstore;
250252
pub use interval::PgInterval;

tests/postgres/types.rs

+6
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,12 @@ test_type!(_point<Vec<sqlx::postgres::types::PgPoint>>(Postgres,
503503
"array[point(2.2,-3.4)]" @= vec![sqlx::postgres::types::PgPoint { x: 2.2, y: -3.4 }],
504504
));
505505

506+
#[cfg(any(postgres_12, postgres_13, postgres_14, postgres_15))]
507+
test_type!(line<sqlx::postgres::types::PgLine>(Postgres,
508+
"line('{1.1, -2.2, 3.3}')" == sqlx::postgres::types::PgLine { a: 1.1, b:-2.2, c: 3.3 },
509+
"line('((0.0, 0.0), (1.0,1.0))')" == sqlx::postgres::types::PgLine { a: 1., b: -1., c: 0. },
510+
));
511+
506512
#[cfg(feature = "rust_decimal")]
507513
test_type!(decimal<sqlx::types::Decimal>(Postgres,
508514
"0::numeric" == sqlx::types::Decimal::from_str("0").unwrap(),

0 commit comments

Comments
 (0)