use nodedb_types::Value;
use super::catalog;
use super::{accessors, constructors, indexing, measures, predicates};
pub fn eval_geo_function(name: &str, args: &[Value]) -> Option<Value> {
let spec = catalog::lookup(name)?;
eval_canonical(spec.canonical, args)
}
fn eval_canonical(canonical: &str, args: &[Value]) -> Option<Value> {
predicates::eval(canonical, args)
.or_else(|| measures::eval(canonical, args))
.or_else(|| accessors::eval(canonical, args))
.or_else(|| constructors::eval(canonical, args))
.or_else(|| indexing::eval(canonical, args))
}
#[cfg(test)]
mod tests {
use super::*;
use nodedb_types::geometry::Geometry;
fn probe_args() -> Vec<Value> {
vec![
Value::Geometry(Geometry::point(1.0, 2.0)),
Value::Geometry(Geometry::point(3.0, 4.0)),
Value::Float(5.0),
Value::Float(6.0),
]
}
#[test]
fn every_catalog_entry_is_evaluable() {
for spec in catalog::GEO_FUNCTIONS {
assert!(
eval_canonical(spec.canonical, &probe_args()).is_some(),
"catalog declares '{}' but no evaluator handles it",
spec.canonical
);
}
}
#[test]
fn every_alias_evaluates_identically_to_its_canonical_name() {
for spec in catalog::GEO_FUNCTIONS {
let canonical = eval_geo_function(spec.canonical, &probe_args());
for alias in spec.aliases {
assert_eq!(
eval_geo_function(alias, &probe_args()),
canonical,
"'{alias}' must behave exactly like '{}'",
spec.canonical
);
}
}
}
#[test]
fn dispatch_is_case_insensitive() {
let args = probe_args();
assert_eq!(
eval_geo_function("ST_AsText", &args),
eval_geo_function("st_astext", &args)
);
}
#[test]
fn non_geo_function_falls_through() {
assert_eq!(eval_geo_function("lower", &probe_args()), None);
}
#[test]
fn standard_accessor_names_read_stored_geometry() {
use crate::value_ops::to_value_number;
let point = vec![Value::Geometry(Geometry::point(1.0, 2.0))];
assert_eq!(
eval_geo_function("st_x", &point),
Some(to_value_number(1.0))
);
assert_eq!(
eval_geo_function("st_y", &point),
Some(to_value_number(2.0))
);
assert!(matches!(
eval_geo_function("st_astext", &point),
Some(Value::String(_))
));
}
}