nodedb_query/geo_functions/catalog/spec.rs
1// SPDX-License-Identifier: Apache-2.0
2
3//! Description of one geospatial SQL function: every spelling that reaches it,
4//! the shape of its argument list, and the kind of value it returns.
5
6/// The argument list a geo function accepts.
7///
8/// This is the only place arity is declared. `nodedb-sql` maps each shape to
9/// its `ArgTypeSpec` slice exhaustively, so a new shape cannot be introduced
10/// without the planner being taught the types that go with it.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum GeoArgShape {
13 /// `(geom)`
14 Geometry1,
15 /// `(geom1, geom2)`
16 Geometry2,
17 /// `(geom1, geom2, distance_m)`
18 Geometry2Distance,
19 /// `(geom, distance_m [, segments])`
20 GeometryBuffer,
21 /// `(lng, lat)`
22 LngLat,
23 /// `(x, y [, z])`
24 LngLatZ,
25 /// `(lng, lat [, precision])`
26 LngLatPrecision,
27 /// `(lat, lng, resolution)` — note the reversed leading pair relative to
28 /// [`GeoArgShape::LngLatPrecision`]; the H3 surface names latitude first.
29 LatLngResolution,
30 /// `(text)`
31 Text1,
32 /// `(wkt [, srid])`
33 TextSrid,
34 /// `(wkb [, srid])`
35 BytesSrid,
36 /// `(lng1, lat1, lng2, lat2)`
37 Haversine4,
38 /// `(lng, lat, radius_m [, segments])`
39 Circle,
40 /// `(min_lng, min_lat, max_lng, max_lat)`
41 Bbox,
42 /// `(point, point, ...)`
43 PointsVariadic,
44 /// `(ring, ring, ...)`
45 RingsVariadic,
46}
47
48impl GeoArgShape {
49 /// Minimum and maximum accepted argument counts.
50 pub const fn arity(self) -> (usize, usize) {
51 match self {
52 Self::Geometry1 | Self::Text1 => (1, 1),
53 Self::TextSrid | Self::BytesSrid => (1, 2),
54 Self::Geometry2 | Self::LngLat => (2, 2),
55 Self::GeometryBuffer | Self::LngLatZ | Self::LngLatPrecision => (2, 3),
56 Self::Geometry2Distance | Self::LatLngResolution => (3, 3),
57 Self::Circle => (3, 4),
58 Self::Haversine4 | Self::Bbox => (4, 4),
59 Self::PointsVariadic => (2, MAX_VARIADIC),
60 Self::RingsVariadic => (1, MAX_VARIADIC),
61 }
62 }
63}
64
65/// Upper bound on a variadic geo constructor's argument count. Matches the
66/// registry's existing cap for `geo_line` / `geo_polygon`.
67pub const MAX_VARIADIC: usize = 255;
68
69/// The kind of value a geo function produces.
70///
71/// `nodedb-sql` maps this to a `ColumnType` for plan-time typing. `Object`
72/// covers the decoded-bounds shapes (geohash / H3 cell), which have no scalar
73/// column type and are typed as unknown at plan time.
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum GeoReturn {
76 Bool,
77 Float,
78 Int,
79 Text,
80 Geometry,
81 Object,
82 Array,
83}
84
85/// One geospatial SQL function.
86#[derive(Debug, Clone, Copy)]
87pub struct GeoFunctionSpec {
88 /// Dispatch key. The evaluator matches on exactly this string, and every
89 /// other spelling in `aliases` resolves to it before dispatch.
90 pub canonical: &'static str,
91 /// Additional accepted spellings — the internal `geo_*` names that predate
92 /// the standard ones, plus legacy synonyms. Never includes `canonical`.
93 pub aliases: &'static [&'static str],
94 pub args: GeoArgShape,
95 pub returns: GeoReturn,
96}
97
98impl GeoFunctionSpec {
99 /// Every spelling that resolves to this function, canonical first.
100 pub fn names(&self) -> impl Iterator<Item = &'static str> + '_ {
101 std::iter::once(self.canonical).chain(self.aliases.iter().copied())
102 }
103
104 /// Whether `name` (already lowercased) refers to this function.
105 pub fn matches(&self, name: &str) -> bool {
106 self.canonical == name || self.aliases.contains(&name)
107 }
108}
109
110/// Compact constructor for the catalog table.
111pub(super) const fn f(
112 canonical: &'static str,
113 aliases: &'static [&'static str],
114 args: GeoArgShape,
115 returns: GeoReturn,
116) -> GeoFunctionSpec {
117 GeoFunctionSpec {
118 canonical,
119 aliases,
120 args,
121 returns,
122 }
123}