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