toasty_core/stmt/
eval.rs

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