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                    BinaryOp::Add => lhs.checked_add(&rhs).ok_or_else(|| {
205                        crate::Error::expression_evaluation_failed(
206                            "arithmetic overflow or type mismatch in `+`",
207                        )
208                    }),
209                    BinaryOp::Sub => lhs.checked_sub(&rhs).ok_or_else(|| {
210                        crate::Error::expression_evaluation_failed(
211                            "arithmetic overflow or type mismatch in `-`",
212                        )
213                    }),
214                }
215            }
216            Expr::Cast(expr_cast) => expr_cast.ty.cast(expr_cast.expr.eval_ref(scope, input)?),
217            Expr::Default => Err(crate::Error::expression_evaluation_failed(
218                "DEFAULT can only be evaluated by the database",
219            )),
220            Expr::Error(expr_error) => Err(crate::Error::expression_evaluation_failed(
221                &expr_error.message,
222            )),
223            Expr::IsNull(expr_is_null) => {
224                let value = expr_is_null.expr.eval_ref(scope, input)?;
225                Ok(value.is_null().into())
226            }
227            Expr::IsVariant(_) => Err(crate::Error::expression_evaluation_failed(
228                "IsVariant must be lowered before evaluation",
229            )),
230            Expr::Let(expr_let) => {
231                let args: Vec<_> = expr_let
232                    .bindings
233                    .iter()
234                    .map(|b| b.eval_ref(scope, input))
235                    .collect::<Result<_, _>>()?;
236                let scope = scope.scope(&args);
237                expr_let.body.eval_ref(&scope, input)
238            }
239            Expr::Not(expr_not) => {
240                let value = expr_not.expr.eval_ref_bool(scope, input)?;
241                Ok((!value).into())
242            }
243            Expr::List(exprs) => {
244                let mut ret = vec![];
245
246                for expr in &exprs.items {
247                    ret.push(expr.eval_ref(scope, input)?);
248                }
249
250                Ok(Value::List(ret))
251            }
252            Expr::Map(expr_map) => {
253                let mut base = expr_map.base.eval_ref(scope, input)?;
254
255                let Value::List(items) = &mut base else {
256                    return Err(crate::Error::expression_evaluation_failed(
257                        "Map base must evaluate to a list",
258                    ));
259                };
260
261                for item in items.iter_mut() {
262                    let args = [item.take()];
263                    let scope = scope.scope(&args);
264                    *item = expr_map.map.eval_ref(&scope, input)?;
265                }
266
267                Ok(base)
268            }
269            Expr::Project(expr_project) => match &*expr_project.base {
270                Expr::Arg(expr_arg) => {
271                    let Some(expr) = scope.resolve_arg(expr_arg, &expr_project.projection, input)
272                    else {
273                        return Err(crate::Error::expression_evaluation_failed(
274                            "failed to resolve argument",
275                        ));
276                    };
277
278                    expr.eval_ref(scope, input)
279                }
280                Expr::Reference(expr_reference) => {
281                    let Some(expr) = input.resolve_ref(expr_reference, &expr_project.projection)
282                    else {
283                        return Err(crate::Error::expression_evaluation_failed(
284                            "failed to resolve reference",
285                        ));
286                    };
287
288                    expr.eval_ref(scope, input)
289                }
290                _ => {
291                    let base = expr_project.base.eval_ref(scope, input)?;
292                    Ok(base.entry(&expr_project.projection).to_value())
293                }
294            },
295            Expr::Record(expr_record) => {
296                let mut ret = Vec::with_capacity(expr_record.len());
297
298                for expr in &expr_record.fields {
299                    ret.push(expr.eval_ref(scope, input)?);
300                }
301
302                Ok(Value::record_from_vec(ret))
303            }
304            Expr::Reference(expr_reference) => {
305                let Some(expr) = input.resolve_ref(expr_reference, &Projection::identity()) else {
306                    return Err(crate::Error::expression_evaluation_failed(
307                        "failed to resolve reference",
308                    ));
309                };
310
311                expr.eval_ref(scope, input)
312            }
313            Expr::Or(expr_or) => {
314                debug_assert!(!expr_or.operands.is_empty());
315
316                for operand in &expr_or.operands {
317                    if operand.eval_ref_bool(scope, input)? {
318                        return Ok(true.into());
319                    }
320                }
321
322                Ok(false.into())
323            }
324            Expr::Any(expr_any) => {
325                let list = expr_any.expr.eval_ref(scope, input)?;
326
327                let Value::List(items) = list else {
328                    return Err(crate::Error::expression_evaluation_failed(
329                        "Any expression must evaluate to a list",
330                    ));
331                };
332
333                for item in &items {
334                    match item {
335                        Value::Bool(true) => return Ok(true.into()),
336                        Value::Bool(false) => {}
337                        _ => {
338                            return Err(crate::Error::expression_evaluation_failed(
339                                "Any expression items must evaluate to bool",
340                            ));
341                        }
342                    }
343                }
344
345                Ok(false.into())
346            }
347            Expr::InList(expr_in_list) => {
348                let needle = expr_in_list.expr.eval_ref(scope, input)?;
349                let list = expr_in_list.list.eval_ref(scope, input)?;
350
351                let Value::List(items) = list else {
352                    return Err(crate::Error::expression_evaluation_failed(
353                        "InList right-hand side must evaluate to a list",
354                    ));
355                };
356
357                Ok(items.iter().any(|item| item == &needle).into())
358            }
359            Expr::AnyOp(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                        "ANY right-hand side must evaluate to a list",
365                    ));
366                };
367                Ok(any_all_compare(&lhs, &items, e.op, /*all=*/ false)?.into())
368            }
369            Expr::AllOp(e) => {
370                let lhs = e.lhs.eval_ref(scope, input)?;
371                let rhs = e.rhs.eval_ref(scope, input)?;
372                let Value::List(items) = rhs else {
373                    return Err(crate::Error::expression_evaluation_failed(
374                        "ALL right-hand side must evaluate to a list",
375                    ));
376                };
377                Ok(any_all_compare(&lhs, &items, e.op, /*all=*/ true)?.into())
378            }
379            Expr::Match(expr_match) => {
380                let subject = expr_match.subject.eval_ref(scope, input)?;
381                for arm in &expr_match.arms {
382                    if subject == arm.pattern {
383                        return arm.expr.eval_ref(scope, input);
384                    }
385                }
386                expr_match.else_expr.eval_ref(scope, input)
387            }
388            Expr::Exists(expr_exists) => {
389                // Evaluate the subquery body. For Values bodies the rows are
390                // evaluated and flattened; for other bodies we evaluate the
391                // query as an expression.
392                match &expr_exists.subquery.body {
393                    ExprSet::Values(values) => {
394                        for row in &values.rows {
395                            let val = row.eval_ref(scope, input)?;
396                            match val {
397                                // An empty list means no rows — keep checking
398                                Value::List(items) if items.is_empty() => {}
399                                // Null means the row doesn't exist
400                                Value::Null => {}
401                                // Any other value means at least one row exists
402                                _ => return Ok(true.into()),
403                            }
404                        }
405                        Ok(false.into())
406                    }
407                    _ => todo!("ExprExists with non-Values body"),
408                }
409            }
410            Expr::Value(value) => Ok(value.clone()),
411            Expr::Func(_) => Err(crate::Error::expression_evaluation_failed(
412                "database functions cannot be evaluated client-side",
413            )),
414            _ => todo!("expr={self:#?}"),
415        }
416    }
417
418    fn eval_ref_bool(&self, scope: &ScopeStack<'_>, input: &mut impl Input) -> Result<bool> {
419        match self.eval_ref(scope, input)? {
420            Value::Bool(ret) => Ok(ret),
421            _ => Err(crate::Error::expression_evaluation_failed(
422                "expected boolean value",
423            )),
424        }
425    }
426
427    fn eval_ref_usize(&self, scope: &ScopeStack<'_>, input: &mut impl Input) -> Result<usize> {
428        match self.eval_ref(scope, input)? {
429            Value::I64(n) if n >= 0 => Ok(n as usize),
430            _ => Err(crate::Error::expression_evaluation_failed(
431                "expected non-negative integer",
432            )),
433        }
434    }
435}
436
437impl ScopeStack<'_> {
438    fn resolve_arg(
439        &self,
440        expr_arg: &ExprArg,
441        projection: &Projection,
442        input: &mut impl Input,
443    ) -> Option<Expr> {
444        let mut nesting = expr_arg.nesting;
445        let mut scope = self;
446
447        while nesting > 0 {
448            nesting -= 1;
449
450            scope = match scope {
451                ScopeStack::Root => return None,
452                ScopeStack::Scope { parent, .. } => parent,
453            };
454        }
455
456        match scope {
457            ScopeStack::Root => input.resolve_arg(expr_arg, projection),
458            &ScopeStack::Scope { mut args, .. } => args.resolve_arg(expr_arg, projection),
459        }
460    }
461
462    fn scope<'child>(&'child self, args: &'child [Value]) -> ScopeStack<'child> {
463        ScopeStack::Scope { args, parent: self }
464    }
465}
466
467fn cmp_ordered(lhs: &Value, rhs: &Value) -> Result<Ordering> {
468    if lhs.is_null() || rhs.is_null() {
469        return Err(crate::Error::expression_evaluation_failed(
470            "ordered comparison with NULL is undefined",
471        ));
472    }
473    lhs.partial_cmp(rhs).ok_or_else(|| {
474        crate::Error::expression_evaluation_failed("ordered comparison between incompatible types")
475    })
476}
477
478fn any_all_compare(lhs: &Value, items: &[Value], op: BinaryOp, all: bool) -> Result<bool> {
479    for item in items {
480        let matches = match op {
481            BinaryOp::Eq => lhs == item,
482            BinaryOp::Ne => lhs != item,
483            BinaryOp::Ge => cmp_ordered(lhs, item)? != Ordering::Less,
484            BinaryOp::Gt => cmp_ordered(lhs, item)? == Ordering::Greater,
485            BinaryOp::Le => cmp_ordered(lhs, item)? != Ordering::Greater,
486            BinaryOp::Lt => cmp_ordered(lhs, item)? == Ordering::Less,
487            BinaryOp::Add | BinaryOp::Sub => {
488                return Err(crate::Error::expression_evaluation_failed(
489                    "ANY/ALL only supports comparison operators",
490                ));
491            }
492        };
493        if all {
494            if !matches {
495                return Ok(false);
496            }
497        } else if matches {
498            return Ok(true);
499        }
500    }
501    // ANY over empty list → false; ALL over empty list → true.
502    Ok(all)
503}