1mod array;
9mod conditional;
10mod datetime;
11pub(crate) mod fts;
12mod id;
13mod json;
14mod math;
15pub(crate) mod shared;
16mod string;
17mod system;
18mod types;
19
20use nodedb_types::Value;
21
22pub fn eval_function(name: &str, args: &[Value]) -> Value {
24 if let Some(v) = string::try_eval(name, args) {
25 return v;
26 }
27 if let Some(v) = math::try_eval(name, args) {
28 return v;
29 }
30 if let Some(v) = conditional::try_eval(name, args) {
31 return v;
32 }
33 if let Some(v) = id::try_eval(name, args) {
34 return v;
35 }
36 if let Some(v) = datetime::try_eval(name, args) {
37 return v;
38 }
39 if let Some(v) = json::try_eval(name, args) {
40 return v;
41 }
42 if let Some(v) = types::try_eval(name, args) {
43 return v;
44 }
45 if let Some(v) = array::try_eval(name, args) {
46 return v;
47 }
48 if let Some(v) = fts::try_eval_fts(name, args) {
49 return v;
50 }
51 if let Some(v) = system::try_eval(name, args) {
52 return v;
53 }
54 crate::geo_functions::eval_geo_function(name, args).unwrap_or(Value::Null)
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61 use crate::expr::SqlExpr;
62
63 fn eval_fn(name: &str, args: Vec<Value>) -> Value {
64 eval_function(name, &args)
65 }
66
67 #[test]
68 fn upper() {
69 assert_eq!(
70 eval_fn("upper", vec![Value::String("hello".into())]),
71 Value::String("HELLO".into())
72 );
73 }
74
75 #[test]
76 fn upper_null_propagation() {
77 assert_eq!(eval_fn("upper", vec![Value::Null]), Value::Null);
78 }
79
80 #[test]
81 fn substring() {
82 assert_eq!(
83 eval_fn(
84 "substr",
85 vec![
86 Value::String("hello".into()),
87 Value::Integer(2),
88 Value::Integer(3)
89 ]
90 ),
91 Value::String("ell".into())
92 );
93 }
94
95 #[test]
96 fn round_with_decimals() {
97 assert_eq!(
98 eval_fn("round", vec![Value::Float(3.15159), Value::Integer(2)]),
99 Value::Float(3.15)
100 );
101 }
102
103 #[test]
104 fn typeof_int() {
105 assert_eq!(
106 eval_fn("typeof", vec![Value::Integer(42)]),
107 Value::String("int".into())
108 );
109 }
110
111 #[test]
112 fn function_via_expr() {
113 let expr = SqlExpr::Function {
114 name: "upper".into(),
115 args: vec![SqlExpr::Column("name".into())],
116 };
117 let doc = Value::Object(
118 [("name".to_string(), Value::String("alice".into()))]
119 .into_iter()
120 .collect(),
121 );
122 assert_eq!(expr.eval(&doc), Value::String("ALICE".into()));
123 }
124
125 #[test]
126 fn geo_geohash_encode() {
127 let result = eval_fn(
128 "geo_geohash",
129 vec![
130 Value::Float(-73.9857),
131 Value::Float(40.758),
132 Value::Integer(6),
133 ],
134 );
135 let hash = result.as_str().unwrap();
136 assert_eq!(hash.len(), 6);
137 assert!(hash.starts_with("dr5ru"), "got {hash}");
138 }
139
140 #[test]
141 fn geo_geohash_decode() {
142 let hash = eval_fn(
143 "geo_geohash",
144 vec![Value::Float(0.0), Value::Float(0.0), Value::Integer(6)],
145 );
146 let result = eval_fn("geo_geohash_decode", vec![hash]);
147 assert!(!result.is_null());
148 assert!(result.get("min_lng").is_some());
149 }
150
151 #[test]
152 fn geo_geohash_neighbors_returns_8() {
153 let hash = eval_fn(
154 "geo_geohash",
155 vec![Value::Float(10.0), Value::Float(50.0), Value::Integer(6)],
156 );
157 let result = eval_fn("geo_geohash_neighbors", vec![hash]);
158 let arr = result.as_array().unwrap();
159 assert_eq!(arr.len(), 8);
160 }
161
162 fn point_value(lng: f64, lat: f64) -> Value {
163 let geom = nodedb_types::geometry::Geometry::point(lng, lat);
164 Value::Geometry(geom)
165 }
166
167 fn square_value() -> Value {
168 let geom = nodedb_types::geometry::Geometry::polygon(vec![vec![
169 [0.0, 0.0],
170 [10.0, 0.0],
171 [10.0, 10.0],
172 [0.0, 10.0],
173 [0.0, 0.0],
174 ]]);
175 Value::Geometry(geom)
176 }
177
178 #[test]
179 fn st_contains_sql() {
180 let result = eval_fn("st_contains", vec![square_value(), point_value(5.0, 5.0)]);
181 assert_eq!(result, Value::Bool(true));
182 }
183
184 #[test]
185 fn st_intersects_sql() {
186 let result = eval_fn("st_intersects", vec![square_value(), point_value(5.0, 0.0)]);
187 assert_eq!(result, Value::Bool(true));
188 }
189
190 #[test]
191 fn st_distance_sql() {
192 let result = eval_fn(
193 "st_distance",
194 vec![point_value(0.0, 0.0), point_value(0.0, 1.0)],
195 );
196 let d = result.as_f64().unwrap();
197 assert!((d - 111_195.0).abs() < 500.0, "got {d}");
198 }
199
200 #[test]
201 fn st_dwithin_sql() {
202 let result = eval_fn(
203 "st_dwithin",
204 vec![
205 point_value(0.0, 0.0),
206 point_value(0.001, 0.0),
207 Value::Float(200.0),
208 ],
209 );
210 assert_eq!(result, Value::Bool(true));
211 }
212
213 #[test]
214 fn st_buffer_sql() {
215 let result = eval_fn(
216 "st_buffer",
217 vec![
218 point_value(0.0, 0.0),
219 Value::Float(1000.0),
220 Value::Integer(8),
221 ],
222 );
223 assert!(result.as_geometry().is_some());
225 }
226
227 #[test]
228 fn st_envelope_sql() {
229 let result = eval_fn("st_envelope", vec![square_value()]);
230 assert!(result.as_geometry().is_some());
231 }
232
233 #[test]
234 fn geo_length_sql() {
235 let line = Value::Geometry(nodedb_types::geometry::Geometry::line_string(vec![
236 [0.0, 0.0],
237 [0.0, 1.0],
238 ]));
239 let result = eval_fn("geo_length", vec![line]);
240 let d = result.as_f64().unwrap();
241 assert!((d - 111_195.0).abs() < 500.0, "got {d}");
242 }
243
244 #[test]
245 fn geo_x_y() {
246 assert_eq!(
247 eval_fn("geo_x", vec![point_value(5.0, 10.0)])
248 .as_f64()
249 .unwrap(),
250 5.0
251 );
252 assert_eq!(
253 eval_fn("geo_y", vec![point_value(5.0, 10.0)])
254 .as_f64()
255 .unwrap(),
256 10.0
257 );
258 }
259
260 #[test]
261 fn geo_type_sql() {
262 assert_eq!(
263 eval_fn("geo_type", vec![point_value(0.0, 0.0)]),
264 Value::String("Point".into())
265 );
266 assert_eq!(
267 eval_fn("geo_type", vec![square_value()]),
268 Value::String("Polygon".into())
269 );
270 }
271
272 #[test]
273 fn geo_num_points_sql() {
274 assert_eq!(
275 eval_fn("geo_num_points", vec![point_value(0.0, 0.0)]),
276 Value::Integer(1)
277 );
278 assert_eq!(
279 eval_fn("geo_num_points", vec![square_value()]),
280 Value::Integer(5)
281 );
282 }
283
284 #[test]
285 fn geo_is_valid_sql() {
286 assert_eq!(
287 eval_fn("geo_is_valid", vec![square_value()]),
288 Value::Bool(true)
289 );
290 }
291
292 #[test]
293 fn geo_as_wkt_sql() {
294 let result = eval_fn("geo_as_wkt", vec![point_value(5.0, 10.0)]);
295 assert_eq!(result, Value::String("POINT(5 10)".into()));
296 }
297
298 #[test]
299 fn geo_from_wkt_sql() {
300 let result = eval_fn("geo_from_wkt", vec![Value::String("POINT(5 10)".into())]);
301 assert!(result.as_geometry().is_some());
302 }
303
304 #[test]
305 fn geo_circle_sql() {
306 let result = eval_fn(
307 "geo_circle",
308 vec![
309 Value::Float(0.0),
310 Value::Float(0.0),
311 Value::Float(1000.0),
312 Value::Integer(16),
313 ],
314 );
315 assert!(result.as_geometry().is_some());
316 }
317
318 #[test]
319 fn geo_bbox_sql() {
320 let result = eval_fn(
321 "geo_bbox",
322 vec![
323 Value::Float(0.0),
324 Value::Float(0.0),
325 Value::Float(10.0),
326 Value::Float(10.0),
327 ],
328 );
329 assert!(result.as_geometry().is_some());
330 }
331
332 #[test]
333 fn version_returns_postgres_compatible_string() {
334 match eval_fn("version", vec![]) {
335 Value::String(s) => assert!(s.contains("PostgreSQL")),
336 other => panic!("expected Value::String, got {other:?}"),
337 }
338 }
339}