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::Match(expr_match) => {
350                let subject = expr_match.subject.eval_ref(scope, input)?;
351                for arm in &expr_match.arms {
352                    if subject == arm.pattern {
353                        return arm.expr.eval_ref(scope, input);
354                    }
355                }
356                expr_match.else_expr.eval_ref(scope, input)
357            }
358            Expr::Exists(expr_exists) => {
359                // Evaluate the subquery body. For Values bodies the rows are
360                // evaluated and flattened; for other bodies we evaluate the
361                // query as an expression.
362                match &expr_exists.subquery.body {
363                    ExprSet::Values(values) => {
364                        for row in &values.rows {
365                            let val = row.eval_ref(scope, input)?;
366                            match val {
367                                // An empty list means no rows — keep checking
368                                Value::List(items) if items.is_empty() => {}
369                                // Null means the row doesn't exist
370                                Value::Null => {}
371                                // Any other value means at least one row exists
372                                _ => return Ok(true.into()),
373                            }
374                        }
375                        Ok(false.into())
376                    }
377                    _ => todo!("ExprExists with non-Values body"),
378                }
379            }
380            Expr::Value(value) => Ok(value.clone()),
381            Expr::Func(_) => Err(crate::Error::expression_evaluation_failed(
382                "database functions cannot be evaluated client-side",
383            )),
384            _ => todo!("expr={self:#?}"),
385        }
386    }
387
388    fn eval_ref_bool(&self, scope: &ScopeStack<'_>, input: &mut impl Input) -> Result<bool> {
389        match self.eval_ref(scope, input)? {
390            Value::Bool(ret) => Ok(ret),
391            _ => Err(crate::Error::expression_evaluation_failed(
392                "expected boolean value",
393            )),
394        }
395    }
396
397    fn eval_ref_usize(&self, scope: &ScopeStack<'_>, input: &mut impl Input) -> Result<usize> {
398        match self.eval_ref(scope, input)? {
399            Value::I64(n) if n >= 0 => Ok(n as usize),
400            _ => Err(crate::Error::expression_evaluation_failed(
401                "expected non-negative integer",
402            )),
403        }
404    }
405}
406
407impl ScopeStack<'_> {
408    fn resolve_arg(
409        &self,
410        expr_arg: &ExprArg,
411        projection: &Projection,
412        input: &mut impl Input,
413    ) -> Option<Expr> {
414        let mut nesting = expr_arg.nesting;
415        let mut scope = self;
416
417        while nesting > 0 {
418            nesting -= 1;
419
420            scope = match scope {
421                ScopeStack::Root => return None,
422                ScopeStack::Scope { parent, .. } => parent,
423            };
424        }
425
426        match scope {
427            ScopeStack::Root => input.resolve_arg(expr_arg, projection),
428            &ScopeStack::Scope { mut args, .. } => args.resolve_arg(expr_arg, projection),
429        }
430    }
431
432    fn scope<'child>(&'child self, args: &'child [Value]) -> ScopeStack<'child> {
433        ScopeStack::Scope { args, parent: self }
434    }
435}
436
437fn cmp_ordered(lhs: &Value, rhs: &Value) -> Result<Ordering> {
438    if lhs.is_null() || rhs.is_null() {
439        return Err(crate::Error::expression_evaluation_failed(
440            "ordered comparison with NULL is undefined",
441        ));
442    }
443    lhs.partial_cmp(rhs).ok_or_else(|| {
444        crate::Error::expression_evaluation_failed("ordered comparison between incompatible types")
445    })
446}