Skip to main content

alopex_sql/executor/evaluator/
registry.rs

1//! Runtime dispatch table for scalar functions.
2
3use std::collections::HashMap;
4use std::sync::OnceLock;
5
6use crate::executor::{ExecutorError, Result};
7use crate::planner::typed_expr::TypedExpr;
8use crate::scalar::{self, ScalarSignature};
9use crate::storage::SqlValue;
10
11use super::{EvalContext, conditional, hash, numeric, string, type_fn};
12
13pub type EvalFn = fn(&[SqlValue]) -> Result<SqlValue>;
14pub type LazyEvalFn = fn(&[TypedExpr], &EvalContext<'_>) -> Result<SqlValue>;
15
16pub struct ScalarFunction {
17    pub signature: &'static ScalarSignature,
18    pub eval: EvalFn,
19    pub eval_lazy: Option<LazyEvalFn>,
20}
21
22pub struct ScalarFunctionRegistry {
23    fns: HashMap<&'static str, ScalarFunction>,
24}
25
26impl ScalarFunctionRegistry {
27    fn build() -> Self {
28        let mut fns = HashMap::new();
29        for signature in scalar::signatures() {
30            let (eval, eval_lazy) = evaluator_for(signature.name);
31            fns.insert(
32                signature.name,
33                ScalarFunction {
34                    signature,
35                    eval,
36                    eval_lazy,
37                },
38            );
39        }
40        Self { fns }
41    }
42
43    pub fn get(&self, name: &str) -> Option<&ScalarFunction> {
44        let lower = name.to_ascii_lowercase();
45        self.fns.get(lower.as_str())
46    }
47
48    pub fn contains(&self, name: &str) -> bool {
49        self.get(name).is_some()
50    }
51
52    pub fn names(&self) -> impl Iterator<Item = &'static str> + '_ {
53        self.fns.keys().copied()
54    }
55}
56
57pub fn scalar_registry() -> &'static ScalarFunctionRegistry {
58    static REGISTRY: OnceLock<ScalarFunctionRegistry> = OnceLock::new();
59    REGISTRY.get_or_init(ScalarFunctionRegistry::build)
60}
61
62fn unsupported(values: &[SqlValue]) -> Result<SqlValue> {
63    Err(ExecutorError::Evaluation(
64        crate::executor::EvaluationError::UnsupportedFunction(format!(
65            "unregistered scalar with {} argument(s)",
66            values.len()
67        )),
68    ))
69}
70
71fn system_placeholder(_values: &[SqlValue]) -> Result<SqlValue> {
72    // Direct execution is handled by the executor, which owns the KV store.
73    // Keeping a registry entry lets the planner enforce the function contract.
74    Ok(SqlValue::Null)
75}
76
77fn evaluator_for(name: &str) -> (EvalFn, Option<LazyEvalFn>) {
78    match name {
79        "memory_stats" | "io_stats" | "clear_cache" => (system_placeholder, None),
80        "vector_similarity" => (super::function_call::eval_vector_similarity_values, None),
81        "vector_distance" => (super::function_call::eval_vector_distance_values, None),
82        "vector_dims" => (super::function_call::eval_vector_dims_values, None),
83        "vector_norm" => (super::function_call::eval_vector_norm_values, None),
84        _ => {
85            if let Some(eval) = numeric::eval_for(name) {
86                (eval, None)
87            } else if let Some(eval) = string::eval_for(name) {
88                (eval, None)
89            } else if let Some(eval) = conditional::eval_for(name) {
90                (eval, conditional::lazy_eval_for(name))
91            } else if let Some(eval) = type_fn::eval_for(name) {
92                (eval, None)
93            } else if let Some(eval) = hash::eval_for(name) {
94                (eval, None)
95            } else {
96                (unsupported, None)
97            }
98        }
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105    use std::collections::HashSet;
106
107    #[test]
108    fn registry_keys_match_signatures() {
109        let signatures: HashSet<_> = scalar::signatures().iter().map(|s| s.name).collect();
110        let registry: HashSet<_> = scalar_registry().names().collect();
111        assert_eq!(signatures, registry);
112    }
113}