Skip to main content

toasty_core/stmt/
eval.rs

1//! Client-side evaluation of constant or input-bound expressions and
2//! statements.
3//!
4//! The evaluator walks the expression tree recursively, resolving arguments
5//! via an [`Input`] implementation and producing [`Value`]s. It supports
6//! boolean logic, comparison, casting, records, lists, let-bindings, match
7//! expressions, and subqueries (VALUES only).
8//!
9//! # Examples
10//!
11//! ```
12//! use toasty_core::stmt::{Expr, Value, ConstInput};
13//!
14//! let expr = Expr::from(Value::from(42_i64));
15//! let result = expr.eval(ConstInput::new()).unwrap();
16//! assert_eq!(result, Value::from(42_i64));
17//! ```
18
19use crate::{
20    Result,
21    stmt::{
22        BinaryOp, ConstInput, Expr, ExprArg, ExprSet, Input, Limit, Projection, Statement, Value,
23    },
24};
25use std::cmp::Ordering;
26
27enum ScopeStack<'a> {
28    Root,
29    Scope {
30        args: &'a [Value],
31        parent: &'a ScopeStack<'a>,
32    },
33}
34
35impl Statement {
36    /// Evaluates this statement using the provided [`Input`] for argument
37    /// resolution. Only `Query` statements are supported.
38    ///
39    /// # Errors
40    ///
41    /// Returns an error for non-Query statements, or if evaluation of any
42    /// sub-expression fails.
43    pub fn eval(&self, mut input: impl Input) -> Result<Value> {
44        self.eval_ref(&ScopeStack::Root, &mut input)
45    }
46
47    /// Evaluates this statement as a constant expression (no external input).
48    pub fn eval_const(&self) -> Result<Value> {
49        self.eval(ConstInput::new())
50    }
51
52    fn eval_ref(&self, scope: &ScopeStack<'_>, input: &mut impl Input) -> Result<Value> {
53        match self {
54            Statement::Query(query) => {
55                if query.with.is_some() {
56                    return Err(crate::Error::expression_evaluation_failed(
57                        "cannot evaluate statement with WITH clause",
58                    ));
59                }
60
61                if query.order_by.is_some() {
62                    return Err(crate::Error::expression_evaluation_failed(
63                        "cannot evaluate statement with ORDER BY clause",
64                    ));
65                }
66
67                let mut result = query.body.eval_ref(scope, input)?;
68
69                if let Some(limit) = &query.limit {
70                    limit.eval_ref(&mut result, scope, input)?;
71                }
72
73                if query.single {
74                    let Value::List(mut items) = result else {
75                        return Err(crate::Error::expression_evaluation_failed(
76                            "single-row query requires body to evaluate to a list",
77                        ));
78                    };
79                    if items.len() != 1 {
80                        return Err(crate::Error::expression_evaluation_failed(
81                            "single-row query did not return exactly one row",
82                        ));
83                    }
84                    return Ok(items.remove(0));
85                }
86
87                Ok(result)
88            }
89            _ => Err(crate::Error::expression_evaluation_failed(
90                "can only evaluate Query statements",
91            )),
92        }
93    }
94}
95
96impl Limit {
97    fn eval_ref(
98        &self,
99        value: &mut Value,
100        scope: &ScopeStack<'_>,
101        input: &mut impl Input,
102    ) -> Result<()> {
103        let Value::List(items) = value else {
104            return Err(crate::Error::expression_evaluation_failed(
105                "LIMIT requires body to evaluate to a list",
106            ));
107        };
108
109        match self {
110            Limit::Cursor(_) => {
111                return Err(crate::Error::expression_evaluation_failed(
112                    "cursor-based pagination cannot be evaluated client-side",
113                ));
114            }
115            Limit::Offset(limit_offset) => {
116                if let Some(offset_expr) = &limit_offset.offset {
117                    let skip = offset_expr.eval_ref_usize(scope, input)?;
118                    if skip >= items.len() {
119                        items.clear();
120                    } else {
121                        items.drain(..skip);
122                    }
123                }
124
125                let n = limit_offset.limit.eval_ref_usize(scope, input)?;
126                items.truncate(n);
127            }
128        }
129        Ok(())
130    }
131}
132
133impl ExprSet {
134    fn eval_ref(&self, scope: &ScopeStack<'_>, input: &mut impl Input) -> Result<Value> {
135        let ExprSet::Values(values) = self else {
136            return Err(crate::Error::expression_evaluation_failed(
137                "can only evaluate Values expressions",
138            ));
139        };
140
141        let mut ret = vec![];
142
143        for row in &values.rows {
144            ret.push(row.eval_ref(scope, input)?);
145        }
146
147        Ok(Value::List(ret))
148    }
149}
150
151impl Expr {
152    /// Evaluates this expression using the provided [`Input`] for argument
153    /// and reference resolution.
154    pub fn eval(&self, mut input: impl Input) -> Result<Value> {
155        self.eval_ref(&ScopeStack::Root, &mut input)
156    }
157
158    /// Evaluates this expression and returns the result as a `bool`.
159    ///
160    /// # Errors
161    ///
162    /// Returns an error if the expression does not evaluate to a boolean.
163    pub fn eval_bool(&self, mut input: impl Input) -> Result<bool> {
164        self.eval_ref_bool(&ScopeStack::Root, &mut input)
165    }
166
167    /// Evaluates this expression as a constant (no external input).
168    pub fn eval_const(&self) -> Result<Value> {
169        self.eval(ConstInput::new())
170    }
171
172    fn eval_ref(&self, scope: &ScopeStack<'_>, input: &mut impl Input) -> Result<Value> {
173        match self {
174            Expr::And(expr_and) => {
175                debug_assert!(!expr_and.operands.is_empty());
176
177                for operand in &expr_and.operands {
178                    if !operand.eval_ref_bool(scope, input)? {
179                        return Ok(false.into());
180                    }
181                }
182
183                Ok(true.into())
184            }
185            Expr::Arg(expr_arg) => {
186                let Some(expr) = scope.resolve_arg(expr_arg, &Projection::identity(), input) else {
187                    return Err(crate::Error::expression_evaluation_failed(
188                        "failed to resolve argument",
189                    ));
190                };
191                expr.eval_ref(scope, input)
192            }
193            Expr::BinaryOp(expr_binary_op) => {
194                let lhs = expr_binary_op.lhs.eval_ref(scope, input)?;
195                let rhs = expr_binary_op.rhs.eval_ref(scope, input)?;
196
197                match expr_binary_op.op {
198                    BinaryOp::Eq => Ok((lhs == rhs).into()),
199                    BinaryOp::Ne => Ok((lhs != rhs).into()),
200                    BinaryOp::Ge => Ok((cmp_ordered(&lhs, &rhs)? != Ordering::Less).into()),
201                    BinaryOp::Gt => Ok((cmp_ordered(&lhs, &rhs)? == Ordering::Greater).into()),
202                    BinaryOp::Le => Ok((cmp_ordered(&lhs, &rhs)? != Ordering::Greater).into()),
203                    BinaryOp::Lt => Ok((cmp_ordered(&lhs, &rhs)? == Ordering::Less).into()),
204                }
205            }
206            Expr::Cast(expr_cast) => expr_cast.ty.cast(expr_cast.expr.eval_ref(scope, input)?),
207            Expr::Default => Err(crate::Error::expression_evaluation_failed(
208                "DEFAULT can only be evaluated by the database",
209            )),
210            Expr::Error(expr_error) => Err(crate::Error::expression_evaluation_failed(
211                &expr_error.message,
212            )),
213            Expr::IsNull(expr_is_null) => {
214                let value = expr_is_null.expr.eval_ref(scope, input)?;
215                Ok(value.is_null().into())
216            }
217            Expr::IsVariant(_) => Err(crate::Error::expression_evaluation_failed(
218                "IsVariant must be lowered before evaluation",
219            )),
220            Expr::Let(expr_let) => {
221                let args: Vec<_> = expr_let
222                    .bindings
223                    .iter()
224                    .map(|b| b.eval_ref(scope, input))
225                    .collect::<Result<_, _>>()?;
226                let scope = scope.scope(&args);
227                expr_let.body.eval_ref(&scope, input)
228            }
229            Expr::Not(expr_not) => {
230                let value = expr_not.expr.eval_ref_bool(scope, input)?;
231                Ok((!value).into())
232            }
233            Expr::List(exprs) => {
234                let mut ret = vec![];
235
236                for expr in &exprs.items {
237                    ret.push(expr.eval_ref(scope, input)?);
238                }
239
240                Ok(Value::List(ret))
241            }
242            Expr::Map(expr_map) => {
243                let mut base = expr_map.base.eval_ref(scope, input)?;
244
245                let Value::List(items) = &mut base else {
246                    return Err(crate::Error::expression_evaluation_failed(
247                        "Map base must evaluate to a list",
248                    ));
249                };
250
251                for item in items.iter_mut() {
252                    let args = [item.take()];
253                    let scope = scope.scope(&args);
254                    *item = expr_map.map.eval_ref(&scope, input)?;
255                }
256
257                Ok(base)
258            }
259            Expr::Project(expr_project) => match &*expr_project.base {
260                Expr::Arg(expr_arg) => {
261                    let Some(expr) = scope.resolve_arg(expr_arg, &expr_project.projection, input)
262                    else {
263                        return Err(crate::Error::expression_evaluation_failed(
264                            "failed to resolve argument",
265                        ));
266                    };
267
268                    expr.eval_ref(scope, input)
269                }
270                Expr::Reference(expr_reference) => {
271                    let Some(expr) = input.resolve_ref(expr_reference, &expr_project.projection)
272                    else {
273                        return Err(crate::Error::expression_evaluation_failed(
274                            "failed to resolve reference",
275                        ));
276                    };
277
278                    expr.eval_ref(scope, input)
279                }
280                _ => {
281                    let base = expr_project.base.eval_ref(scope, input)?;
282                    Ok(base.entry(&expr_project.projection).to_value())
283                }
284            },
285            Expr::Record(expr_record) => {
286                let mut ret = Vec::with_capacity(expr_record.len());
287
288                for expr in &expr_record.fields {
289                    ret.push(expr.eval_ref(scope, input)?);
290                }
291
292                Ok(Value::record_from_vec(ret))
293            }
294            Expr::Reference(expr_reference) => {
295                let Some(expr) = input.resolve_ref(expr_reference, &Projection::identity()) else {
296                    return Err(crate::Error::expression_evaluation_failed(
297                        "failed to resolve reference",
298                    ));
299                };
300
301                expr.eval_ref(scope, input)
302            }
303            Expr::Or(expr_or) => {
304                debug_assert!(!expr_or.operands.is_empty());
305
306                for operand in &expr_or.operands {
307                    if operand.eval_ref_bool(scope, input)? {
308                        return Ok(true.into());
309                    }
310                }
311
312                Ok(false.into())
313            }
314            Expr::Any(expr_any) => {
315                let list = expr_any.expr.eval_ref(scope, input)?;
316
317                let Value::List(items) = list else {
318                    return Err(crate::Error::expression_evaluation_failed(
319                        "Any expression must evaluate to a list",
320                    ));
321                };
322
323                for item in &items {
324                    match item {
325                        Value::Bool(true) => return Ok(true.into()),
326                        Value::Bool(false) => {}
327                        _ => {
328                            return Err(crate::Error::expression_evaluation_failed(
329                                "Any expression items must evaluate to bool",
330                            ));
331                        }
332                    }
333                }
334
335                Ok(false.into())
336            }
337            Expr::InList(expr_in_list) => {
338                let needle = expr_in_list.expr.eval_ref(scope, input)?;
339                let list = expr_in_list.list.eval_ref(scope, input)?;
340
341                let Value::List(items) = list else {
342                    return Err(crate::Error::expression_evaluation_failed(
343                        "InList right-hand side must evaluate to a list",
344                    ));
345                };
346
347                Ok(items.iter().any(|item| item == &needle).into())
348            }
349            Expr::AnyOp(e) => {
350                let lhs = e.lhs.eval_ref(scope, input)?;
351                let rhs = e.rhs.eval_ref(scope, input)?;
352                let Value::List(items) = rhs else {
353                    return Err(crate::Error::expression_evaluation_failed(
354                        "ANY right-hand side must evaluate to a list",
355                    ));
356                };
357                Ok(any_all_compare(&lhs, &items, e.op, /*all=*/ false)?.into())
358            }
359            Expr::AllOp(e) => {
360                let lhs = e.lhs.eval_ref(scope, input)?;
361                let rhs = e.rhs.eval_ref(scope, input)?;
362                let Value::List(items) = rhs else {
363                    return Err(crate::Error::expression_evaluation_failed(
364                        "ALL right-hand side must evaluate to a list",
365                    ));
366                };
367                Ok(any_all_compare(&lhs, &items, e.op, /*all=*/ true)?.into())
368            }
369            Expr::Match(expr_match) => {
370                let subject = expr_match.subject.eval_ref(scope, input)?;
371                for arm in &expr_match.arms {
372                    if subject == arm.pattern {
373                        return arm.expr.eval_ref(scope, input);
374                    }
375                }
376                expr_match.else_expr.eval_ref(scope, input)
377            }
378            Expr::Exists(expr_exists) => {
379                // Evaluate the subquery body. For Values bodies the rows are
380                // evaluated and flattened; for other bodies we evaluate the
381                // query as an expression.
382                match &expr_exists.subquery.body {
383                    ExprSet::Values(values) => {
384                        for row in &values.rows {
385                            let val = row.eval_ref(scope, input)?;
386                            match val {
387                                // An empty list means no rows — keep checking
388                                Value::List(items) if items.is_empty() => {}
389                                // Null means the row doesn't exist
390                                Value::Null => {}
391                                // Any other value means at least one row exists
392                                _ => return Ok(true.into()),
393                            }
394                        }
395                        Ok(false.into())
396                    }
397                    _ => todo!("ExprExists with non-Values body"),
398                }
399            }
400            Expr::Value(value) => Ok(value.clone()),
401            Expr::Func(_) => Err(crate::Error::expression_evaluation_failed(
402                "database functions cannot be evaluated client-side",
403            )),
404            _ => todo!("expr={self:#?}"),
405        }
406    }
407
408    fn eval_ref_bool(&self, scope: &ScopeStack<'_>, input: &mut impl Input) -> Result<bool> {
409        match self.eval_ref(scope, input)? {
410            Value::Bool(ret) => Ok(ret),
411            _ => Err(crate::Error::expression_evaluation_failed(
412                "expected boolean value",
413            )),
414        }
415    }
416
417    fn eval_ref_usize(&self, scope: &ScopeStack<'_>, input: &mut impl Input) -> Result<usize> {
418        match self.eval_ref(scope, input)? {
419            Value::I64(n) if n >= 0 => Ok(n as usize),
420            _ => Err(crate::Error::expression_evaluation_failed(
421                "expected non-negative integer",
422            )),
423        }
424    }
425}
426
427impl ScopeStack<'_> {
428    fn resolve_arg(
429        &self,
430        expr_arg: &ExprArg,
431        projection: &Projection,
432        input: &mut impl Input,
433    ) -> Option<Expr> {
434        let mut nesting = expr_arg.nesting;
435        let mut scope = self;
436
437        while nesting > 0 {
438            nesting -= 1;
439
440            scope = match scope {
441                ScopeStack::Root => return None,
442                ScopeStack::Scope { parent, .. } => parent,
443            };
444        }
445
446        match scope {
447            ScopeStack::Root => input.resolve_arg(expr_arg, projection),
448            &ScopeStack::Scope { mut args, .. } => args.resolve_arg(expr_arg, projection),
449        }
450    }
451
452    fn scope<'child>(&'child self, args: &'child [Value]) -> ScopeStack<'child> {
453        ScopeStack::Scope { args, parent: self }
454    }
455}
456
457fn cmp_ordered(lhs: &Value, rhs: &Value) -> Result<Ordering> {
458    if lhs.is_null() || rhs.is_null() {
459        return Err(crate::Error::expression_evaluation_failed(
460            "ordered comparison with NULL is undefined",
461        ));
462    }
463    lhs.partial_cmp(rhs).ok_or_else(|| {
464        crate::Error::expression_evaluation_failed("ordered comparison between incompatible types")
465    })
466}
467
468fn any_all_compare(lhs: &Value, items: &[Value], op: BinaryOp, all: bool) -> Result<bool> {
469    for item in items {
470        let matches = match op {
471            BinaryOp::Eq => lhs == item,
472            BinaryOp::Ne => lhs != item,
473            BinaryOp::Ge => cmp_ordered(lhs, item)? != Ordering::Less,
474            BinaryOp::Gt => cmp_ordered(lhs, item)? == Ordering::Greater,
475            BinaryOp::Le => cmp_ordered(lhs, item)? != Ordering::Greater,
476            BinaryOp::Lt => cmp_ordered(lhs, item)? == Ordering::Less,
477        };
478        if all {
479            if !matches {
480                return Ok(false);
481            }
482        } else if matches {
483            return Ok(true);
484        }
485    }
486    // ANY over empty list → false; ALL over empty list → true.
487    Ok(all)
488}