Skip to main content

nodedb_query/
functions.rs

1//! Scalar function evaluation for SqlExpr.
2//!
3//! All functions return `serde_json::Value::Null` on invalid/missing
4//! arguments (SQL NULL propagation semantics).
5
6use crate::json_ops::{compare_json, json_to_display_string, json_to_f64, to_json_number};
7
8/// Evaluate a scalar function call.
9pub fn eval_function(name: &str, args: &[serde_json::Value]) -> serde_json::Value {
10    match name {
11        // ── String functions ──
12        "upper" => str_arg(args, 0).map_or(serde_json::Value::Null, |s| {
13            serde_json::Value::String(s.to_uppercase())
14        }),
15        "lower" => str_arg(args, 0).map_or(serde_json::Value::Null, |s| {
16            serde_json::Value::String(s.to_lowercase())
17        }),
18        "trim" => str_arg(args, 0).map_or(serde_json::Value::Null, |s| {
19            serde_json::Value::String(s.trim().to_string())
20        }),
21        "ltrim" => str_arg(args, 0).map_or(serde_json::Value::Null, |s| {
22            serde_json::Value::String(s.trim_start().to_string())
23        }),
24        "rtrim" => str_arg(args, 0).map_or(serde_json::Value::Null, |s| {
25            serde_json::Value::String(s.trim_end().to_string())
26        }),
27        "length" | "char_length" | "character_length" => str_arg(args, 0)
28            .map_or(serde_json::Value::Null, |s| {
29                serde_json::Value::Number(serde_json::Number::from(s.len() as i64))
30            }),
31        "substr" | "substring" => {
32            let Some(s) = str_arg(args, 0) else {
33                return serde_json::Value::Null;
34            };
35            let start = num_arg(args, 1).unwrap_or(1.0) as usize;
36            let len = num_arg(args, 2).map(|n| n as usize);
37            let start_idx = start.saturating_sub(1); // SQL is 1-based.
38            let result: String = match len {
39                Some(l) => s.chars().skip(start_idx).take(l).collect(),
40                None => s.chars().skip(start_idx).collect(),
41            };
42            serde_json::Value::String(result)
43        }
44        "concat" => {
45            let parts: Vec<String> = args.iter().map(json_to_display_string).collect();
46            serde_json::Value::String(parts.join(""))
47        }
48        "replace" => {
49            let Some(s) = str_arg(args, 0) else {
50                return serde_json::Value::Null;
51            };
52            let from = str_arg(args, 1).unwrap_or_default();
53            let to = str_arg(args, 2).unwrap_or_default();
54            serde_json::Value::String(s.replace(&from, &to))
55        }
56        "reverse" => str_arg(args, 0).map_or(serde_json::Value::Null, |s| {
57            serde_json::Value::String(s.chars().rev().collect())
58        }),
59
60        // ── Math functions ──
61        "abs" => num_arg(args, 0).map_or(serde_json::Value::Null, |n| to_json_number(n.abs())),
62        "round" => {
63            let Some(n) = num_arg(args, 0) else {
64                return serde_json::Value::Null;
65            };
66            let decimals = num_arg(args, 1).unwrap_or(0.0) as i32;
67            let factor = 10.0_f64.powi(decimals);
68            to_json_number((n * factor).round() / factor)
69        }
70        "ceil" | "ceiling" => {
71            num_arg(args, 0).map_or(serde_json::Value::Null, |n| to_json_number(n.ceil()))
72        }
73        "floor" => num_arg(args, 0).map_or(serde_json::Value::Null, |n| to_json_number(n.floor())),
74        "power" | "pow" => {
75            let Some(base) = num_arg(args, 0) else {
76                return serde_json::Value::Null;
77            };
78            let exp = num_arg(args, 1).unwrap_or(1.0);
79            to_json_number(base.powf(exp))
80        }
81        "sqrt" => num_arg(args, 0).map_or(serde_json::Value::Null, |n| to_json_number(n.sqrt())),
82        "mod" => {
83            let Some(a) = num_arg(args, 0) else {
84                return serde_json::Value::Null;
85            };
86            let b = num_arg(args, 1).unwrap_or(1.0);
87            if b == 0.0 {
88                serde_json::Value::Null
89            } else {
90                to_json_number(a % b)
91            }
92        }
93        "sign" => num_arg(args, 0).map_or(serde_json::Value::Null, |n| to_json_number(n.signum())),
94        "log" | "ln" => {
95            num_arg(args, 0).map_or(serde_json::Value::Null, |n| to_json_number(n.ln()))
96        }
97        "log10" => num_arg(args, 0).map_or(serde_json::Value::Null, |n| to_json_number(n.log10())),
98        "log2" => num_arg(args, 0).map_or(serde_json::Value::Null, |n| to_json_number(n.log2())),
99        "exp" => num_arg(args, 0).map_or(serde_json::Value::Null, |n| to_json_number(n.exp())),
100
101        // ── Conditional ──
102        "coalesce" => {
103            for arg in args {
104                if !arg.is_null() {
105                    return arg.clone();
106                }
107            }
108            serde_json::Value::Null
109        }
110        "nullif" => {
111            if args.len() >= 2 && args[0] == args[1] {
112                serde_json::Value::Null
113            } else {
114                args.first().cloned().unwrap_or(serde_json::Value::Null)
115            }
116        }
117        "greatest" => args
118            .iter()
119            .filter(|v| !v.is_null())
120            .max_by(|a, b| compare_json(a, b))
121            .cloned()
122            .unwrap_or(serde_json::Value::Null),
123        "least" => args
124            .iter()
125            .filter(|v| !v.is_null())
126            .min_by(|a, b| compare_json(a, b))
127            .cloned()
128            .unwrap_or(serde_json::Value::Null),
129
130        // ── ID generation ──
131        "uuid" | "uuid_v4" | "gen_random_uuid" => {
132            serde_json::Value::String(nodedb_types::id_gen::uuid_v4())
133        }
134        "uuid_v7" => serde_json::Value::String(nodedb_types::id_gen::uuid_v7()),
135        "ulid" => serde_json::Value::String(nodedb_types::id_gen::ulid()),
136        "cuid2" => serde_json::Value::String(nodedb_types::id_gen::cuid2()),
137        "nanoid" => {
138            let len = num_arg(args, 0).map(|n| n as usize);
139            match len {
140                Some(l) => serde_json::Value::String(nodedb_types::id_gen::nanoid_with_length(l)),
141                None => serde_json::Value::String(nodedb_types::id_gen::nanoid()),
142            }
143        }
144
145        // ── ID type detection ──
146        "is_uuid" => bool_id_check(args, nodedb_types::id_gen::is_uuid),
147        "is_ulid" => bool_id_check(args, nodedb_types::id_gen::is_ulid),
148        "is_cuid2" => bool_id_check(args, nodedb_types::id_gen::is_cuid2),
149        "is_nanoid" => bool_id_check(args, nodedb_types::id_gen::is_nanoid),
150        "id_type" => args
151            .first()
152            .and_then(|v| v.as_str())
153            .map_or(serde_json::Value::String("unknown".into()), |s| {
154                serde_json::Value::String(nodedb_types::id_gen::detect_id_type(s).to_string())
155            }),
156        "uuid_version" => args
157            .first()
158            .and_then(|v| v.as_str())
159            .map_or(serde_json::Value::Number(0.into()), |s| {
160                serde_json::Value::Number(nodedb_types::id_gen::uuid_version(s).into())
161            }),
162        "ulid_timestamp" => args
163            .first()
164            .and_then(|v| v.as_str())
165            .and_then(nodedb_types::id_gen::ulid_timestamp_ms)
166            .map_or(serde_json::Value::Null, |ms| {
167                serde_json::Value::Number(serde_json::Number::from(ms as i64))
168            }),
169
170        // ── DateTime ──
171        "now" | "current_timestamp" => {
172            let dt = nodedb_types::NdbDateTime::now();
173            serde_json::Value::String(dt.to_iso8601())
174        }
175        "datetime" | "to_datetime" => args
176            .first()
177            .and_then(|v| match v {
178                serde_json::Value::String(s) => nodedb_types::NdbDateTime::parse(s)
179                    .map(|dt| serde_json::Value::String(dt.to_iso8601())),
180                serde_json::Value::Number(n) => {
181                    let micros = n.as_i64().unwrap_or(0);
182                    Some(serde_json::Value::String(
183                        nodedb_types::NdbDateTime::from_micros(micros).to_iso8601(),
184                    ))
185                }
186                _ => None,
187            })
188            .unwrap_or(serde_json::Value::Null),
189        "unix_secs" | "epoch_secs" => args
190            .first()
191            .and_then(|v| v.as_str())
192            .and_then(nodedb_types::NdbDateTime::parse)
193            .map_or(serde_json::Value::Null, |dt| {
194                serde_json::Value::Number(dt.unix_secs().into())
195            }),
196        "unix_millis" | "epoch_millis" => args
197            .first()
198            .and_then(|v| v.as_str())
199            .and_then(nodedb_types::NdbDateTime::parse)
200            .map_or(serde_json::Value::Null, |dt| {
201                serde_json::Value::Number(dt.unix_millis().into())
202            }),
203        "extract" | "date_part" => {
204            let part = args.first().and_then(|v| v.as_str()).unwrap_or("");
205            let dt = args
206                .get(1)
207                .and_then(|v| v.as_str())
208                .and_then(nodedb_types::NdbDateTime::parse);
209            match dt {
210                Some(dt) => {
211                    let c = dt.components();
212                    let val: i64 = match part.to_lowercase().as_str() {
213                        "year" | "y" => c.year as i64,
214                        "month" | "mon" => c.month as i64,
215                        "day" | "d" => c.day as i64,
216                        "hour" | "h" => c.hour as i64,
217                        "minute" | "min" | "m" => c.minute as i64,
218                        "second" | "sec" | "s" => c.second as i64,
219                        "microsecond" | "us" => c.microsecond as i64,
220                        "epoch" => dt.unix_secs(),
221                        "dow" | "dayofweek" => {
222                            let days = dt.micros / 86_400_000_000;
223                            (days + 4) % 7
224                        }
225                        _ => return serde_json::Value::Null,
226                    };
227                    serde_json::Value::Number(val.into())
228                }
229                None => serde_json::Value::Null,
230            }
231        }
232        "date_trunc" | "datetrunc" => {
233            let part = args.first().and_then(|v| v.as_str()).unwrap_or("");
234            let dt = args
235                .get(1)
236                .and_then(|v| v.as_str())
237                .and_then(nodedb_types::NdbDateTime::parse);
238            match dt {
239                Some(dt) => {
240                    let c = dt.components();
241                    let truncated = match part.to_lowercase().as_str() {
242                        "year" => nodedb_types::NdbDateTime::parse(&format!(
243                            "{:04}-01-01T00:00:00Z",
244                            c.year
245                        )),
246                        "month" => nodedb_types::NdbDateTime::parse(&format!(
247                            "{:04}-{:02}-01T00:00:00Z",
248                            c.year, c.month
249                        )),
250                        "day" => nodedb_types::NdbDateTime::parse(&format!(
251                            "{:04}-{:02}-{:02}T00:00:00Z",
252                            c.year, c.month, c.day
253                        )),
254                        "hour" => nodedb_types::NdbDateTime::parse(&format!(
255                            "{:04}-{:02}-{:02}T{:02}:00:00Z",
256                            c.year, c.month, c.day, c.hour
257                        )),
258                        "minute" => nodedb_types::NdbDateTime::parse(&format!(
259                            "{:04}-{:02}-{:02}T{:02}:{:02}:00Z",
260                            c.year, c.month, c.day, c.hour, c.minute
261                        )),
262                        "second" => nodedb_types::NdbDateTime::parse(&format!(
263                            "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
264                            c.year, c.month, c.day, c.hour, c.minute, c.second
265                        )),
266                        _ => None,
267                    };
268                    truncated.map_or(serde_json::Value::Null, |t| {
269                        serde_json::Value::String(t.to_iso8601())
270                    })
271                }
272                None => serde_json::Value::Null,
273            }
274        }
275        "date_add" | "datetime_add" => {
276            let dt = args
277                .first()
278                .and_then(|v| v.as_str())
279                .and_then(nodedb_types::NdbDateTime::parse);
280            let dur = args
281                .get(1)
282                .and_then(|v| v.as_str())
283                .and_then(nodedb_types::NdbDuration::parse);
284            match (dt, dur) {
285                (Some(dt), Some(dur)) => {
286                    serde_json::Value::String(dt.add_duration(dur).to_iso8601())
287                }
288                _ => serde_json::Value::Null,
289            }
290        }
291        "date_sub" | "datetime_sub" => {
292            let dt = args
293                .first()
294                .and_then(|v| v.as_str())
295                .and_then(nodedb_types::NdbDateTime::parse);
296            let dur = args
297                .get(1)
298                .and_then(|v| v.as_str())
299                .and_then(nodedb_types::NdbDuration::parse);
300            match (dt, dur) {
301                (Some(dt), Some(dur)) => {
302                    serde_json::Value::String(dt.sub_duration(dur).to_iso8601())
303                }
304                _ => serde_json::Value::Null,
305            }
306        }
307        "date_diff" | "datediff" => {
308            let dt1 = args
309                .first()
310                .and_then(|v| v.as_str())
311                .and_then(nodedb_types::NdbDateTime::parse);
312            let dt2 = args
313                .get(1)
314                .and_then(|v| v.as_str())
315                .and_then(nodedb_types::NdbDateTime::parse);
316            match (dt1, dt2) {
317                (Some(a), Some(b)) => to_json_number(a.duration_since(&b).as_secs_f64()),
318                _ => serde_json::Value::Null,
319            }
320        }
321        "duration" | "to_duration" => args
322            .first()
323            .and_then(|v| v.as_str())
324            .and_then(nodedb_types::NdbDuration::parse)
325            .map_or(serde_json::Value::Null, |d| {
326                serde_json::Value::String(d.to_human())
327            }),
328
329        "decimal" | "to_decimal" => args.first().map_or(serde_json::Value::Null, |v| {
330            let s = json_to_display_string(v);
331            match s.parse::<rust_decimal::Decimal>() {
332                Ok(d) => serde_json::Value::String(d.to_string()),
333                Err(_) => serde_json::Value::Null,
334            }
335        }),
336
337        // ── JSON manipulation ──
338        "json_extract" | "json_get" => {
339            let obj = args.first().unwrap_or(&serde_json::Value::Null);
340            let path = args.get(1).and_then(|v| v.as_str()).unwrap_or("");
341            let mut current = obj;
342            for key in path.split('.') {
343                current = match current {
344                    serde_json::Value::Object(map) => {
345                        map.get(key).unwrap_or(&serde_json::Value::Null)
346                    }
347                    serde_json::Value::Array(arr) => key
348                        .parse::<usize>()
349                        .ok()
350                        .and_then(|i| arr.get(i))
351                        .unwrap_or(&serde_json::Value::Null),
352                    _ => &serde_json::Value::Null,
353                };
354            }
355            current.clone()
356        }
357        "json_set" => {
358            let mut obj = args
359                .first()
360                .cloned()
361                .unwrap_or(serde_json::Value::Object(serde_json::Map::new()));
362            let key = args.get(1).and_then(|v| v.as_str()).unwrap_or("");
363            let val = args.get(2).cloned().unwrap_or(serde_json::Value::Null);
364            if let serde_json::Value::Object(ref mut map) = obj {
365                map.insert(key.to_string(), val);
366            }
367            obj
368        }
369        "json_remove" => {
370            let mut obj = args.first().cloned().unwrap_or(serde_json::Value::Null);
371            let key = args.get(1).and_then(|v| v.as_str()).unwrap_or("");
372            if let serde_json::Value::Object(ref mut map) = obj {
373                map.remove(key);
374            }
375            obj
376        }
377        "json_keys" => match args.first() {
378            Some(serde_json::Value::Object(map)) => serde_json::Value::Array(
379                map.keys()
380                    .map(|k| serde_json::Value::String(k.clone()))
381                    .collect(),
382            ),
383            _ => serde_json::Value::Null,
384        },
385        "json_values" => match args.first() {
386            Some(serde_json::Value::Object(map)) => {
387                serde_json::Value::Array(map.values().cloned().collect())
388            }
389            _ => serde_json::Value::Null,
390        },
391        "json_length" | "json_len" => match args.first() {
392            Some(serde_json::Value::Object(map)) => {
393                serde_json::Value::Number(serde_json::Number::from(map.len() as i64))
394            }
395            Some(serde_json::Value::Array(arr)) => {
396                serde_json::Value::Number(serde_json::Number::from(arr.len() as i64))
397            }
398            Some(serde_json::Value::String(s)) => {
399                serde_json::Value::Number(serde_json::Number::from(s.len() as i64))
400            }
401            _ => serde_json::Value::Null,
402        },
403        "json_type" => {
404            let type_name = match args.first() {
405                Some(serde_json::Value::Null) | None => "null",
406                Some(serde_json::Value::Bool(_)) => "boolean",
407                Some(serde_json::Value::Number(_)) => "number",
408                Some(serde_json::Value::String(_)) => "string",
409                Some(serde_json::Value::Array(_)) => "array",
410                Some(serde_json::Value::Object(_)) => "object",
411            };
412            serde_json::Value::String(type_name.into())
413        }
414        "json_array" => serde_json::Value::Array(args.to_vec()),
415        "json_object" => {
416            let mut map = serde_json::Map::new();
417            let mut i = 0;
418            while i + 1 < args.len() {
419                let key = json_to_display_string(&args[i]);
420                let val = args[i + 1].clone();
421                map.insert(key, val);
422                i += 2;
423            }
424            serde_json::Value::Object(map)
425        }
426        "json_contains" => {
427            let container = args.first().unwrap_or(&serde_json::Value::Null);
428            let needle = args.get(1).unwrap_or(&serde_json::Value::Null);
429            let result = match container {
430                serde_json::Value::Array(arr) => arr.contains(needle),
431                serde_json::Value::Object(map) => {
432                    if let Some(key) = needle.as_str() {
433                        map.contains_key(key)
434                    } else {
435                        false
436                    }
437                }
438                _ => false,
439            };
440            serde_json::Value::Bool(result)
441        }
442        "json_merge" | "json_patch" => {
443            let mut base = match args.first() {
444                Some(serde_json::Value::Object(m)) => m.clone(),
445                _ => serde_json::Map::new(),
446            };
447            if let Some(serde_json::Value::Object(overlay)) = args.get(1) {
448                for (k, v) in overlay {
449                    base.insert(k.clone(), v.clone());
450                }
451            }
452            serde_json::Value::Object(base)
453        }
454
455        // ── Type checking ──
456        "typeof" | "type_of" => {
457            let type_name = match args.first() {
458                Some(serde_json::Value::Null) => "null",
459                Some(serde_json::Value::Bool(_)) => "bool",
460                Some(serde_json::Value::Number(n)) => {
461                    if n.is_i64() {
462                        "int"
463                    } else {
464                        "float"
465                    }
466                }
467                Some(serde_json::Value::String(_)) => "string",
468                Some(serde_json::Value::Array(_)) => "array",
469                Some(serde_json::Value::Object(_)) => "object",
470                None => "null",
471            };
472            serde_json::Value::String(type_name.to_string())
473        }
474
475        // Geo / Spatial functions — delegated to geo_functions module.
476        other => {
477            crate::geo_functions::eval_geo_function(other, args).unwrap_or(serde_json::Value::Null)
478        }
479    }
480}
481
482// ── Argument helpers ──
483
484/// Extract a string argument, returning None for null/missing.
485fn str_arg(args: &[serde_json::Value], idx: usize) -> Option<String> {
486    args.get(idx)?.as_str().map(|s| s.to_string())
487}
488
489/// Extract a numeric argument with bool coercion.
490fn num_arg(args: &[serde_json::Value], idx: usize) -> Option<f64> {
491    args.get(idx).and_then(|v| json_to_f64(v, true))
492}
493
494/// Check if the first arg is a string matching a predicate.
495fn bool_id_check(args: &[serde_json::Value], check: impl Fn(&str) -> bool) -> serde_json::Value {
496    args.first()
497        .and_then(|v| v.as_str())
498        .map_or(serde_json::Value::Bool(false), |s| {
499            serde_json::Value::Bool(check(s))
500        })
501}
502
503#[cfg(test)]
504mod tests {
505    use super::*;
506    use crate::expr::SqlExpr;
507    use serde_json::json;
508
509    fn eval_fn(name: &str, args: Vec<serde_json::Value>) -> serde_json::Value {
510        eval_function(name, &args)
511    }
512
513    #[test]
514    fn upper() {
515        assert_eq!(eval_fn("upper", vec![json!("hello")]), json!("HELLO"));
516    }
517
518    #[test]
519    fn upper_null_propagation() {
520        assert_eq!(eval_fn("upper", vec![json!(null)]), json!(null));
521    }
522
523    #[test]
524    fn substring() {
525        assert_eq!(
526            eval_fn("substr", vec![json!("hello"), json!(2), json!(3)]),
527            json!("ell")
528        );
529    }
530
531    #[test]
532    fn round_with_decimals() {
533        assert_eq!(
534            eval_fn("round", vec![json!(3.15159), json!(2)]),
535            json!(3.15)
536        );
537    }
538
539    #[test]
540    fn typeof_int() {
541        assert_eq!(eval_fn("typeof", vec![json!(42)]), json!("int"));
542    }
543
544    #[test]
545    fn function_via_expr() {
546        let expr = SqlExpr::Function {
547            name: "upper".into(),
548            args: vec![SqlExpr::Column("name".into())],
549        };
550        let doc = json!({"name": "alice"});
551        assert_eq!(expr.eval(&doc), json!("ALICE"));
552    }
553
554    #[test]
555    fn geo_geohash_encode() {
556        let result = eval_fn(
557            "geo_geohash",
558            vec![json!(-73.9857), json!(40.758), json!(6)],
559        );
560        let hash = result.as_str().unwrap();
561        assert_eq!(hash.len(), 6);
562        assert!(hash.starts_with("dr5ru"), "got {hash}");
563    }
564
565    #[test]
566    fn geo_geohash_decode() {
567        let hash = eval_fn("geo_geohash", vec![json!(0.0), json!(0.0), json!(6)]);
568        let result = eval_fn("geo_geohash_decode", vec![hash]);
569        assert!(result.is_object());
570        assert!(result["min_lng"].as_f64().is_some());
571        assert!(result["max_lat"].as_f64().is_some());
572    }
573
574    #[test]
575    fn geo_geohash_neighbors_returns_8() {
576        let hash = eval_fn("geo_geohash", vec![json!(10.0), json!(50.0), json!(6)]);
577        let result = eval_fn("geo_geohash_neighbors", vec![hash]);
578        let arr = result.as_array().unwrap();
579        assert_eq!(arr.len(), 8);
580    }
581
582    fn point_json(lng: f64, lat: f64) -> serde_json::Value {
583        json!({"type": "Point", "coordinates": [lng, lat]})
584    }
585
586    fn square_json() -> serde_json::Value {
587        json!({"type": "Polygon", "coordinates": [[[0.0,0.0],[10.0,0.0],[10.0,10.0],[0.0,10.0],[0.0,0.0]]]})
588    }
589
590    #[test]
591    fn st_contains_sql() {
592        let result = eval_fn("st_contains", vec![square_json(), point_json(5.0, 5.0)]);
593        assert_eq!(result, json!(true));
594    }
595
596    #[test]
597    fn st_intersects_sql() {
598        let result = eval_fn("st_intersects", vec![square_json(), point_json(5.0, 0.0)]);
599        assert_eq!(result, json!(true));
600    }
601
602    #[test]
603    fn st_distance_sql() {
604        let result = eval_fn(
605            "st_distance",
606            vec![point_json(0.0, 0.0), point_json(0.0, 1.0)],
607        );
608        let d = result.as_f64().unwrap();
609        assert!((d - 111_195.0).abs() < 500.0, "got {d}");
610    }
611
612    #[test]
613    fn st_dwithin_sql() {
614        let result = eval_fn(
615            "st_dwithin",
616            vec![point_json(0.0, 0.0), point_json(0.001, 0.0), json!(200.0)],
617        );
618        assert_eq!(result, json!(true));
619    }
620
621    #[test]
622    fn st_buffer_sql() {
623        let result = eval_fn(
624            "st_buffer",
625            vec![point_json(0.0, 0.0), json!(1000.0), json!(8)],
626        );
627        assert!(result.is_object());
628        assert_eq!(result["type"], "Polygon");
629    }
630
631    #[test]
632    fn st_envelope_sql() {
633        let result = eval_fn("st_envelope", vec![square_json()]);
634        assert_eq!(result["type"], "Polygon");
635    }
636
637    #[test]
638    fn geo_length_sql() {
639        let line = json!({"type": "LineString", "coordinates": [[0.0,0.0],[0.0,1.0]]});
640        let result = eval_fn("geo_length", vec![line]);
641        let d = result.as_f64().unwrap();
642        assert!((d - 111_195.0).abs() < 500.0, "got {d}");
643    }
644
645    #[test]
646    fn geo_x_y() {
647        assert_eq!(
648            eval_fn("geo_x", vec![point_json(5.0, 10.0)])
649                .as_f64()
650                .unwrap(),
651            5.0
652        );
653        assert_eq!(
654            eval_fn("geo_y", vec![point_json(5.0, 10.0)])
655                .as_f64()
656                .unwrap(),
657            10.0
658        );
659    }
660
661    #[test]
662    fn geo_type_sql() {
663        assert_eq!(
664            eval_fn("geo_type", vec![point_json(0.0, 0.0)]),
665            json!("Point")
666        );
667        assert_eq!(eval_fn("geo_type", vec![square_json()]), json!("Polygon"));
668    }
669
670    #[test]
671    fn geo_num_points_sql() {
672        assert_eq!(
673            eval_fn("geo_num_points", vec![point_json(0.0, 0.0)]),
674            json!(1)
675        );
676        assert_eq!(eval_fn("geo_num_points", vec![square_json()]), json!(5));
677    }
678
679    #[test]
680    fn geo_is_valid_sql() {
681        assert_eq!(eval_fn("geo_is_valid", vec![square_json()]), json!(true));
682    }
683
684    #[test]
685    fn geo_as_wkt_sql() {
686        let result = eval_fn("geo_as_wkt", vec![point_json(5.0, 10.0)]);
687        assert_eq!(result, json!("POINT(5 10)"));
688    }
689
690    #[test]
691    fn geo_from_wkt_sql() {
692        let result = eval_fn("geo_from_wkt", vec![json!("POINT(5 10)")]);
693        assert_eq!(result["type"], "Point");
694    }
695
696    #[test]
697    fn geo_circle_sql() {
698        let result = eval_fn(
699            "geo_circle",
700            vec![json!(0.0), json!(0.0), json!(1000.0), json!(16)],
701        );
702        assert_eq!(result["type"], "Polygon");
703    }
704
705    #[test]
706    fn geo_bbox_sql() {
707        let result = eval_fn(
708            "geo_bbox",
709            vec![json!(0.0), json!(0.0), json!(10.0), json!(10.0)],
710        );
711        assert_eq!(result["type"], "Polygon");
712    }
713}