sentinel_driver/types/
cube.rs1use bytes::{BufMut, BytesMut};
2
3use crate::error::{Error, Result};
4use crate::types::{FromSql, Oid, ToSql};
5
6#[derive(Debug, Clone, PartialEq)]
18pub struct PgCube {
19 pub coordinates: Vec<f64>,
22 pub is_point: bool,
24}
25
26const CUBE_IS_POINT: u32 = 1;
27
28impl PgCube {
29 pub fn point(coordinates: Vec<f64>) -> Self {
31 PgCube {
32 coordinates,
33 is_point: true,
34 }
35 }
36
37 pub fn cube(coordinates: Vec<f64>, ndim: usize) -> Self {
42 debug_assert_eq!(
43 coordinates.len(),
44 ndim * 2,
45 "box coordinates must be ndim * 2"
46 );
47 PgCube {
48 coordinates,
49 is_point: false,
50 }
51 }
52
53 pub fn ndim(&self) -> usize {
55 if self.is_point {
56 self.coordinates.len()
57 } else if self.coordinates.is_empty() {
58 0
59 } else {
60 self.coordinates.len() / 2
61 }
62 }
63}
64
65impl ToSql for PgCube {
66 fn oid(&self) -> Oid {
67 Oid::TEXT
68 }
69
70 fn to_sql(&self, buf: &mut BytesMut) -> Result<()> {
71 let ndim = self.ndim() as u32;
72 let flags = if self.is_point { CUBE_IS_POINT } else { 0 };
73
74 buf.put_u32(ndim);
75 buf.put_u32(flags);
76
77 for &coord in &self.coordinates {
78 buf.put_f64(coord);
79 }
80
81 Ok(())
82 }
83}
84
85impl FromSql for PgCube {
86 fn oid() -> Oid {
87 Oid::TEXT
88 }
89
90 fn from_sql(buf: &[u8]) -> Result<Self> {
91 if buf.len() < 8 {
92 return Err(Error::Decode(format!(
93 "cube: expected at least 8 bytes, got {}",
94 buf.len()
95 )));
96 }
97
98 let ndim = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]) as usize;
99 let flags = u32::from_be_bytes([buf[4], buf[5], buf[6], buf[7]]);
100 let is_point = (flags & CUBE_IS_POINT) != 0;
101
102 let num_coords = if is_point { ndim } else { ndim * 2 };
103 let expected_len = 8 + num_coords * 8;
104
105 if buf.len() < expected_len {
106 return Err(Error::Decode(format!(
107 "cube: expected {} bytes for {ndim}D {}, got {}",
108 expected_len,
109 if is_point { "point" } else { "box" },
110 buf.len()
111 )));
112 }
113
114 let mut coordinates = Vec::with_capacity(num_coords);
115 let mut offset = 8;
116 for _ in 0..num_coords {
117 let val = f64::from_be_bytes([
118 buf[offset],
119 buf[offset + 1],
120 buf[offset + 2],
121 buf[offset + 3],
122 buf[offset + 4],
123 buf[offset + 5],
124 buf[offset + 6],
125 buf[offset + 7],
126 ]);
127 coordinates.push(val);
128 offset += 8;
129 }
130
131 Ok(PgCube {
132 coordinates,
133 is_point,
134 })
135 }
136}
137
138impl std::fmt::Display for PgCube {
139 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140 if self.is_point {
141 write!(f, "(")?;
142 for (i, coord) in self.coordinates.iter().enumerate() {
143 if i > 0 {
144 write!(f, ", ")?;
145 }
146 write!(f, "{coord}")?;
147 }
148 write!(f, ")")
149 } else {
150 let ndim = self.ndim();
151 let (lower, upper) = self.coordinates.split_at(ndim);
152 write!(f, "(")?;
153 for (i, coord) in lower.iter().enumerate() {
154 if i > 0 {
155 write!(f, ", ")?;
156 }
157 write!(f, "{coord}")?;
158 }
159 write!(f, "),(")?;
160 for (i, coord) in upper.iter().enumerate() {
161 if i > 0 {
162 write!(f, ", ")?;
163 }
164 write!(f, "{coord}")?;
165 }
166 write!(f, ")")
167 }
168 }
169}