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, datetime, 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        "now" => (datetime::eval_now_values, Some(datetime::eval_now_lazy)),
81        "vector_similarity" => (super::function_call::eval_vector_similarity_values, None),
82        "vector_distance" => (super::function_call::eval_vector_distance_values, None),
83        "vector_dims" => (super::function_call::eval_vector_dims_values, None),
84        "vector_norm" => (super::function_call::eval_vector_norm_values, None),
85        _ => {
86            if let Some(eval) = numeric::eval_for(name) {
87                (eval, None)
88            } else if let Some(eval) = string::eval_for(name) {
89                (eval, None)
90            } else if let Some(eval) = conditional::eval_for(name) {
91                (eval, conditional::lazy_eval_for(name))
92            } else if let Some(eval) = type_fn::eval_for(name) {
93                (eval, None)
94            } else if let Some(eval) = hash::eval_for(name) {
95                (eval, None)
96            } else {
97                (unsupported, None)
98            }
99        }
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106    use std::collections::HashSet;
107
108    #[test]
109    fn registry_keys_match_signatures() {
110        let signatures: HashSet<_> = scalar::signatures().iter().map(|s| s.name).collect();
111        let registry: HashSet<_> = scalar_registry().names().collect();
112        assert_eq!(signatures, registry);
113    }
114}