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