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