1use rupg_common::SqlState;
4
5use crate::binary::Recv;
6use crate::error::TypeError;
7use crate::float::{float8_out, strtod};
8use crate::number::is_space;
9use crate::oid;
10
11const EPSILON: f64 = 1.0e-6;
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub enum GeoKind {
17 Point,
18 Lseg,
19 Box,
20 Path,
21 Polygon,
22 Line,
23 Circle,
24}
25
26impl GeoKind {
27 #[must_use]
29 pub fn from_oid(ty: u32) -> Option<GeoKind> {
30 Some(match ty {
31 oid::POINT => GeoKind::Point,
32 oid::LSEG => GeoKind::Lseg,
33 oid::BOX => GeoKind::Box,
34 oid::PATH => GeoKind::Path,
35 oid::POLYGON => GeoKind::Polygon,
36 oid::LINE => GeoKind::Line,
37 oid::CIRCLE => GeoKind::Circle,
38 _ => return None,
39 })
40 }
41
42 fn name(self) -> &'static str {
44 match self {
45 GeoKind::Point => "point",
46 GeoKind::Lseg => "lseg",
47 GeoKind::Box => "box",
48 GeoKind::Path => "path",
49 GeoKind::Polygon => "polygon",
50 GeoKind::Line => "line",
51 GeoKind::Circle => "circle",
52 }
53 }
54}
55
56#[derive(Clone, Copy, Debug)]
58struct Point {
59 x: f64,
60 y: f64,
61}
62
63fn fp_zero(a: f64) -> bool {
65 a.abs() <= EPSILON
66}
67
68fn fp_eq(a: f64, b: f64) -> bool {
70 a == b || (a - b).abs() <= EPSILON
71}
72
73fn float8_lt(a: f64, b: f64) -> bool {
75 !a.is_nan() && (b.is_nan() || a < b)
76}
77
78fn float8_eq(a: f64, b: f64) -> bool {
80 if a.is_nan() || b.is_nan() { a.is_nan() && b.is_nan() } else { a == b }
81}
82
83fn point_eq(a: Point, b: Point) -> bool {
85 if a.x.is_nan() || a.y.is_nan() || b.x.is_nan() || b.y.is_nan() {
86 return float8_eq(a.x, b.x) && float8_eq(a.y, b.y);
87 }
88 fp_eq(a.x, b.x) && fp_eq(a.y, b.y)
89}
90
91fn out_of_range(what: &str) -> TypeError {
93 TypeError::new(SqlState::NUMERIC_VALUE_OUT_OF_RANGE, format!("value out of range: {what}"))
94}
95
96fn float8_mi(a: f64, b: f64) -> Result<f64, TypeError> {
98 let r = a - b;
99 if r.is_infinite() && !a.is_infinite() && !b.is_infinite() {
100 return Err(out_of_range("overflow"));
101 }
102 Ok(r)
103}
104
105fn float8_mul(a: f64, b: f64) -> Result<f64, TypeError> {
107 let r = a * b;
108 if r.is_infinite() && !a.is_infinite() && !b.is_infinite() {
109 return Err(out_of_range("overflow"));
110 }
111 if r == 0.0 && a != 0.0 && b != 0.0 {
112 return Err(out_of_range("underflow"));
113 }
114 Ok(r)
115}
116
117fn float8_div(a: f64, b: f64) -> Result<f64, TypeError> {
119 if b == 0.0 && !a.is_nan() {
120 return Err(TypeError::new(SqlState::DIVISION_BY_ZERO, "division by zero".to_string()));
121 }
122 let r = a / b;
123 if r.is_infinite() && !a.is_infinite() {
124 return Err(out_of_range("overflow"));
125 }
126 if r == 0.0 && a != 0.0 && !b.is_infinite() {
127 return Err(out_of_range("underflow"));
128 }
129 Ok(r)
130}
131
132fn point_sl(a: Point, b: Point) -> Result<f64, TypeError> {
134 if fp_eq(a.x, b.x) {
135 return Ok(f64::INFINITY);
136 }
137 if fp_eq(a.y, b.y) {
138 return Ok(0.0);
139 }
140 float8_div(float8_mi(a.y, b.y)?, float8_mi(a.x, b.x)?)
141}
142
143fn line_construct(pt: Point, m: f64) -> Result<[f64; 3], TypeError> {
145 if m.is_infinite() {
146 return Ok([-1.0, 0.0, pt.x]);
147 }
148 if m == 0.0 {
149 return Ok([0.0, -1.0, pt.y]);
150 }
151 let c = float8_mi(pt.y, float8_mul(m, pt.x)?)?;
152 Ok([m, -1.0, if c == 0.0 { 0.0 } else { c }])
154}
155
156struct Reader<'a> {
158 text: &'a str,
159 kind: GeoKind,
160}
161
162impl Reader<'_> {
163 fn at(&self, i: usize) -> u8 {
165 self.text.as_bytes().get(i).copied().unwrap_or(0)
166 }
167
168 fn skip_space(&self, mut i: usize) -> usize {
169 while is_space(self.at(i)) {
170 i += 1;
171 }
172 i
173 }
174
175 fn syntax(&self) -> TypeError {
177 TypeError::syntax(self.kind.name(), self.text)
178 }
179
180 fn end(&self, i: usize) -> Result<(), TypeError> {
182 if self.at(i) == 0 { Ok(()) } else { Err(self.syntax()) }
183 }
184
185 fn expect(&self, i: usize, c: u8) -> Result<usize, TypeError> {
187 if self.at(i) == c { Ok(i + 1) } else { Err(self.syntax()) }
188 }
189
190 fn single(&self, i: usize) -> Result<(f64, usize), TypeError> {
192 let i = self.skip_space(i);
193 if self.at(i) == 0 {
194 return Err(self.syntax());
195 }
196 let Some((value, len, range)) = strtod::<f64>(&self.text.as_bytes()[i..]) else {
197 return Err(self.syntax());
198 };
199 if range {
200 return Err(TypeError::new(
201 SqlState::NUMERIC_VALUE_OUT_OF_RANGE,
202 format!("\"{}\" is out of range for type double precision", &self.text[i..i + len]),
203 ));
204 }
205 Ok((value, self.skip_space(i + len)))
206 }
207
208 fn pair(&self, i: usize) -> Result<(Point, usize), TypeError> {
210 let mut i = self.skip_space(i);
211 let delim = self.at(i) == b'(';
212 i += usize::from(delim);
213 let (x, i) = self.single(i)?;
214 let i = self.expect(i, b',')?;
215 let (y, mut i) = self.single(i)?;
216 if delim {
217 i = self.skip_space(self.expect(i, b')')?);
218 }
219 Ok((Point { x, y }, i))
220 }
221
222 fn path(
224 &self,
225 i: usize,
226 open: bool,
227 npts: usize,
228 ) -> Result<(Vec<Point>, bool, usize), TypeError> {
229 let mut i = self.skip_space(i);
230 let mut depth = 0;
231 let is_open = self.at(i) == b'[';
232 if is_open {
233 if !open {
234 return Err(self.syntax());
235 }
236 depth += 1;
237 i += 1;
238 } else if self.at(i) == b'(' {
239 let cp = self.skip_space(i + 1);
240 if self.at(cp) == b'(' || !self.text.as_bytes()[i + 1..].contains(&b'(') {
241 depth += 1;
242 i = cp;
243 }
244 }
245 let mut points = Vec::with_capacity(npts.min(self.text.len()));
246 for _ in 0..npts {
247 let (p, next) = self.pair(i)?;
248 points.push(p);
249 i = next + usize::from(self.at(next) == b',');
250 }
251 while depth > 0 {
252 if self.at(i) == b')' || self.at(i) == b']' && is_open && depth == 1 {
253 depth -= 1;
254 i = self.skip_space(i + 1);
255 } else {
256 return Err(self.syntax());
257 }
258 }
259 Ok((points, is_open, i))
260 }
261
262 fn pair_count(&self) -> Result<usize, TypeError> {
264 let commas = self.text.bytes().filter(|&c| c == b',').count();
265 if commas % 2 == 1 { Ok(commas.div_ceil(2)) } else { Err(self.syntax()) }
266 }
267}
268
269fn put_points(points: &[Point], out: &mut Vec<u8>) {
271 for p in points {
272 out.extend_from_slice(&p.x.to_be_bytes());
273 out.extend_from_slice(&p.y.to_be_bytes());
274 }
275}
276
277fn put_count(n: usize, out: &mut Vec<u8>) {
279 out.extend_from_slice(&i32::try_from(n).unwrap_or(i32::MAX).to_be_bytes());
280}
281
282fn box_corners(mut high: Point, mut low: Point) -> [Point; 2] {
284 if float8_lt(high.x, low.x) {
285 std::mem::swap(&mut high.x, &mut low.x);
286 }
287 if float8_lt(high.y, low.y) {
288 std::mem::swap(&mut high.y, &mut low.y);
289 }
290 [high, low]
291}
292
293fn check_line(a: f64, b: f64, binary: bool) -> Result<(), TypeError> {
295 if fp_zero(a) && fp_zero(b) {
296 let state = if binary {
297 SqlState::INVALID_BINARY_REPRESENTATION
298 } else {
299 SqlState::INVALID_TEXT_REPRESENTATION
300 };
301 return Err(TypeError::new(
302 state,
303 "invalid line specification: A and B cannot both be zero".to_string(),
304 ));
305 }
306 Ok(())
307}
308
309pub fn geo_in(kind: GeoKind, text: &str) -> Result<Vec<u8>, TypeError> {
315 let r = Reader { text, kind };
316 let mut out = Vec::new();
317 match kind {
318 GeoKind::Point => {
319 let (p, i) = r.pair(0)?;
320 r.end(i)?;
321 put_points(&[p], &mut out);
322 }
323 GeoKind::Lseg => {
324 let (points, _, i) = r.path(0, true, 2)?;
325 r.end(i)?;
326 put_points(&points, &mut out);
327 }
328 GeoKind::Box => {
329 let (points, _, i) = r.path(0, false, 2)?;
330 r.end(i)?;
331 put_points(&box_corners(points[0], points[1]), &mut out);
332 }
333 GeoKind::Path => {
334 let npts = r.pair_count()?;
335 let mut i = r.skip_space(0);
336 let single = r.at(i) == b'(' && !text.as_bytes()[i + 1..].contains(&b'(');
338 i += usize::from(single);
339 let (points, open, mut i) = r.path(i, true, npts)?;
340 if single {
341 i = r.skip_space(r.expect(i, b')')?);
342 }
343 r.end(i)?;
344 out.push(u8::from(!open));
345 put_count(points.len(), &mut out);
346 put_points(&points, &mut out);
347 }
348 GeoKind::Polygon => {
349 let npts = r.pair_count()?;
350 let (points, _, i) = r.path(0, false, npts)?;
351 r.end(i)?;
352 put_count(points.len(), &mut out);
353 put_points(&points, &mut out);
354 }
355 GeoKind::Line => {
356 let i = r.skip_space(0);
357 let coefficients = if r.at(i) == b'{' {
358 let (a, i) = r.single(i + 1)?;
359 let (b, i) = r.single(r.expect(i, b',')?)?;
360 let (c, i) = r.single(r.expect(i, b',')?)?;
361 r.end(r.skip_space(r.expect(i, b'}')?))?;
362 check_line(a, b, false)?;
363 [a, b, c]
364 } else {
365 let (points, _, i) = r.path(i, true, 2)?;
366 r.end(i)?;
367 if point_eq(points[0], points[1]) {
368 return Err(TypeError::new(
369 SqlState::INVALID_TEXT_REPRESENTATION,
370 "invalid line specification: must be two distinct points".to_string(),
371 ));
372 }
373 line_construct(points[0], point_sl(points[0], points[1])?)?
374 };
375 for v in coefficients {
376 out.extend_from_slice(&v.to_be_bytes());
377 }
378 }
379 GeoKind::Circle => {
380 let mut i = r.skip_space(0);
381 let mut depth = 0;
382 if r.at(i) == b'<' {
383 depth += 1;
384 i += 1;
385 } else if r.at(i) == b'(' {
386 let cp = r.skip_space(i + 1);
388 if r.at(cp) == b'(' {
389 depth += 1;
390 i = cp;
391 }
392 }
393 let (center, i) = r.pair(i)?;
394 let i = i + usize::from(r.at(i) == b',');
395 let (radius, mut i) = r.single(i)?;
396 if radius < 0.0 {
397 return Err(r.syntax());
398 }
399 while depth > 0 {
400 if r.at(i) == b')' || r.at(i) == b'>' && depth == 1 {
401 depth -= 1;
402 i = r.skip_space(i + 1);
403 } else {
404 return Err(r.syntax());
405 }
406 }
407 r.end(i)?;
408 put_points(&[center], &mut out);
409 out.extend_from_slice(&radius.to_be_bytes());
410 }
411 }
412 Ok(out)
413}
414
415fn numbers(bytes: &[u8], from: usize) -> impl Iterator<Item = f64> + '_ {
417 bytes.get(from..).unwrap_or_default().as_chunks::<8>().0.iter().map(|c| f64::from_be_bytes(*c))
418}
419
420fn put_pair(x: f64, y: f64, digits: i32, out: &mut Vec<u8>) {
422 out.push(b'(');
423 float8_out(x, digits, out);
424 out.push(b',');
425 float8_out(y, digits, out);
426 out.push(b')');
427}
428
429fn put_path(bytes: &[u8], from: usize, digits: i32, out: &mut Vec<u8>) {
431 let values: Vec<f64> = numbers(bytes, from).collect();
432 for (n, p) in values.as_chunks::<2>().0.iter().enumerate() {
433 if n > 0 {
434 out.push(b',');
435 }
436 put_pair(p[0], p[1], digits, out);
437 }
438}
439
440pub fn geo_out(kind: GeoKind, bytes: &[u8], digits: i32, out: &mut Vec<u8>) {
442 match kind {
443 GeoKind::Point | GeoKind::Box => put_path(bytes, 0, digits, out),
444 GeoKind::Lseg => {
445 out.push(b'[');
446 put_path(bytes, 0, digits, out);
447 out.push(b']');
448 }
449 GeoKind::Path => {
450 let closed = bytes.first().is_some_and(|&c| c != 0);
451 out.push(if closed { b'(' } else { b'[' });
452 put_path(bytes, 5, digits, out);
453 out.push(if closed { b')' } else { b']' });
454 }
455 GeoKind::Polygon => {
456 out.push(b'(');
457 put_path(bytes, 4, digits, out);
458 out.push(b')');
459 }
460 GeoKind::Line => {
461 out.push(b'{');
462 for (n, v) in numbers(bytes, 0).enumerate() {
463 if n > 0 {
464 out.push(b',');
465 }
466 float8_out(v, digits, out);
467 }
468 out.push(b'}');
469 }
470 GeoKind::Circle => {
471 let v: Vec<f64> = numbers(bytes, 0).collect();
472 if let [x, y, r] = v[..] {
473 out.push(b'<');
474 put_pair(x, y, digits, out);
475 out.push(b',');
476 float8_out(r, digits, out);
477 out.push(b'>');
478 }
479 }
480 }
481}
482
483fn recv_points(recv: &mut Recv<'_>, n: usize) -> Result<Vec<Point>, TypeError> {
485 let mut points = Vec::with_capacity(n.min(recv.remaining() / 16));
486 for _ in 0..n {
487 let x = recv.f64()?;
488 points.push(Point { x, y: recv.f64()? });
489 }
490 Ok(points)
491}
492
493fn recv_count(recv: &mut Recv<'_>, header: usize, name: &str) -> Result<usize, TypeError> {
495 let n = recv.i32()?;
496 let limit = (0x7fff_ffff - header) / 16;
497 usize::try_from(n).ok().filter(|&n| n > 0 && n < limit).ok_or_else(|| {
498 TypeError::new(
499 SqlState::INVALID_BINARY_REPRESENTATION,
500 format!("invalid number of points in external \"{name}\" value"),
501 )
502 })
503}
504
505pub fn geo_recv(kind: GeoKind, recv: &mut Recv<'_>) -> Result<Vec<u8>, TypeError> {
511 let mut out = Vec::new();
512 match kind {
513 GeoKind::Point => put_points(&recv_points(recv, 1)?, &mut out),
514 GeoKind::Lseg => put_points(&recv_points(recv, 2)?, &mut out),
515 GeoKind::Box => {
516 let p = recv_points(recv, 2)?;
517 put_points(&box_corners(p[0], p[1]), &mut out);
518 }
519 GeoKind::Path => {
520 let closed = recv.byte()? != 0;
521 let n = recv_count(recv, 16, "path")?;
522 let points = recv_points(recv, n)?;
523 out.push(u8::from(closed));
524 put_count(n, &mut out);
525 put_points(&points, &mut out);
526 }
527 GeoKind::Polygon => {
528 let n = recv_count(recv, 40, "polygon")?;
529 let points = recv_points(recv, n)?;
530 put_count(n, &mut out);
531 put_points(&points, &mut out);
532 }
533 GeoKind::Line => {
534 let (a, b, c) = (recv.f64()?, recv.f64()?, recv.f64()?);
535 check_line(a, b, true)?;
536 for v in [a, b, c] {
537 out.extend_from_slice(&v.to_be_bytes());
538 }
539 }
540 GeoKind::Circle => {
541 let center = recv_points(recv, 1)?;
542 let radius = recv.f64()?;
543 if radius < 0.0 {
544 return Err(TypeError::new(
545 SqlState::INVALID_BINARY_REPRESENTATION,
546 "invalid radius in external \"circle\" value".to_string(),
547 ));
548 }
549 put_points(¢er, &mut out);
550 out.extend_from_slice(&radius.to_be_bytes());
551 }
552 }
553 Ok(out)
554}
555
556#[cfg(test)]
557mod tests {
558 use super::*;
559
560 fn text(kind: GeoKind, s: &str) -> String {
561 let bytes = geo_in(kind, s).unwrap();
562 let mut out = Vec::new();
563 geo_out(kind, &bytes, 1, &mut out);
564 String::from_utf8(out).unwrap()
565 }
566
567 fn error(kind: GeoKind, s: &str) -> String {
568 geo_in(kind, s).unwrap_err().message
569 }
570
571 #[test]
573 fn text_forms() {
574 assert_eq!(text(GeoKind::Point, " ( 1.5 , -2 ) "), "(1.5,-2)");
575 assert_eq!(text(GeoKind::Point, "(NaN,Infinity)"), "(NaN,Infinity)");
576 assert_eq!(error(GeoKind::Point, "(1,2"), "invalid input syntax for type point: \"(1,2\"");
577 assert_eq!(
578 error(GeoKind::Point, "(1e400,2)"),
579 "\"1e400\" is out of range for type double precision"
580 );
581 assert_eq!(text(GeoKind::Lseg, "(1,2,3,4)"), "[(1,2),(3,4)]");
582 assert_eq!(text(GeoKind::Box, "(NaN,1),(2,3)"), "(NaN,3),(2,1)");
583 assert!(geo_in(GeoKind::Box, "[(1,2),(3,4)]").is_err());
584 assert_eq!(text(GeoKind::Line, "(1,2),(3,7)"), "{2.5,-1,-0.5}");
585 assert_eq!(text(GeoKind::Line, "(0,0),(0,5)"), "{-1,0,0}");
586 assert_eq!(
587 error(GeoKind::Line, "{1e-7,0,1}"),
588 "invalid line specification: A and B cannot both be zero"
589 );
590 assert_eq!(text(GeoKind::Path, "(1,2,3,4)"), "((1,2),(3,4))");
591 assert_eq!(text(GeoKind::Path, "[(1,2)]"), "[(1,2)]");
592 assert!(geo_in(GeoKind::Path, "(1,2),(3,4)]").is_err());
593 assert_eq!(text(GeoKind::Polygon, "1,2,3,4,5,6"), "((1,2),(3,4),(5,6))");
594 assert_eq!(text(GeoKind::Circle, "((1,2),3>"), "<(1,2),3>");
595 assert!(geo_in(GeoKind::Circle, "(1,2,3)").is_err());
596 assert!(geo_in(GeoKind::Circle, "<(1,2),-1>").is_err());
597 }
598
599 #[test]
600 fn binary_forms() {
601 let bytes = geo_in(GeoKind::Path, "[(1,2),(3,4)]").unwrap();
602 assert_eq!(bytes.len(), 37);
603 assert_eq!(geo_recv(GeoKind::Path, &mut Recv::new(&bytes)).unwrap(), bytes);
604 let mut bad = bytes.clone();
605 bad[1..5].copy_from_slice(&0i32.to_be_bytes());
606 assert!(geo_recv(GeoKind::Path, &mut Recv::new(&bad)).is_err());
607 let line = [0.0f64, 0.0, 1.0].iter().flat_map(|v| v.to_be_bytes()).collect::<Vec<u8>>();
608 let e = geo_recv(GeoKind::Line, &mut Recv::new(&line)).unwrap_err();
609 assert_eq!(e.sqlstate.as_str(), "22P03");
610 }
611}