Skip to content

Commit 4248c94

Browse files
committed
fix: clean branch
1 parent 9c94ce8 commit 4248c94

File tree

3 files changed

+261
-0
lines changed

3 files changed

+261
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pub mod r#box;
2+
pub mod line;
3+
pub mod line_segment;
4+
pub mod path;
5+
pub mod point;
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
use crate::decode::Decode;
2+
use crate::encode::{Encode, IsNull};
3+
use crate::error::BoxDynError;
4+
use crate::types::{PgPoint, Type};
5+
use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres};
6+
use sqlx_core::Error;
7+
use std::str::FromStr;
8+
9+
const BYTE_WIDTH: usize = 8;
10+
11+
/// Postgres Geometric Path type
12+
///
13+
/// Storage size: 16+16n bytes
14+
/// Description: Open path or Closed path (similar to polygon)
15+
/// Representation: ((x1,y1),(x2,y2))
16+
///
17+
/// See https://www.postgresql.org/docs/16/datatype-geometric.html#DATATYPE-GEOMETRIC-PATH
18+
#[derive(Debug, Clone, PartialEq)]
19+
pub struct PgPath {
20+
pub points: Vec<PgPoint>,
21+
}
22+
23+
impl Type<Postgres> for PgPath {
24+
fn type_info() -> PgTypeInfo {
25+
PgTypeInfo::with_name("path")
26+
}
27+
}
28+
29+
impl PgHasArrayType for PgPath {
30+
fn array_type_info() -> PgTypeInfo {
31+
PgTypeInfo::with_name("_path")
32+
}
33+
}
34+
35+
impl<'r> Decode<'r, Postgres> for PgPath {
36+
fn decode(value: PgValueRef<'r>) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
37+
match value.format() {
38+
PgValueFormat::Text => Ok(PgPath::from_str(value.as_str()?)?),
39+
PgValueFormat::Binary => Ok(pg_path_from_bytes(value.as_bytes()?)?),
40+
}
41+
}
42+
}
43+
44+
impl<'q> Encode<'q, Postgres> for PgPath {
45+
fn produces(&self) -> Option<PgTypeInfo> {
46+
Some(PgTypeInfo::with_name("path"))
47+
}
48+
49+
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
50+
self.serialize(buf)?;
51+
Ok(IsNull::No)
52+
}
53+
}
54+
55+
impl FromStr for PgPath {
56+
type Err = Error;
57+
58+
fn from_str(s: &str) -> Result<Self, Self::Err> {
59+
let sanitised = s.replace(&['(', ')', '[', ']', ' '][..], "");
60+
let mut parts = sanitised.splitn(4, ",");
61+
62+
let mut points = vec![];
63+
64+
while let (Some(x_str), Some(y_str)) = (parts.next(), parts.next()) {
65+
let x = parse_float_from_str(x_str, "could not get x")?;
66+
let y = parse_float_from_str(y_str, "could not get y")?;
67+
68+
let point = PgPoint { x, y };
69+
points.push(point);
70+
}
71+
72+
if !points.is_empty() {
73+
return Ok(PgPath { points });
74+
}
75+
76+
Err(Error::Decode(
77+
format!("could not get path from {}", s).into(),
78+
))
79+
}
80+
}
81+
82+
fn pg_path_from_bytes(bytes: &[u8]) -> Result<PgPath, Error> {
83+
let mut points = vec![];
84+
85+
let steps = bytes.len() / BYTE_WIDTH;
86+
87+
for n in (0..steps).step_by(2) {
88+
let x = get_f64_from_bytes(bytes, BYTE_WIDTH * n)?;
89+
let y = get_f64_from_bytes(bytes, BYTE_WIDTH * (n + 1))?;
90+
points.push(PgPoint { x, y })
91+
}
92+
93+
Ok(PgPath { points })
94+
}
95+
96+
impl PgPath {
97+
fn serialize(&self, buff: &mut PgArgumentBuffer) -> Result<(), Error> {
98+
for point in &self.points {
99+
buff.extend_from_slice(&point.x.to_be_bytes());
100+
buff.extend_from_slice(&point.y.to_be_bytes());
101+
}
102+
Ok(())
103+
}
104+
105+
#[cfg(test)]
106+
fn serialize_to_vec(&self) -> Vec<u8> {
107+
let mut buff = PgArgumentBuffer::default();
108+
self.serialize(&mut buff).unwrap();
109+
buff.to_vec()
110+
}
111+
}
112+
113+
fn get_f64_from_bytes(bytes: &[u8], start: usize) -> Result<f64, Error> {
114+
bytes
115+
.get(start..start + BYTE_WIDTH)
116+
.ok_or(Error::Decode(
117+
format!("Could not decode path bytes: {:?}", bytes).into(),
118+
))?
119+
.try_into()
120+
.map(f64::from_be_bytes)
121+
.map_err(|err| Error::Decode(format!("Invalid bytes slice: {:?}", err).into()))
122+
}
123+
124+
fn parse_float_from_str(s: &str, error_msg: &str) -> Result<f64, Error> {
125+
s.parse().map_err(|_| Error::Decode(error_msg.into()))
126+
}
127+
128+
#[cfg(test)]
129+
mod path_tests {
130+
131+
use std::str::FromStr;
132+
133+
use crate::types::PgPoint;
134+
135+
use super::{pg_path_from_bytes, PgPath};
136+
137+
const LINE_SEGMENT_BYTES: &[u8] = &[
138+
63, 241, 153, 153, 153, 153, 153, 154, 64, 1, 153, 153, 153, 153, 153, 154, 64, 10, 102,
139+
102, 102, 102, 102, 102, 64, 17, 153, 153, 153, 153, 153, 154,
140+
];
141+
142+
#[test]
143+
fn can_deserialise_path_type_byes() {
144+
let path = pg_path_from_bytes(LINE_SEGMENT_BYTES).unwrap();
145+
assert_eq!(
146+
path,
147+
PgPath {
148+
points: vec![PgPoint { x: 1.1, y: 2.2 }, PgPoint { x: 3.3, y: 4.4 }]
149+
}
150+
)
151+
}
152+
153+
#[test]
154+
fn can_deserialise_path_type_str_first_syntax() {
155+
let path = PgPath::from_str("[( 1, 2), (3, 4 )]").unwrap();
156+
assert_eq!(
157+
path,
158+
PgPath {
159+
points: vec![PgPoint { x: 1., y: 2. }, PgPoint { x: 3., y: 4. }]
160+
}
161+
);
162+
}
163+
#[test]
164+
fn can_deserialise_path_type_str_second_syntax() {
165+
let path = PgPath::from_str("(( 1, 2), (3, 4 ))").unwrap();
166+
assert_eq!(
167+
path,
168+
PgPath {
169+
points: vec![PgPoint { x: 1., y: 2. }, PgPoint { x: 3., y: 4. }]
170+
}
171+
);
172+
}
173+
174+
#[test]
175+
fn can_deserialise_path_type_str_third_syntax() {
176+
let path = PgPath::from_str("(1, 2), (3, 4 )").unwrap();
177+
assert_eq!(
178+
path,
179+
PgPath {
180+
points: vec![PgPoint { x: 1., y: 2. }, PgPoint { x: 3., y: 4. }]
181+
}
182+
);
183+
}
184+
185+
#[test]
186+
fn can_deserialise_path_type_str_fourth_syntax() {
187+
let path = PgPath::from_str("1, 2, 3, 4").unwrap();
188+
assert_eq!(
189+
path,
190+
PgPath {
191+
points: vec![PgPoint { x: 1., y: 2. }, PgPoint { x: 3., y: 4. }]
192+
}
193+
);
194+
}
195+
196+
#[test]
197+
fn can_deserialise_path_type_str_float() {
198+
let path = PgPath::from_str("(1.1, 2.2), (3.3, 4.4)").unwrap();
199+
assert_eq!(
200+
path,
201+
PgPath {
202+
points: vec![PgPoint { x: 1.1, y: 2.2 }, PgPoint { x: 3.3, y: 4.4 }]
203+
}
204+
);
205+
}
206+
207+
#[test]
208+
fn can_serialise_path_type() {
209+
let path = PgPath {
210+
points: vec![PgPoint { x: 1.1, y: 2.2 }, PgPoint { x: 3.3, y: 4.4 }],
211+
};
212+
assert_eq!(path.serialize_to_vec(), LINE_SEGMENT_BYTES,)
213+
}
214+
}

tests/postgres/types.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,48 @@ test_type!(_cube<Vec<sqlx::postgres::types::PgCube>>(Postgres,
492492
"array[cube(2.2,-3.4)]" == vec![sqlx::postgres::types::PgCube::OneDimensionInterval(2.2, -3.4)],
493493
));
494494

495+
#[cfg(any(postgres_12, postgres_13, postgres_14, postgres_15))]
496+
test_type!(point<sqlx::postgres::types::PgPoint>(Postgres,
497+
"point(2.2,-3.4)" @= sqlx::postgres::types::PgPoint { x: 2.2, y:-3.4 },
498+
));
499+
500+
#[cfg(any(postgres_12, postgres_13, postgres_14, postgres_15))]
501+
test_type!(_point<Vec<sqlx::postgres::types::PgPoint>>(Postgres,
502+
"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 }],
503+
"array[point(2.2,-3.4)]" @= vec![sqlx::postgres::types::PgPoint { x: 2.2, y: -3.4 }],
504+
));
505+
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+
512+
#[cfg(any(postgres_12, postgres_13, postgres_14, postgres_15))]
513+
test_type!(_line<Vec<sqlx::postgres::types::PgLine>>(Postgres,
514+
"array[line('{1,2,3}'),line('{1.1, 2.2, 3.3}')]" @= vec![sqlx::postgres::types::PgLine { a:1., b: 2., c: 3. }, sqlx::postgres::types::PgLine { a:1.1, b: 2.2, c: 3.3 }],
515+
));
516+
517+
#[cfg(any(postgres_12, postgres_13, postgres_14, postgres_15))]
518+
test_type!(lseg<sqlx::postgres::types::PgLSeg>(Postgres,
519+
"lseg('((1.0, 2.0), (3.0,4.0))')" @= sqlx::postgres::types::PgLSeg { x1: 1., y1: 2., x2: 3. , y2: 4.},
520+
));
521+
522+
#[cfg(any(postgres_12, postgres_13, postgres_14, postgres_15))]
523+
test_type!(_lseg<Vec<sqlx::postgres::types::PgLSeg>>(Postgres,
524+
"array[lseg('(1,2,3,4)'),lseg('[(1.1, 2.2), (3.3, 4.4)]')]" @= vec![sqlx::postgres::types::PgLSeg { x1: 1., y1: 2., x2: 3., y2: 4 }, sqlx::postgres::types::PgLSeg { x1: 1.1, y1: 2.2, x2: 3.3, y2: 4.4 }],
525+
));
526+
527+
#[cfg(any(postgres_12, postgres_13, postgres_14, postgres_15))]
528+
test_type!(box<sqlx::postgres::types::PgBox>(Postgres,
529+
"box('((1.0, 2.0), (3.0,4.0))')" @= sqlx::postgres::types::PgBox { x1: 1., y1: 2., x2: 3. , y2: 4.},
530+
));
531+
532+
#[cfg(any(postgres_12, postgres_13, postgres_14, postgres_15))]
533+
test_type!(_box<Vec<sqlx::postgres::types::PgBox>>(Postgres,
534+
"array[box('(1,2,3,4)'),box('[(1.1, 2.2), (3.3, 4.4)]')]" @= vec![sqlx::postgres::types::PgBox { x1: 1., y1: 2., x2: 3., y2: 4 }, sqlx::postgres::types::Pgbox { x1: 1.1, y1: 2.2, x2: 3.3, y2: 4.4 }],
535+
));
536+
495537
#[cfg(feature = "rust_decimal")]
496538
test_type!(decimal<sqlx::types::Decimal>(Postgres,
497539
"0::numeric" == sqlx::types::Decimal::from_str("0").unwrap(),

0 commit comments

Comments
 (0)