Skip to main content

mz_expr/
scalar.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10use std::collections::BTreeSet;
11use std::ops::BitOrAssign;
12use std::sync::Arc;
13use std::{fmt, mem};
14
15use itertools::Itertools;
16use mz_ore::cast::CastFrom;
17use mz_ore::iter::IteratorExt;
18use mz_ore::soft_assert_or_log;
19use mz_ore::stack::RecursionLimitError;
20use mz_ore::str::StrExt;
21use mz_ore::treat_as_equal::TreatAsEqual;
22use mz_ore::vec::swap_remove_multiple;
23use mz_pgrepr::TypeFromOidError;
24use mz_proto::{IntoRustIfSome, ProtoType, RustType, TryFromProtoError};
25use mz_repr::adt::array::InvalidArrayError;
26use mz_repr::adt::date::DateError;
27use mz_repr::adt::range::InvalidRangeError;
28use mz_repr::adt::regex::RegexCompilationError;
29use mz_repr::adt::timestamp::TimestampError;
30use mz_repr::strconv::{ParseError, ParseHexError};
31use mz_repr::{Datum, ReprColumnType, ReprScalarType, Row, RowArena, SqlColumnType};
32
33#[cfg(any(test, feature = "proptest"))]
34use proptest::prelude::*;
35#[cfg(any(test, feature = "proptest"))]
36use proptest_derive::Arbitrary;
37use serde::{Deserialize, Serialize};
38
39use crate::explain::{HumanizedExplain, HumanizerMode};
40pub use crate::scalar::columns::Columns;
41pub use crate::scalar::eval::Eval;
42use crate::scalar::func::variadic::{And, Or};
43use crate::scalar::func::{BinaryFunc, UnaryFunc, UnmaterializableFunc, VariadicFunc};
44pub use crate::scalar::optimizable::OptimizableExpr;
45use crate::scalar::proto_eval_error::proto_incompatible_array_dimensions::ProtoDims;
46use crate::visit::{Visit, VisitChildren};
47
48pub mod columns;
49pub mod eval;
50pub mod func;
51pub mod like_pattern;
52pub mod optimizable;
53mod reduce;
54
55include!(concat!(env!("OUT_DIR"), "/mz_expr.scalar.rs"));
56
57#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
58pub enum MirScalarExpr {
59    /// A column of the input row
60    Column(usize, TreatAsEqual<Option<Arc<str>>>),
61    /// A literal value.
62    /// (Stored as a row, because we can't own a Datum)
63    Literal(Result<Row, EvalError>, ReprColumnType),
64    /// A call to an unmaterializable function.
65    ///
66    /// These functions cannot be evaluated by `MirScalarExpr::eval`. They must
67    /// be transformed away by a higher layer.
68    CallUnmaterializable(UnmaterializableFunc),
69    /// A function call that takes one expression as an argument.
70    CallUnary {
71        func: UnaryFunc,
72        expr: Box<MirScalarExpr>,
73    },
74    /// A function call that takes two expressions as arguments.
75    CallBinary {
76        func: BinaryFunc,
77        expr1: Box<MirScalarExpr>,
78        expr2: Box<MirScalarExpr>,
79    },
80    /// A function call that takes an arbitrary number of arguments.
81    CallVariadic {
82        func: VariadicFunc,
83        exprs: Vec<MirScalarExpr>,
84    },
85    /// Conditionally evaluated expressions.
86    ///
87    /// It is important that `then` and `els` only be evaluated if
88    /// `cond` is true or not, respectively. This is the only way
89    /// users can guard execution (other logical operator do not
90    /// short-circuit) and we need to preserve that.
91    If {
92        cond: Box<MirScalarExpr>,
93        then: Box<MirScalarExpr>,
94        els: Box<MirScalarExpr>,
95    },
96}
97
98// We need a custom Debug because we don't want to show `None` for name information.
99// Sadly, the `derivative` crate doesn't support this use case.
100impl std::fmt::Debug for MirScalarExpr {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        match self {
103            MirScalarExpr::Column(i, TreatAsEqual(Some(name))) => {
104                write!(f, "Column({i}, {name:?})")
105            }
106            MirScalarExpr::Column(i, TreatAsEqual(None)) => write!(f, "Column({i})"),
107            MirScalarExpr::Literal(lit, typ) => write!(f, "Literal({lit:?}, {typ:?})"),
108            MirScalarExpr::CallUnmaterializable(func) => {
109                write!(f, "CallUnmaterializable({func:?})")
110            }
111            MirScalarExpr::CallUnary { func, expr } => {
112                write!(f, "CallUnary({func:?}, {expr:?})")
113            }
114            MirScalarExpr::CallBinary { func, expr1, expr2 } => {
115                write!(f, "CallBinary({func:?}, {expr1:?}, {expr2:?})")
116            }
117            MirScalarExpr::CallVariadic { func, exprs } => {
118                write!(f, "CallVariadic({func:?}, {exprs:?})")
119            }
120            MirScalarExpr::If { cond, then, els } => {
121                write!(f, "If({cond:?}, {then:?}, {els:?})")
122            }
123        }
124    }
125}
126
127impl MirScalarExpr {
128    pub fn columns(is: &[usize]) -> Vec<MirScalarExpr> {
129        is.iter().map(|i| MirScalarExpr::column(*i)).collect()
130    }
131
132    pub fn column(column: usize) -> Self {
133        MirScalarExpr::Column(column, TreatAsEqual(None))
134    }
135
136    pub fn named_column(column: usize, name: Arc<str>) -> Self {
137        MirScalarExpr::Column(column, TreatAsEqual(Some(name)))
138    }
139
140    pub fn literal(res: Result<Datum, EvalError>, typ: ReprScalarType) -> Self {
141        let typ = ReprColumnType {
142            scalar_type: typ,
143            nullable: matches!(res, Ok(Datum::Null)),
144        };
145        let row = res.map(|datum| Row::pack_slice(&[datum]));
146        MirScalarExpr::Literal(row, typ)
147    }
148
149    pub fn literal_ok(datum: Datum, typ: ReprScalarType) -> Self {
150        MirScalarExpr::literal(Ok(datum), typ)
151    }
152
153    /// Constructs a `MirScalarExpr::Literal` from a pre-packed `Row`
154    /// containing a single datum and a `ReprScalarType`. Nullability is
155    /// derived by inspecting the first datum in the row.
156    pub fn literal_from_single_element_row(row: Row, typ: ReprScalarType) -> Self {
157        soft_assert_or_log!(
158            row.iter().count() == 1,
159            "literal_from_row called with a Row containing {} datums",
160            row.iter().count()
161        );
162        let nullable = row.unpack_first() == Datum::Null;
163        let typ = ReprColumnType {
164            scalar_type: typ,
165            nullable,
166        };
167        MirScalarExpr::Literal(Ok(row), typ)
168    }
169
170    pub fn literal_null(typ: ReprScalarType) -> Self {
171        MirScalarExpr::literal_ok(Datum::Null, typ)
172    }
173
174    pub fn literal_false() -> Self {
175        MirScalarExpr::literal_ok(Datum::False, ReprScalarType::Bool)
176    }
177
178    pub fn literal_true() -> Self {
179        MirScalarExpr::literal_ok(Datum::True, ReprScalarType::Bool)
180    }
181
182    pub fn call_unary<U: Into<UnaryFunc>>(self, func: U) -> Self {
183        MirScalarExpr::CallUnary {
184            func: func.into(),
185            expr: Box::new(self),
186        }
187    }
188
189    pub fn call_binary<B: Into<BinaryFunc>>(self, other: Self, func: B) -> Self {
190        MirScalarExpr::CallBinary {
191            func: func.into(),
192            expr1: Box::new(self),
193            expr2: Box::new(other),
194        }
195    }
196
197    /// Call function `func` on `exprs`.
198    pub fn call_variadic<V: Into<VariadicFunc>>(func: V, exprs: Vec<Self>) -> Self {
199        MirScalarExpr::CallVariadic {
200            func: func.into(),
201            exprs,
202        }
203    }
204
205    pub fn if_then_else(self, t: Self, f: Self) -> Self {
206        MirScalarExpr::If {
207            cond: Box::new(self),
208            then: Box::new(t),
209            els: Box::new(f),
210        }
211    }
212
213    pub fn or(self, other: Self) -> Self {
214        MirScalarExpr::call_variadic(Or, vec![self, other])
215    }
216
217    pub fn and(self, other: Self) -> Self {
218        MirScalarExpr::call_variadic(And, vec![self, other])
219    }
220
221    pub fn not(self) -> Self {
222        self.call_unary(UnaryFunc::Not(func::Not))
223    }
224
225    pub fn call_is_null(self) -> Self {
226        self.call_unary(UnaryFunc::IsNull(func::IsNull))
227    }
228
229    /// Match AND or OR on self and get the args. If no match, then interpret self as if it were
230    /// wrapped in a 1-arg AND/OR.
231    pub fn and_or_args(&self, func_to_match: VariadicFunc) -> Vec<MirScalarExpr> {
232        assert!(func_to_match == Or.into() || func_to_match == And.into());
233        match self {
234            MirScalarExpr::CallVariadic { func, exprs } if *func == func_to_match => exprs.clone(),
235            _ => vec![self.clone()],
236        }
237    }
238
239    /// Try to match a literal equality involving the given expression on one side.
240    /// Return the (non-null) literal and a bool that indicates whether an inversion was needed.
241    ///
242    /// More specifically:
243    /// If `self` is an equality with a `null` literal on any side, then the match fails!
244    /// Otherwise: for a given `expr`, if `self` is `<expr> = <literal>` or `<literal> = <expr>`
245    /// then return `Some((<literal>, false))`. In addition to just trying to match `<expr>` as it
246    /// is, we also try to remove an invertible function call (such as a cast). If the match
247    /// succeeds with the inversion, then return `Some((<inverted-literal>, true))`. For more
248    /// details on the inversion, see `invert_casts_on_expr_eq_literal_inner`.
249    pub fn expr_eq_literal(&self, expr: &MirScalarExpr) -> Option<(Row, bool)> {
250        if let MirScalarExpr::CallBinary {
251            func: BinaryFunc::Eq(_),
252            expr1,
253            expr2,
254        } = self
255        {
256            if expr1.is_literal_null() || expr2.is_literal_null() {
257                return None;
258            }
259            if let Some(Ok(lit)) = expr1.as_literal_owned() {
260                return Self::expr_eq_literal_inner(expr, lit, expr1, expr2);
261            }
262            if let Some(Ok(lit)) = expr2.as_literal_owned() {
263                return Self::expr_eq_literal_inner(expr, lit, expr2, expr1);
264            }
265        }
266        None
267    }
268
269    fn expr_eq_literal_inner(
270        expr_to_match: &MirScalarExpr,
271        literal: Row,
272        literal_expr: &MirScalarExpr,
273        other_side: &MirScalarExpr,
274    ) -> Option<(Row, bool)> {
275        if other_side == expr_to_match {
276            return Some((literal, false));
277        } else {
278            // expr didn't exactly match. See if we can match it by inverse-casting.
279            let (cast_removed, inv_cast_lit) =
280                Self::invert_casts_on_expr_eq_literal_inner(other_side, literal_expr);
281            if &cast_removed == expr_to_match {
282                if let Some(Ok(inv_cast_lit_row)) = inv_cast_lit.as_literal_owned() {
283                    return Some((inv_cast_lit_row, true));
284                }
285            }
286        }
287        None
288    }
289
290    /// If `self` is `<expr> = <literal>` or `<literal> = <expr>` then
291    /// return `<expr>`. It also tries to remove a cast (or other invertible function call) from
292    /// `<expr>` before returning it, see `invert_casts_on_expr_eq_literal_inner`.
293    pub fn any_expr_eq_literal(&self) -> Option<MirScalarExpr> {
294        if let MirScalarExpr::CallBinary {
295            func: BinaryFunc::Eq(_),
296            expr1,
297            expr2,
298        } = self
299        {
300            if expr1.is_literal() {
301                let (expr, _literal) = Self::invert_casts_on_expr_eq_literal_inner(expr2, expr1);
302                return Some(expr);
303            }
304            if expr2.is_literal() {
305                let (expr, _literal) = Self::invert_casts_on_expr_eq_literal_inner(expr1, expr2);
306                return Some(expr);
307            }
308        }
309        None
310    }
311
312    /// If the given `MirScalarExpr` is a literal equality where one side is an invertible function
313    /// call, then calls the inverse function on both sides of the equality and returns the modified
314    /// version of the given `MirScalarExpr`. Otherwise, it returns the original expression.
315    /// For more details, see `invert_casts_on_expr_eq_literal_inner`.
316    pub fn invert_casts_on_expr_eq_literal(&self) -> MirScalarExpr {
317        if let MirScalarExpr::CallBinary {
318            func: BinaryFunc::Eq(_),
319            expr1,
320            expr2,
321        } = self
322        {
323            if expr1.is_literal() {
324                let (expr, literal) = Self::invert_casts_on_expr_eq_literal_inner(expr2, expr1);
325                return literal.call_binary(expr, func::Eq);
326            }
327            if expr2.is_literal() {
328                let (expr, literal) = Self::invert_casts_on_expr_eq_literal_inner(expr1, expr2);
329                return literal.call_binary(expr, func::Eq);
330            }
331            // Note: The above return statements should be consistent in whether they put the
332            // literal in expr1 or expr2, for the deduplication in CanonicalizeMfp to work.
333        }
334        self.clone()
335    }
336
337    /// Given an `<expr>` and a `<literal>` that were taken out from `<expr> = <literal>` or
338    /// `<literal> = <expr>`, it tries to simplify the equality by applying the inverse function of
339    /// the outermost function call of `<expr>` (if exists):
340    ///
341    /// `<literal> = func(<inner_expr>)`, where `func` is invertible
342    ///  -->
343    /// `<func^-1(literal)> = <inner_expr>`
344    /// if `func^-1(literal)` doesn't error out, and both `func` and `func^-1` preserve uniqueness.
345    ///
346    /// The return value is the `<inner_expr>` and the literal value that we get by applying the
347    /// inverse function.
348    fn invert_casts_on_expr_eq_literal_inner(
349        expr: &MirScalarExpr,
350        literal: &MirScalarExpr,
351    ) -> (MirScalarExpr, MirScalarExpr) {
352        assert!(matches!(literal, MirScalarExpr::Literal(..)));
353
354        let temp_storage = &RowArena::new();
355        let eval = |e: &MirScalarExpr| {
356            MirScalarExpr::literal(e.eval(&[], temp_storage), e.typ(&[]).scalar_type)
357        };
358
359        if let MirScalarExpr::CallUnary {
360            func,
361            expr: inner_expr,
362        } = expr
363        {
364            if let Some(inverse_func) = func.inverse() {
365                // We don't want to remove a function call that doesn't preserve uniqueness, e.g.,
366                // if `f` is a float, we don't want to inverse-cast `f::INT = 0`, because the
367                // inserted int-to-float cast wouldn't be able to invert the rounding.
368                // Also, we don't want to insert a function call that doesn't preserve
369                // uniqueness. E.g., if `a` has an integer type, we don't want to do
370                // a surprise rounding for `WHERE a = 3.14`.
371                if func.preserves_uniqueness() && inverse_func.preserves_uniqueness() {
372                    let lit_inv = eval(&MirScalarExpr::CallUnary {
373                        func: inverse_func,
374                        expr: Box::new(literal.clone()),
375                    });
376                    // The evaluation can error out, e.g., when casting a too large int32 to int16.
377                    // This case is handled by `impossible_literal_equality_because_types`.
378                    if !lit_inv.is_literal_err() {
379                        return (*inner_expr.clone(), lit_inv);
380                    }
381                }
382            }
383        }
384        (expr.clone(), literal.clone())
385    }
386
387    /// Tries to remove a cast (or other invertible function) in the same way as
388    /// `invert_casts_on_expr_eq_literal`, but if calling the inverse function fails on the literal,
389    /// then it deems the equality to be impossible. For example if `a` is a smallint column, then
390    /// it catches `a::integer = 1000000` to be an always false predicate (where the `::integer`
391    /// could have been inserted implicitly).
392    pub fn impossible_literal_equality_because_types(&self) -> bool {
393        if let MirScalarExpr::CallBinary {
394            func: BinaryFunc::Eq(_),
395            expr1,
396            expr2,
397        } = self
398        {
399            if expr1.is_literal() {
400                return Self::impossible_literal_equality_because_types_inner(expr1, expr2);
401            }
402            if expr2.is_literal() {
403                return Self::impossible_literal_equality_because_types_inner(expr2, expr1);
404            }
405        }
406        false
407    }
408
409    fn impossible_literal_equality_because_types_inner(
410        literal: &MirScalarExpr,
411        other_side: &MirScalarExpr,
412    ) -> bool {
413        assert!(matches!(literal, MirScalarExpr::Literal(..)));
414
415        let temp_storage = &RowArena::new();
416        let eval = |e: &MirScalarExpr| {
417            MirScalarExpr::literal(e.eval(&[], temp_storage), e.typ(&[]).scalar_type)
418        };
419
420        if let MirScalarExpr::CallUnary { func, .. } = other_side {
421            if let Some(inverse_func) = func.inverse() {
422                if inverse_func.preserves_uniqueness()
423                    && eval(&MirScalarExpr::CallUnary {
424                        func: inverse_func,
425                        expr: Box::new(literal.clone()),
426                    })
427                    .is_literal_err()
428                {
429                    return true;
430                }
431            }
432        }
433
434        false
435    }
436
437    /// Determines if `self` is
438    /// `<expr> < <literal>` or
439    /// `<expr> > <literal>` or
440    /// `<literal> < <expr>` or
441    /// `<literal> > <expr>` or
442    /// `<expr> <= <literal>` or
443    /// `<expr> >= <literal>` or
444    /// `<literal> <= <expr>` or
445    /// `<literal> >= <expr>`.
446    pub fn any_expr_ineq_literal(&self) -> bool {
447        match self {
448            MirScalarExpr::CallBinary {
449                func:
450                    BinaryFunc::Lt(_) | BinaryFunc::Lte(_) | BinaryFunc::Gt(_) | BinaryFunc::Gte(_),
451                expr1,
452                expr2,
453            } => expr1.is_literal() || expr2.is_literal(),
454            _ => false,
455        }
456    }
457
458    pub fn take(&mut self) -> Self {
459        mem::replace(self, MirScalarExpr::literal_null(ReprScalarType::String))
460    }
461
462    /// If the expression is a literal, this returns the literal's Datum or the literal's EvalError.
463    /// Otherwise, it returns None.
464    pub fn as_literal(&self) -> Option<Result<Datum<'_>, &EvalError>> {
465        if let MirScalarExpr::Literal(lit, _column_type) = self {
466            Some(lit.as_ref().map(|row| row.unpack_first()))
467        } else {
468            None
469        }
470    }
471
472    /// Flattens the two failure modes of `as_literal` into one layer of Option: returns the
473    /// literal's Datum only if the expression is a literal, and it's not a literal error.
474    pub fn as_literal_non_error(&self) -> Option<Datum<'_>> {
475        self.as_literal().map(|eval_err| eval_err.ok()).flatten()
476    }
477
478    pub fn as_literal_owned(&self) -> Option<Result<Row, EvalError>> {
479        if let MirScalarExpr::Literal(lit, _column_type) = self {
480            Some(lit.clone())
481        } else {
482            None
483        }
484    }
485
486    /// Returns a reference to the `Row` if the expression is a non-NULL `Ok` literal.
487    pub fn as_literal_non_null_row(&self) -> Option<&Row> {
488        if let MirScalarExpr::Literal(Ok(row), _) = self {
489            if !row.unpack_first().is_null() {
490                return Some(row);
491            }
492        }
493        None
494    }
495
496    pub fn as_literal_str(&self) -> Option<&str> {
497        match self.as_literal() {
498            Some(Ok(Datum::String(s))) => Some(s),
499            _ => None,
500        }
501    }
502
503    pub fn as_literal_int64(&self) -> Option<i64> {
504        match self.as_literal() {
505            Some(Ok(Datum::Int64(i))) => Some(i),
506            _ => None,
507        }
508    }
509
510    pub fn as_literal_err(&self) -> Option<&EvalError> {
511        self.as_literal().and_then(|lit| lit.err())
512    }
513
514    pub fn is_literal(&self) -> bool {
515        matches!(self, MirScalarExpr::Literal(_, _))
516    }
517
518    pub fn is_literal_true(&self) -> bool {
519        Some(Ok(Datum::True)) == self.as_literal()
520    }
521
522    pub fn is_literal_false(&self) -> bool {
523        Some(Ok(Datum::False)) == self.as_literal()
524    }
525
526    pub fn is_literal_null(&self) -> bool {
527        Some(Ok(Datum::Null)) == self.as_literal()
528    }
529
530    pub fn is_literal_ok(&self) -> bool {
531        matches!(self, MirScalarExpr::Literal(Ok(_), _typ))
532    }
533
534    pub fn is_literal_err(&self) -> bool {
535        matches!(self, MirScalarExpr::Literal(Err(_), _typ))
536    }
537
538    pub fn is_error_if_null(&self) -> bool {
539        matches!(
540            self,
541            Self::CallVariadic {
542                func: VariadicFunc::ErrorIfNull(_),
543                ..
544            }
545        )
546    }
547
548    /// If `self` expresses a temporal filter, normalize it to start with `mz_now()` and return
549    /// references.
550    ///
551    /// A temporal filter is an expression of the form `mz_now() <BINOP> <EXPR>`,
552    /// for a restricted set of `BINOP` and `EXPR` that do not themselves contain `mz_now()`.
553    /// Expressions may conform to this once their expressions are swapped.
554    ///
555    /// If the expression is not a temporal filter, it will be unchanged, and the reason for why
556    /// it's not a temporal filter is returned as a string.
557    pub fn as_mut_temporal_filter(&mut self) -> Result<(&BinaryFunc, &mut MirScalarExpr), String> {
558        if !self.contains_temporal() {
559            return Err("Does not involve mz_now()".to_string());
560        }
561        // Supported temporal predicates are exclusively binary operators.
562        if let MirScalarExpr::CallBinary { func, expr1, expr2 } = self {
563            // Attempt to put `LogicalTimestamp` in the first argument position.
564            if !expr1.contains_temporal()
565                && **expr2 == MirScalarExpr::CallUnmaterializable(UnmaterializableFunc::MzNow)
566            {
567                let new_func = match func {
568                    BinaryFunc::Eq(_) => func::Eq.into(),
569                    BinaryFunc::Lt(_) => func::Gt.into(),
570                    BinaryFunc::Lte(_) => func::Gte.into(),
571                    BinaryFunc::Gt(_) => func::Lt.into(),
572                    BinaryFunc::Gte(_) => func::Lte.into(),
573                    x => {
574                        return Err(format!("Unsupported binary temporal operation: {:?}", x));
575                    }
576                };
577                std::mem::swap(expr1, expr2);
578                *func = new_func;
579            }
580
581            // Error if MLT is referenced in an unsupported position.
582            if expr2.contains_temporal()
583                || **expr1 != MirScalarExpr::CallUnmaterializable(UnmaterializableFunc::MzNow)
584            {
585                let mode = HumanizedExplain::new(false); // no redaction
586                let bad_expr = MirScalarExpr::CallBinary {
587                    func: func.clone(),
588                    expr1: expr1.clone(),
589                    expr2: expr2.clone(),
590                };
591                return Err(format!(
592                    "Unsupported temporal predicate. Note: `mz_now()` must be directly compared to a mz_timestamp-castable expression. Expression found: {}",
593                    mode.expr(&bad_expr, None),
594                ));
595            }
596
597            Ok((&*func, expr2))
598        } else {
599            let mode = HumanizedExplain::new(false); // no redaction
600            Err(format!(
601                "Unsupported temporal predicate. Note: `mz_now()` must be directly compared to a non-temporal expression of mz_timestamp-castable type. Expression found: {}",
602                mode.expr(self, None),
603            ))
604        }
605    }
606
607    #[deprecated = "Use `might_error` instead"]
608    pub fn contains_error_if_null(&self) -> bool {
609        let mut worklist = vec![self];
610        while let Some(expr) = worklist.pop() {
611            if expr.is_error_if_null() {
612                return true;
613            }
614            worklist.extend(expr.children());
615        }
616        false
617    }
618
619    pub fn contains_err(&self) -> bool {
620        let mut worklist = vec![self];
621        while let Some(expr) = worklist.pop() {
622            if expr.is_literal_err() {
623                return true;
624            }
625            worklist.extend(expr.children());
626        }
627        false
628    }
629
630    /// A very crude approximation for scalar expressions that might produce an
631    /// error.
632    ///
633    /// Currently, this is restricted only to expressions that either contain a
634    /// literal error or a [`VariadicFunc::ErrorIfNull`] call.
635    pub fn might_error(&self) -> bool {
636        let mut worklist = vec![self];
637        while let Some(expr) = worklist.pop() {
638            if expr.is_literal_err() || expr.is_error_if_null() {
639                return true;
640            }
641            worklist.extend(expr.children());
642        }
643        false
644    }
645
646    /// Reduces a complex expression where possible.
647    ///
648    /// This function uses nullability information present in `column_types`,
649    /// and the result may only continue to be a correct transformation as
650    /// long as this information continues to hold (nullability may not hold
651    /// as expressions migrate around).
652    ///
653    /// (If you'd like to not use nullability information here, then you can
654    /// tweak the nullabilities in `column_types` before passing it to this
655    /// function, see e.g. in `EquivalenceClasses::minimize`.)
656    ///
657    /// Also performs partial canonicalization on the expression.
658    ///
659    /// ```rust
660    /// use mz_expr::MirScalarExpr;
661    /// use mz_repr::{ReprColumnType, Datum, SqlScalarType};
662    ///
663    /// let expr_0 = MirScalarExpr::column(0);
664    /// let expr_t = MirScalarExpr::literal_true();
665    /// let expr_f = MirScalarExpr::literal_false();
666    ///
667    /// let mut test =
668    /// expr_t
669    ///     .clone()
670    ///     .and(expr_f.clone())
671    ///     .if_then_else(expr_0, expr_t.clone());
672    ///
673    /// let input_type = vec![ReprColumnType::from(&SqlScalarType::Int32.nullable(false))];
674    /// test.reduce(&input_type);
675    /// assert_eq!(test, expr_t);
676    /// ```
677    /// Reduce the expression to a simpler form.
678    pub fn reduce(&mut self, column_types: &[ReprColumnType]) {
679        reduce::reduce(self, column_types);
680    }
681
682    /// Decompose an IsNull expression into a disjunction of
683    /// simpler expressions.
684    ///
685    /// Assumes that `self` is the expression inside of an IsNull.
686    /// Returns `Some(expressions)` if the outer IsNull is to be
687    /// replaced by some other expression. Note: if it returns
688    /// None, it might still have mutated *self.
689    fn decompose_is_null(&mut self) -> Option<MirScalarExpr> {
690        // TODO: allow simplification of unmaterializable functions
691
692        match self {
693            MirScalarExpr::CallUnary {
694                func,
695                expr: inner_expr,
696            } => {
697                if !func.introduces_nulls() {
698                    if func.propagates_nulls() {
699                        *self = inner_expr.take();
700                        return self.decompose_is_null();
701                    } else {
702                        // We can simplify to `false`, because the function simply can't produce
703                        // nulls at all. This is because
704                        // - !propagates_nulls means that the input type of the Rust function is not
705                        //   nullable, so the automatic null propagation won't kick in;
706                        // - !introduces_nulls means that the output type of the Rust function is
707                        //   not nullable, so the Rust function can't produce a null manually either.
708                        //
709                        // Note that we can't do this same optimization for binary and variadic
710                        // functions. This is because for binary and variadic functions the value of
711                        // propagates_nulls and introduces_nulls is not derived solely from the
712                        // input/output type nullabilities, but instead depends on what the Rust
713                        // function does. For example, list concatenation neither introduces nor
714                        // propagates nulls, but it can produce a null:
715                        // - It does not introduce nulls, because if both input lists are not null,
716                        //   then it will produce a list.
717                        // - It does not propagate nulls, because giving a null as just one of the
718                        //   arguments returns the other argument instead of null.
719                        // - It does produce a null if both arguments are null.
720                        return Some(MirScalarExpr::literal_false());
721                    }
722                }
723            }
724            MirScalarExpr::CallBinary { func, expr1, expr2 } => {
725                // (<expr1> <op> <expr2>) IS NULL can often be simplified to
726                // (<expr1> IS NULL) OR (<expr2> IS NULL).
727                if func.propagates_nulls() && !func.introduces_nulls() {
728                    let expr1 = expr1.take().call_is_null();
729                    let expr2 = expr2.take().call_is_null();
730                    return Some(expr1.or(expr2));
731                }
732            }
733            MirScalarExpr::CallVariadic { func, exprs } => {
734                if func.propagates_nulls() && !func.introduces_nulls() {
735                    let exprs = exprs.into_iter().map(|e| e.take().call_is_null()).collect();
736                    return Some(MirScalarExpr::call_variadic(Or, exprs));
737                }
738            }
739            _ => {}
740        }
741
742        None
743    }
744
745    /// Flattens a chain of calls to associative variadic functions
746    /// (For example: ORs or ANDs)
747    pub fn flatten_associative(&mut self) {
748        match self {
749            MirScalarExpr::CallVariadic {
750                exprs: outer_operands,
751                func: outer_func,
752            } if outer_func.is_associative() => {
753                *outer_operands = outer_operands
754                    .into_iter()
755                    .flat_map(|o| {
756                        if let MirScalarExpr::CallVariadic {
757                            exprs: inner_operands,
758                            func: inner_func,
759                        } = o
760                        {
761                            if *inner_func == *outer_func {
762                                mem::take(inner_operands)
763                            } else {
764                                vec![o.take()]
765                            }
766                        } else {
767                            vec![o.take()]
768                        }
769                    })
770                    .collect();
771            }
772            _ => {}
773        }
774    }
775
776    /* #region AND/OR canonicalization and transformations  */
777
778    /// Canonicalizes AND/OR, and does some straightforward simplifications
779    fn reduce_and_canonicalize_and_or(&mut self) {
780        // We do this until fixed point, because after undistribute_and_or calls us, it relies on
781        // the property that self is not an 1-arg AND/OR. Just one application of our loop body
782        // can't ensure this, because the application itself might create a 1-arg AND/OR.
783        let mut old_self = MirScalarExpr::column(0);
784        while old_self != *self {
785            old_self = self.clone();
786            match self {
787                MirScalarExpr::CallVariadic {
788                    func: func @ (VariadicFunc::And(_) | VariadicFunc::Or(_)),
789                    exprs,
790                } => {
791                    // Canonically order elements so that various deduplications work better,
792                    // e.g., in undistribute_and_or.
793                    // Also, extract_equal_or_both_null_inner depends on the args being sorted.
794                    exprs.sort();
795
796                    // x AND/OR x --> x
797                    exprs.dedup(); // this also needs the above sorting
798
799                    if exprs.len() == 1 {
800                        // AND/OR of 1 argument evaluates to that argument
801                        *self = exprs.swap_remove(0);
802                    } else if exprs.len() == 0 {
803                        // AND/OR of 0 arguments evaluates to true/false
804                        *self = func.unit_of_and_or();
805                    } else if exprs.iter().any(|e| *e == func.zero_of_and_or()) {
806                        // short-circuiting
807                        *self = func.zero_of_and_or();
808                    } else {
809                        // a AND true --> a
810                        // a OR false --> a
811                        exprs.retain(|e| *e != func.unit_of_and_or());
812                    }
813                }
814                _ => {}
815            }
816        }
817    }
818
819    /// Transforms !(a && b) into !a || !b, and !(a || b) into !a && !b
820    fn demorgans(&mut self) {
821        if let MirScalarExpr::CallUnary {
822            expr: inner,
823            func: UnaryFunc::Not(func::Not),
824        } = self
825        {
826            inner.flatten_associative();
827            match &mut **inner {
828                MirScalarExpr::CallVariadic {
829                    func: inner_func @ (VariadicFunc::And(_) | VariadicFunc::Or(_)),
830                    exprs,
831                } => {
832                    *inner_func = inner_func.switch_and_or();
833                    *exprs = exprs.into_iter().map(|e| e.take().not()).collect();
834                    *self = (*inner).take(); // Removes the outer not
835                }
836                _ => {}
837            }
838        }
839    }
840
841    /// AND/OR undistribution (factoring out) to apply at each `MirScalarExpr`.
842    ///
843    /// This method attempts to apply one of the [distribution laws][distributivity]
844    /// (in a direction opposite to the their name):
845    /// ```text
846    /// (a && b) || (a && c) --> a && (b || c)  // Undistribute-OR
847    /// (a || b) && (a || c) --> a || (b && c)  // Undistribute-AND
848    /// ```
849    /// or one of their corresponding two [absorption law][absorption] special
850    /// cases:
851    /// ```text
852    /// a || (a && c)  -->  a  // Absorb-OR
853    /// a && (a || c)  -->  a  // Absorb-AND
854    /// ```
855    ///
856    /// The method also works with more than 2 arguments at the top, e.g.
857    /// ```text
858    /// (a && b) || (a && c) || (a && d)  -->  a && (b || c || d)
859    /// ```
860    /// It can also factor out only a subset of the top arguments, e.g.
861    /// ```text
862    /// (a && b) || (a && c) || (d && e)  -->  (a && (b || c)) || (d && e)
863    /// ```
864    ///
865    /// Note that sometimes there are two overlapping possibilities to factor
866    /// out from, e.g.
867    /// ```text
868    /// (a && b) || (a && c) || (d && c)
869    /// ```
870    /// Here we can factor out `a` from from the 1. and 2. terms, or we can
871    /// factor out `c` from the 2. and 3. terms. One of these might lead to
872    /// more/better undistribution opportunities later, but we just pick one
873    /// locally, because recursively trying out all of them would lead to
874    /// exponential run time.
875    ///
876    /// The local heuristic is that we prefer a candidate that leads to an
877    /// absorption, or if there is no such one then we simply pick the first. In
878    /// case of multiple absorption candidates, it doesn't matter which one we
879    /// pick, because applying an absorption cannot adversely effect the
880    /// possibility of applying other absorptions.
881    ///
882    /// # Assumption
883    ///
884    /// Assumes that nested chains of AND/OR applications are flattened (this
885    /// can be enforced with [`Self::flatten_associative`]).
886    ///
887    /// # Examples
888    ///
889    /// Absorb-OR:
890    /// ```text
891    /// a || (a && c) || (a && d)
892    /// -->
893    /// a && (true || c || d)
894    /// -->
895    /// a && true
896    /// -->
897    /// a
898    /// ```
899    /// Here only the first step is performed by this method. The rest is done
900    /// by [`Self::reduce_and_canonicalize_and_or`] called after us in
901    /// `reduce()`.
902    ///
903    /// [distributivity]: https://en.wikipedia.org/wiki/Distributive_property
904    /// [absorption]: https://en.wikipedia.org/wiki/Absorption_law
905    fn undistribute_and_or(&mut self) {
906        // It wouldn't be strictly necessary to wrap this fn in this loop, because `reduce()` calls
907        // us in a loop anyway. However, `reduce()` tries to do many other things, so the loop here
908        // improves performance when there are several undistributions to apply in sequence, which
909        // can occur in `CanonicalizeMfp` when undoing the DNF.
910        let mut old_self = MirScalarExpr::column(0);
911        while old_self != *self {
912            old_self = self.clone();
913            self.reduce_and_canonicalize_and_or(); // We don't want to deal with 1-arg AND/OR at the top
914            if let MirScalarExpr::CallVariadic {
915                exprs: outer_operands,
916                func: outer_func @ (VariadicFunc::Or(_) | VariadicFunc::And(_)),
917            } = self
918            {
919                let inner_func = outer_func.switch_and_or();
920
921                // Make sure that each outer operand is a call to inner_func, by wrapping in a 1-arg
922                // call if necessary.
923                outer_operands.iter_mut().for_each(|o| {
924                    if !matches!(o, MirScalarExpr::CallVariadic {func: f, ..} if *f == inner_func) {
925                        *o = MirScalarExpr::CallVariadic {
926                            func: inner_func.clone(),
927                            exprs: vec![o.take()],
928                        };
929                    }
930                });
931
932                let mut inner_operands_refs: Vec<&mut Vec<MirScalarExpr>> = outer_operands
933                    .iter_mut()
934                    .map(|o| match o {
935                        MirScalarExpr::CallVariadic { func: f, exprs } if *f == inner_func => exprs,
936                        _ => unreachable!(), // the wrapping made sure that we'll get a match
937                    })
938                    .collect();
939
940                // Find inner operands to undistribute, i.e., which are in _all_ of the outer operands.
941                let mut intersection = inner_operands_refs
942                    .iter()
943                    .map(|v| (*v).clone())
944                    .reduce(|ops1, ops2| ops1.into_iter().filter(|e| ops2.contains(e)).collect())
945                    .unwrap();
946                intersection.sort();
947                intersection.dedup();
948
949                if !intersection.is_empty() {
950                    // Factor out the intersection from all the top-level args.
951
952                    // Remove the intersection from each inner operand vector.
953                    inner_operands_refs
954                        .iter_mut()
955                        .for_each(|ops| (**ops).retain(|o| !intersection.contains(o)));
956
957                    // Simplify terms that now have only 0 or 1 args due to removing the intersection.
958                    outer_operands
959                        .iter_mut()
960                        .for_each(|o| o.reduce_and_canonicalize_and_or());
961
962                    // Add the intersection at the beginning
963                    *self = MirScalarExpr::CallVariadic {
964                        func: inner_func,
965                        exprs: intersection.into_iter().chain_one(self.clone()).collect(),
966                    };
967                } else {
968                    // If the intersection was empty, that means that there is nothing we can factor out
969                    // from _all_ the top-level args. However, we might still find something to factor
970                    // out from a subset of the top-level args. To find such an opportunity, we look for
971                    // duplicates across all inner args, e.g. if we have
972                    // `(...) OR (... AND `a` AND ...) OR (...) OR (... AND `a` AND ...)`
973                    // then we'll find that `a` occurs in more than one top-level arg, so
974                    // `indexes_to_undistribute` will point us to the 2. and 4. top-level args.
975
976                    // Create (inner_operand, index) pairs, where the index is the position in
977                    // outer_operands
978                    let all_inner_operands = inner_operands_refs
979                        .iter()
980                        .enumerate()
981                        .flat_map(|(i, inner_vec)| inner_vec.iter().map(move |a| ((*a).clone(), i)))
982                        .sorted()
983                        .collect_vec();
984
985                    // Find inner operand expressions that occur in more than one top-level arg.
986                    // Each inner vector in `undistribution_opportunities` will belong to one such inner
987                    // operand expression, and it is a set of indexes pointing to top-level args where
988                    // that inner operand occurs.
989                    let undistribution_opportunities = all_inner_operands
990                        .iter()
991                        .chunk_by(|(a, _i)| a)
992                        .into_iter()
993                        .map(|(_a, g)| g.map(|(_a, i)| *i).sorted().dedup().collect_vec())
994                        .filter(|g| g.len() > 1)
995                        .collect_vec();
996
997                    // Choose one of the inner vectors from `undistribution_opportunities`.
998                    let indexes_to_undistribute = undistribution_opportunities
999                        .iter()
1000                        // Let's prefer index sets that directly lead to an absorption.
1001                        .find(|index_set| {
1002                            index_set
1003                                .iter()
1004                                .any(|i| inner_operands_refs.get(*i).unwrap().len() == 1)
1005                        })
1006                        // If we didn't find any absorption, then any index set will do.
1007                        .or_else(|| undistribution_opportunities.first())
1008                        .cloned();
1009
1010                    // In any case, undo the 1-arg wrapping that we did at the beginning.
1011                    outer_operands
1012                        .iter_mut()
1013                        .for_each(|o| o.reduce_and_canonicalize_and_or());
1014
1015                    if let Some(indexes_to_undistribute) = indexes_to_undistribute {
1016                        // Found something to undistribute from a subset of the outer operands.
1017                        // We temporarily remove these from outer_operands, call ourselves on it, and
1018                        // then push back the result.
1019                        let mut undistribute_from = MirScalarExpr::CallVariadic {
1020                            func: outer_func.clone(),
1021                            exprs: swap_remove_multiple(outer_operands, indexes_to_undistribute),
1022                        };
1023                        // By construction, the recursive call is guaranteed to hit
1024                        // the `!intersection.is_empty()` branch.
1025                        undistribute_from.undistribute_and_or();
1026                        // Append the undistributed result to outer operands that were not included in
1027                        // indexes_to_undistribute.
1028                        outer_operands.push(undistribute_from);
1029                    }
1030                }
1031            }
1032        }
1033    }
1034
1035    /* #endregion */
1036
1037    /// Adds any columns that *must* be non-Null for `self` to be non-Null.
1038    pub fn non_null_requirements(&self, columns: &mut BTreeSet<usize>) {
1039        match self {
1040            MirScalarExpr::Column(col, _name) => {
1041                columns.insert(*col);
1042            }
1043            MirScalarExpr::Literal(..) => {}
1044            MirScalarExpr::CallUnmaterializable(_) => (),
1045            MirScalarExpr::CallUnary { func, expr } => {
1046                if func.propagates_nulls() {
1047                    expr.non_null_requirements(columns);
1048                }
1049            }
1050            MirScalarExpr::CallBinary { func, expr1, expr2 } => {
1051                if func.propagates_nulls() {
1052                    expr1.non_null_requirements(columns);
1053                    expr2.non_null_requirements(columns);
1054                }
1055            }
1056            MirScalarExpr::CallVariadic { func, exprs } => {
1057                if func.propagates_nulls() {
1058                    for expr in exprs {
1059                        expr.non_null_requirements(columns);
1060                    }
1061                }
1062            }
1063            MirScalarExpr::If { .. } => (),
1064        }
1065    }
1066
1067    pub fn sql_typ(&self, column_types: &[SqlColumnType]) -> SqlColumnType {
1068        let repr_column_types = column_types.iter().map(ReprColumnType::from).collect_vec();
1069        SqlColumnType::from_repr(&self.typ(&repr_column_types))
1070    }
1071
1072    pub fn typ(&self, column_types: &[ReprColumnType]) -> ReprColumnType {
1073        match self {
1074            MirScalarExpr::Column(i, _name) => column_types[*i].clone(),
1075            MirScalarExpr::Literal(_, typ) => typ.clone(),
1076            MirScalarExpr::CallUnmaterializable(func) => func.output_type(),
1077            MirScalarExpr::CallUnary { expr, func } => func.output_type(expr.typ(column_types)),
1078            MirScalarExpr::CallBinary { expr1, expr2, func } => {
1079                func.output_type(&[expr1.typ(column_types), expr2.typ(column_types)])
1080            }
1081            MirScalarExpr::CallVariadic { exprs, func } => {
1082                func.output_type(exprs.iter().map(|e| e.typ(column_types)).collect())
1083            }
1084            MirScalarExpr::If { cond: _, then, els } => {
1085                let then_type = then.typ(column_types);
1086                let else_type = els.typ(column_types);
1087                then_type.union(&else_type).unwrap()
1088            }
1089        }
1090    }
1091
1092    /// True iff the expression contains
1093    /// `UnmaterializableFunc::MzNow`.
1094    pub fn contains_temporal(&self) -> bool {
1095        let mut contains = false;
1096        self.visit_pre(|e| {
1097            if let MirScalarExpr::CallUnmaterializable(UnmaterializableFunc::MzNow) = e {
1098                contains = true;
1099            }
1100        });
1101        contains
1102    }
1103
1104    /// True iff the expression contains an `UnmaterializableFunc`.
1105    pub fn contains_unmaterializable(&self) -> bool {
1106        let mut contains = false;
1107        self.visit_pre(|e| {
1108            if let MirScalarExpr::CallUnmaterializable(_) = e {
1109                contains = true;
1110            }
1111        });
1112        contains
1113    }
1114
1115    /// True iff the expression contains a call that constant folding must not
1116    /// evaluate: an `UnmaterializableFunc`, or `jq`, whose programs can loop or
1117    /// recurse without bound and would hang or abort the optimizing process.
1118    pub fn contains_unfoldable(&self) -> bool {
1119        let mut contains = false;
1120        self.visit_pre(|e| match e {
1121            MirScalarExpr::CallUnmaterializable(_)
1122            | MirScalarExpr::CallUnary {
1123                func: UnaryFunc::Jq(_),
1124                ..
1125            } => contains = true,
1126            _ => {}
1127        });
1128        contains
1129    }
1130
1131    /// True iff the expression contains an `UnmaterializableFunc` that is not in the `exceptions`
1132    /// list.
1133    pub fn contains_unmaterializable_except(&self, exceptions: &[UnmaterializableFunc]) -> bool {
1134        let mut contains = false;
1135        self.visit_pre(|e| match e {
1136            MirScalarExpr::CallUnmaterializable(f) if !exceptions.contains(f) => contains = true,
1137            _ => (),
1138        });
1139        contains
1140    }
1141
1142    /// True iff the expression contains a `Column`.
1143    pub fn contains_column(&self) -> bool {
1144        let mut contains = false;
1145        self.visit_pre(|e| {
1146            if let MirScalarExpr::Column(_col, _name) = e {
1147                contains = true;
1148            }
1149        });
1150        contains
1151    }
1152
1153    /// True iff the expression contains a `Dummy`.
1154    pub fn contains_dummy(&self) -> bool {
1155        let mut contains = false;
1156        self.visit_pre(|e| {
1157            if let MirScalarExpr::Literal(row, _) = e {
1158                if let Ok(row) = row {
1159                    contains |= row.iter().any(|d| d.contains_dummy());
1160                }
1161            }
1162        });
1163        contains
1164    }
1165
1166    /// The size of the expression as a tree.
1167    pub fn size(&self) -> usize {
1168        let mut size = 0;
1169        self.visit_pre(&mut |_: &MirScalarExpr| {
1170            size += 1;
1171        });
1172        size
1173    }
1174}
1175
1176/// Fails once `temp_storage` holds more than the budget it was built with.
1177///
1178/// Checked after each function call rather than inside the arena, because `RowArena`'s pushes are
1179/// infallible: refusing one would hand back a truncated value. Between calls is the innermost point
1180/// that can return an error, so a budgeted arena reaches at most its budget plus whatever the call
1181/// that crossed it allocated. Functions that can predict their own size cut that overshoot by
1182/// consulting [`crate::func::max_string_func_result_bytes`] first.
1183///
1184/// An unbudgeted arena, which is every arena in a dataflow, costs one branch on a `None`.
1185fn check_temp_storage_budget(temp_storage: &RowArena) -> Result<(), EvalError> {
1186    if temp_storage.over_budget() {
1187        return Err(EvalError::TempStorageBudgetExceeded);
1188    }
1189    Ok(())
1190}
1191
1192impl Eval for MirScalarExpr {
1193    fn eval<'a>(
1194        &'a self,
1195        datums: &[Datum<'a>],
1196        temp_storage: &'a RowArena,
1197    ) -> Result<Datum<'a>, EvalError> {
1198        match self {
1199            MirScalarExpr::Column(index, _name) => Ok(datums[*index]),
1200            MirScalarExpr::Literal(res, _column_type) => match res {
1201                Ok(row) => Ok(row.unpack_first()),
1202                Err(e) => Err(e.clone()),
1203            },
1204            // Unmaterializable functions must be transformed away before
1205            // evaluation. Their purpose is as a placeholder for data that is
1206            // not known at plan time but can be inlined before runtime.
1207            MirScalarExpr::CallUnmaterializable(x) => Err(EvalError::Internal(
1208                format!("cannot evaluate unmaterializable function: {:?}", x).into(),
1209            )),
1210            MirScalarExpr::CallUnary { func, expr } => {
1211                let datum = func.eval(datums, temp_storage, expr.as_ref())?;
1212                check_temp_storage_budget(temp_storage)?;
1213                Ok(datum)
1214            }
1215            MirScalarExpr::CallBinary { func, expr1, expr2 } => {
1216                let datum = func.eval(datums, temp_storage, &[expr1.as_ref(), expr2.as_ref()])?;
1217                check_temp_storage_budget(temp_storage)?;
1218                Ok(datum)
1219            }
1220            MirScalarExpr::CallVariadic { func, exprs } => {
1221                let datum = func.eval(datums, temp_storage, exprs.as_slice())?;
1222                check_temp_storage_budget(temp_storage)?;
1223                Ok(datum)
1224            }
1225            MirScalarExpr::If { cond, then, els } => match cond.eval(datums, temp_storage)? {
1226                Datum::True => then.eval(datums, temp_storage),
1227                Datum::False | Datum::Null => els.eval(datums, temp_storage),
1228                d => Err(EvalError::Internal(
1229                    format!("if condition evaluated to non-boolean datum: {:?}", d).into(),
1230                )),
1231            },
1232        }
1233    }
1234
1235    fn could_error(&self) -> bool {
1236        match self {
1237            MirScalarExpr::Column(_col, _name) => false,
1238            MirScalarExpr::Literal(row, ..) => row.is_err(),
1239            MirScalarExpr::CallUnmaterializable(_) => true,
1240            MirScalarExpr::CallUnary { func, expr } => func.could_error() || expr.could_error(),
1241            MirScalarExpr::CallBinary { func, expr1, expr2 } => {
1242                func.could_error() || expr1.could_error() || expr2.could_error()
1243            }
1244            MirScalarExpr::CallVariadic { func, exprs } => {
1245                func.could_error() || exprs.iter().any(|e| e.could_error())
1246            }
1247            MirScalarExpr::If { cond, then, els } => {
1248                cond.could_error() || then.could_error() || els.could_error()
1249            }
1250        }
1251    }
1252}
1253
1254impl Columns for MirScalarExpr {
1255    fn column(c: usize) -> Self {
1256        MirScalarExpr::column(c)
1257    }
1258
1259    fn is_column(&self) -> bool {
1260        matches!(self, MirScalarExpr::Column(_col, _name))
1261    }
1262
1263    fn as_column(&self) -> Option<usize> {
1264        if let MirScalarExpr::Column(c, _) = self {
1265            Some(*c)
1266        } else {
1267            None
1268        }
1269    }
1270
1271    fn as_column_mut(&mut self) -> Option<&mut usize> {
1272        if let MirScalarExpr::Column(c, _) = self {
1273            Some(c)
1274        } else {
1275            None
1276        }
1277    }
1278
1279    fn support_into(&self, support: &mut BTreeSet<usize>) {
1280        self.visit_pre(|e| {
1281            if let MirScalarExpr::Column(i, _) = e {
1282                support.insert(*i);
1283            }
1284        });
1285    }
1286
1287    fn visit_columns<F>(&mut self, mut action: F)
1288    where
1289        F: FnMut(&mut usize),
1290    {
1291        self.visit_pre_mut(|e| {
1292            if let MirScalarExpr::Column(col, _) = e {
1293                action(col);
1294            }
1295        });
1296    }
1297}
1298
1299impl VisitChildren<Self> for MirScalarExpr {
1300    fn visit_children<F>(&self, mut f: F)
1301    where
1302        F: FnMut(&Self),
1303    {
1304        use MirScalarExpr::*;
1305        match self {
1306            Column(_, _) | Literal(_, _) | CallUnmaterializable(_) => (),
1307            CallUnary { expr, .. } => {
1308                f(expr);
1309            }
1310            CallBinary { expr1, expr2, .. } => {
1311                f(expr1);
1312                f(expr2);
1313            }
1314            CallVariadic { exprs, .. } => {
1315                for expr in exprs {
1316                    f(expr);
1317                }
1318            }
1319            If { cond, then, els } => {
1320                f(cond);
1321                f(then);
1322                f(els);
1323            }
1324        }
1325    }
1326
1327    fn visit_mut_children<F>(&mut self, mut f: F)
1328    where
1329        F: FnMut(&mut Self),
1330    {
1331        use MirScalarExpr::*;
1332        match self {
1333            Column(_, _) | Literal(_, _) | CallUnmaterializable(_) => (),
1334            CallUnary { expr, .. } => {
1335                f(expr);
1336            }
1337            CallBinary { expr1, expr2, .. } => {
1338                f(expr1);
1339                f(expr2);
1340            }
1341            CallVariadic { exprs, .. } => {
1342                for expr in exprs {
1343                    f(expr);
1344                }
1345            }
1346            If { cond, then, els } => {
1347                f(cond);
1348                f(then);
1349                f(els);
1350            }
1351        }
1352    }
1353
1354    fn try_visit_children<F, E>(&self, mut f: F) -> Result<(), E>
1355    where
1356        F: FnMut(&Self) -> Result<(), E>,
1357    {
1358        use MirScalarExpr::*;
1359        match self {
1360            Column(_, _) | Literal(_, _) | CallUnmaterializable(_) => (),
1361            CallUnary { expr, .. } => {
1362                f(expr)?;
1363            }
1364            CallBinary { expr1, expr2, .. } => {
1365                f(expr1)?;
1366                f(expr2)?;
1367            }
1368            CallVariadic { exprs, .. } => {
1369                for expr in exprs {
1370                    f(expr)?;
1371                }
1372            }
1373            If { cond, then, els } => {
1374                f(cond)?;
1375                f(then)?;
1376                f(els)?;
1377            }
1378        }
1379        Ok(())
1380    }
1381
1382    fn try_visit_mut_children<F, E>(&mut self, mut f: F) -> Result<(), E>
1383    where
1384        F: FnMut(&mut Self) -> Result<(), E>,
1385    {
1386        use MirScalarExpr::*;
1387        match self {
1388            Column(_, _) | Literal(_, _) | CallUnmaterializable(_) => (),
1389            CallUnary { expr, .. } => {
1390                f(expr)?;
1391            }
1392            CallBinary { expr1, expr2, .. } => {
1393                f(expr1)?;
1394                f(expr2)?;
1395            }
1396            CallVariadic { exprs, .. } => {
1397                for expr in exprs {
1398                    f(expr)?;
1399                }
1400            }
1401            If { cond, then, els } => {
1402                f(cond)?;
1403                f(then)?;
1404                f(els)?;
1405            }
1406        }
1407        Ok(())
1408    }
1409
1410    fn children<'a>(&'a self) -> impl DoubleEndedIterator<Item = &'a Self>
1411    where
1412        Self: 'a,
1413    {
1414        self.children()
1415    }
1416
1417    fn children_mut<'a>(&'a mut self) -> impl DoubleEndedIterator<Item = &'a mut Self>
1418    where
1419        Self: 'a,
1420    {
1421        self.children_mut()
1422    }
1423}
1424
1425impl MirScalarExpr {
1426    /// Reports whether this expression contains a non-strict variadic call with an
1427    /// operand that could error, the shape of the open bug CLU-137.
1428    ///
1429    /// `And`, `Or` and `ErrorIfNull` do not evaluate every operand: `Or::eval`
1430    /// returns `true` the moment it sees a true operand and drops any error it
1431    /// collected, `And::eval` does the same for `false`, and `ErrorIfNull`
1432    /// evaluates its message operand only when the first operand is NULL. Yet
1433    /// `reduce_call_variadic`'s generic error propagation replaces the whole call
1434    /// with any operand's literal error, wherever it sits. So `reduce` can turn a
1435    /// row the expression should evaluate into an error, and, because that literal
1436    /// is typed non-nullable, it can go on to license a nullability-dependent
1437    /// rewrite and yield a different *value* rather than an error.
1438    ///
1439    /// Fuzz oracles that compare evaluation across `reduce` use this to skip the
1440    /// shape rather than rediscover CLU-137 on every run. It lives here, next to
1441    /// the fold it describes, so the several oracles that need it cannot drift
1442    /// apart on which functions count as non-strict.
1443    ///
1444    /// Deliberately conservative: it asks whether an operand *could* error rather
1445    /// than whether it already holds a literal error, because `reduce` folds a
1446    /// column-free fallible operand (`1 / 0`) to a literal error first and absorbs
1447    /// it after.
1448    pub fn could_hit_nonstrict_error_fold(&self) -> bool {
1449        let mut hit = false;
1450        self.visit_pre(|e| {
1451            if let MirScalarExpr::CallVariadic { func, exprs } = e {
1452                let non_strict = matches!(
1453                    func,
1454                    VariadicFunc::And(_) | VariadicFunc::Or(_) | VariadicFunc::ErrorIfNull(_)
1455                );
1456                if non_strict && exprs.iter().any(|operand| operand.could_error()) {
1457                    hit = true;
1458                }
1459            }
1460        });
1461        hit
1462    }
1463
1464    /// Iterates through references to child expressions.
1465    pub fn children(&self) -> impl DoubleEndedIterator<Item = &Self> {
1466        let mut first = None;
1467        let mut second = None;
1468        let mut third = None;
1469        let mut variadic = None;
1470
1471        use MirScalarExpr::*;
1472        match self {
1473            Column(_, _) | Literal(_, _) | CallUnmaterializable(_) => (),
1474            CallUnary { expr, .. } => {
1475                first = Some(&**expr);
1476            }
1477            CallBinary { expr1, expr2, .. } => {
1478                first = Some(&**expr1);
1479                second = Some(&**expr2);
1480            }
1481            CallVariadic { exprs, .. } => {
1482                variadic = Some(exprs);
1483            }
1484            If { cond, then, els } => {
1485                first = Some(&**cond);
1486                second = Some(&**then);
1487                third = Some(&**els);
1488            }
1489        }
1490
1491        first
1492            .into_iter()
1493            .chain(second)
1494            .chain(third)
1495            .chain(variadic.into_iter().flatten())
1496    }
1497
1498    /// Iterates through mutable references to child expressions.
1499    pub fn children_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut Self> {
1500        let mut first = None;
1501        let mut second = None;
1502        let mut third = None;
1503        let mut variadic = None;
1504
1505        use MirScalarExpr::*;
1506        match self {
1507            Column(_, _) | Literal(_, _) | CallUnmaterializable(_) => (),
1508            CallUnary { expr, .. } => {
1509                first = Some(&mut **expr);
1510            }
1511            CallBinary { expr1, expr2, .. } => {
1512                first = Some(&mut **expr1);
1513                second = Some(&mut **expr2);
1514            }
1515            CallVariadic { exprs, .. } => {
1516                variadic = Some(exprs);
1517            }
1518            If { cond, then, els } => {
1519                first = Some(&mut **cond);
1520                second = Some(&mut **then);
1521                third = Some(&mut **els);
1522            }
1523        }
1524
1525        first
1526            .into_iter()
1527            .chain(second)
1528            .chain(third)
1529            .chain(variadic.into_iter().flatten())
1530    }
1531
1532    /// Visits all subexpressions in DFS preorder.
1533    pub fn visit_pre<F>(&self, mut f: F)
1534    where
1535        F: FnMut(&Self),
1536    {
1537        let mut worklist = vec![self];
1538        while let Some(e) = worklist.pop() {
1539            f(e);
1540            worklist.extend(e.children().rev());
1541        }
1542    }
1543
1544    /// Iterative pre-order visitor.
1545    pub fn visit_pre_mut<F: FnMut(&mut Self)>(&mut self, mut f: F) {
1546        let mut worklist = vec![self];
1547        while let Some(expr) = worklist.pop() {
1548            f(expr);
1549            worklist.extend(expr.children_mut().rev());
1550        }
1551    }
1552}
1553
1554/// Filter characteristics that are used for ordering join inputs.
1555/// This can be created for a `Vec<MirScalarExpr>`, which represents an AND of predicates.
1556///
1557/// The fields are ordered based on heuristic assumptions about their typical selectivity, so that
1558/// Ord gives the right ordering for join inputs. Bigger is better, i.e., will tend to come earlier
1559/// than other inputs.
1560#[derive(
1561    Eq,
1562    PartialEq,
1563    Ord,
1564    PartialOrd,
1565    Debug,
1566    Clone,
1567    Serialize,
1568    Deserialize,
1569    Hash
1570)]
1571pub struct FilterCharacteristics {
1572    // `<expr> = <literal>` appears in the filter.
1573    // Excludes cases where NOT appears anywhere above the literal equality.
1574    literal_equality: bool,
1575    // (Assuming a random string of lower-case characters, `LIKE 'a%'` has a selectivity of 1/26.)
1576    like: bool,
1577    is_null: bool,
1578    // Number of Vec elements that involve inequality predicates. (A BETWEEN is represented as two
1579    // inequality predicates.)
1580    // Excludes cases where NOT appears around the literal inequality.
1581    // Note that for inequality predicates, some databases assume 1/3 selectivity in the absence of
1582    // concrete statistics.
1583    literal_inequality: usize,
1584    /// Any filter, except ones involving `IS NOT NULL`, because those are too common.
1585    /// Can be true by itself, or any other field being true can also make this true.
1586    /// `NOT LIKE` is only in this category.
1587    /// `!=` is only in this category.
1588    /// `NOT (a = b)` is turned into `!=` by `reduce` before us!
1589    any_filter: bool,
1590}
1591
1592impl BitOrAssign for FilterCharacteristics {
1593    fn bitor_assign(&mut self, rhs: Self) {
1594        self.literal_equality |= rhs.literal_equality;
1595        self.like |= rhs.like;
1596        self.is_null |= rhs.is_null;
1597        self.literal_inequality += rhs.literal_inequality;
1598        self.any_filter |= rhs.any_filter;
1599    }
1600}
1601
1602impl FilterCharacteristics {
1603    pub fn none() -> FilterCharacteristics {
1604        FilterCharacteristics {
1605            literal_equality: false,
1606            like: false,
1607            is_null: false,
1608            literal_inequality: 0,
1609            any_filter: false,
1610        }
1611    }
1612
1613    pub fn explain(&self) -> String {
1614        let mut e = "".to_owned();
1615        if self.literal_equality {
1616            e.push_str("e");
1617        }
1618        if self.like {
1619            e.push_str("l");
1620        }
1621        if self.is_null {
1622            e.push_str("n");
1623        }
1624        for _ in 0..self.literal_inequality {
1625            e.push_str("i");
1626        }
1627        if self.any_filter {
1628            e.push_str("f");
1629        }
1630        e
1631    }
1632
1633    pub fn filter_characteristics(
1634        filters: &Vec<MirScalarExpr>,
1635    ) -> Result<FilterCharacteristics, RecursionLimitError> {
1636        let mut literal_equality = false;
1637        let mut like = false;
1638        let mut is_null = false;
1639        let mut literal_inequality = 0;
1640        let mut any_filter = false;
1641        filters.iter().try_for_each(|f| {
1642            let mut literal_inequality_in_current_filter = false;
1643            let mut is_not_null_in_current_filter = false;
1644            f.visit_pre_with_context(
1645                false,
1646                &mut |not_in_parent_chain, expr| {
1647                    not_in_parent_chain
1648                        || matches!(
1649                            expr,
1650                            MirScalarExpr::CallUnary {
1651                                func: UnaryFunc::Not(func::Not),
1652                                ..
1653                            }
1654                        )
1655                },
1656                &mut |not_in_parent_chain, expr| {
1657                    if !not_in_parent_chain {
1658                        if expr.any_expr_eq_literal().is_some() {
1659                            literal_equality = true;
1660                        }
1661                        if expr.any_expr_ineq_literal() {
1662                            literal_inequality_in_current_filter = true;
1663                        }
1664                        if matches!(
1665                            expr,
1666                            MirScalarExpr::CallUnary {
1667                                func: UnaryFunc::IsLikeMatch(_),
1668                                ..
1669                            }
1670                        ) {
1671                            like = true;
1672                        }
1673                    };
1674                    if matches!(
1675                        expr,
1676                        MirScalarExpr::CallUnary {
1677                            func: UnaryFunc::IsNull(crate::func::IsNull),
1678                            ..
1679                        }
1680                    ) {
1681                        if *not_in_parent_chain {
1682                            is_not_null_in_current_filter = true;
1683                        } else {
1684                            is_null = true;
1685                        }
1686                    }
1687                },
1688            );
1689            if literal_inequality_in_current_filter {
1690                literal_inequality += 1;
1691            }
1692            if !is_not_null_in_current_filter {
1693                // We want to ignore `IS NOT NULL` for `any_filter`.
1694                any_filter = true;
1695            }
1696            Ok(())
1697        })?;
1698        Ok(FilterCharacteristics {
1699            literal_equality,
1700            like,
1701            is_null,
1702            literal_inequality,
1703            any_filter,
1704        })
1705    }
1706
1707    pub fn add_literal_equality(&mut self) {
1708        self.literal_equality = true;
1709    }
1710
1711    pub fn worst_case_scaling_factor(&self) -> f64 {
1712        let mut factor = 1.0;
1713
1714        if self.literal_equality {
1715            factor *= 0.1;
1716        }
1717
1718        if self.is_null {
1719            factor *= 0.1;
1720        }
1721
1722        if self.literal_inequality >= 2 {
1723            factor *= 0.25;
1724        } else if self.literal_inequality == 1 {
1725            factor *= 0.33;
1726        }
1727
1728        // catch various negated filters, treat them pessimistically
1729        if !(self.literal_equality || self.is_null || self.literal_inequality > 0)
1730            && self.any_filter
1731        {
1732            factor *= 0.9;
1733        }
1734
1735        factor
1736    }
1737}
1738
1739#[derive(
1740    Ord,
1741    PartialOrd,
1742    Copy,
1743    Clone,
1744    Debug,
1745    Eq,
1746    PartialEq,
1747    Serialize,
1748    Deserialize,
1749    Hash
1750)]
1751#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))]
1752pub enum DomainLimit {
1753    None,
1754    Inclusive(i64),
1755    Exclusive(i64),
1756}
1757
1758impl RustType<ProtoDomainLimit> for DomainLimit {
1759    fn into_proto(&self) -> ProtoDomainLimit {
1760        use proto_domain_limit::Kind::*;
1761        let kind = match self {
1762            DomainLimit::None => None(()),
1763            DomainLimit::Inclusive(v) => Inclusive(*v),
1764            DomainLimit::Exclusive(v) => Exclusive(*v),
1765        };
1766        ProtoDomainLimit { kind: Some(kind) }
1767    }
1768
1769    fn from_proto(proto: ProtoDomainLimit) -> Result<Self, TryFromProtoError> {
1770        use proto_domain_limit::Kind::*;
1771        if let Some(kind) = proto.kind {
1772            match kind {
1773                None(()) => Ok(DomainLimit::None),
1774                Inclusive(v) => Ok(DomainLimit::Inclusive(v)),
1775                Exclusive(v) => Ok(DomainLimit::Exclusive(v)),
1776            }
1777        } else {
1778            Err(TryFromProtoError::missing_field("ProtoDomainLimit::kind"))
1779        }
1780    }
1781}
1782
1783#[derive(
1784    Ord,
1785    PartialOrd,
1786    Clone,
1787    Debug,
1788    Eq,
1789    PartialEq,
1790    Serialize,
1791    Deserialize,
1792    Hash
1793)]
1794#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))]
1795pub enum EvalError {
1796    CharacterNotValidForEncoding(i32),
1797    CharacterTooLargeForEncoding(i32),
1798    DateBinOutOfRange(Box<str>),
1799    DivisionByZero,
1800    Unsupported {
1801        feature: Box<str>,
1802        discussion_no: Option<usize>,
1803    },
1804    FloatOverflow,
1805    FloatUnderflow,
1806    NumericFieldOverflow,
1807    Float32OutOfRange(Box<str>),
1808    Float64OutOfRange(Box<str>),
1809    Int16OutOfRange(Box<str>),
1810    Int32OutOfRange(Box<str>),
1811    Int64OutOfRange(Box<str>),
1812    UInt16OutOfRange(Box<str>),
1813    UInt32OutOfRange(Box<str>),
1814    UInt64OutOfRange(Box<str>),
1815    MzTimestampOutOfRange(Box<str>),
1816    MzTimestampStepOverflow,
1817    OidOutOfRange(Box<str>),
1818    IntervalOutOfRange(Box<str>),
1819    TimestampCannotBeNan,
1820    TimestampOutOfRange,
1821    DateOutOfRange,
1822    CharOutOfRange,
1823    IndexOutOfRange {
1824        provided: i32,
1825        // The last valid index position, i.e. `v.len() - 1`
1826        valid_end: i32,
1827    },
1828    InvalidBase64Equals,
1829    InvalidBase64Symbol(char),
1830    InvalidBase64EndSequence,
1831    InvalidTimezone(Box<str>),
1832    InvalidTimezoneInterval,
1833    InvalidTimezoneConversion,
1834    InvalidIanaTimezoneId(Box<str>),
1835    InvalidLayer {
1836        max_layer: usize,
1837        val: i64,
1838    },
1839    InvalidArray(InvalidArrayError),
1840    InvalidEncodingName(Box<str>),
1841    InvalidHashAlgorithm(Box<str>),
1842    InvalidByteSequence {
1843        byte_sequence: Box<str>,
1844        encoding_name: Box<str>,
1845    },
1846    InvalidJsonbCast {
1847        from: Box<str>,
1848        to: Box<str>,
1849    },
1850    InvalidRegex(Box<str>),
1851    InvalidRegexFlag(char),
1852    InvalidParameterValue(Box<str>),
1853    InvalidDatePart(Box<str>),
1854    KeyCannotBeNull,
1855    NegSqrt,
1856    NegLimit,
1857    NullCharacterNotPermitted,
1858    UnknownUnits(Box<str>),
1859    UnsupportedUnits(Box<str>, Box<str>),
1860    UnterminatedLikeEscapeSequence,
1861    Parse(ParseError),
1862    ParseHex(ParseHexError),
1863    Internal(Box<str>),
1864    InfinityOutOfDomain(Box<str>),
1865    NegativeOutOfDomain(Box<str>),
1866    ZeroOutOfDomain(Box<str>),
1867    OutOfDomain(DomainLimit, DomainLimit, Box<str>),
1868    ComplexOutOfRange(Box<str>),
1869    MultipleRowsFromSubquery,
1870    NegativeRowsFromSubquery,
1871    Undefined(Box<str>),
1872    LikePatternTooLong,
1873    LikeEscapeTooLong,
1874    StringValueTooLong {
1875        target_type: Box<str>,
1876        length: usize,
1877    },
1878    MultidimensionalArrayRemovalNotSupported,
1879    IncompatibleArrayDimensions {
1880        dims: Option<(usize, usize)>,
1881    },
1882    TypeFromOid(Box<str>),
1883    InvalidRange(InvalidRangeError),
1884    InvalidRoleId(Box<str>),
1885    InvalidPrivileges(Box<str>),
1886    InvalidCatalogJson(Box<str>),
1887    LetRecLimitExceeded(Box<str>),
1888    MultiDimensionalArraySearch,
1889    MustNotBeNull(Box<str>),
1890    InvalidIdentifier {
1891        ident: Box<str>,
1892        detail: Option<Box<str>>,
1893    },
1894    ArrayFillWrongArraySubscripts,
1895    // TODO: propagate this check more widely throughout the expr crate
1896    MaxArraySizeExceeded(usize),
1897    DateDiffOverflow {
1898        unit: Box<str>,
1899        a: Box<str>,
1900        b: Box<str>,
1901    },
1902    // The error for ErrorIfNull; this should not be used in other contexts as a generic error
1903    // printer.
1904    IfNullError(Box<str>),
1905    LengthTooLarge,
1906    // A budgeted `RowArena` (`mz_repr::RowArena::with_budget`) exceeded its budget while an
1907    // expression was being evaluated. Only a budgeted arena raises this, so it never arises in a
1908    // dataflow, only on the webhook `CHECK` path that runs user expressions in `environmentd`.
1909    TempStorageBudgetExceeded,
1910    AclArrayNullElement,
1911    MzAclArrayNullElement,
1912    PrettyError(Box<str>),
1913    RedactError(Box<str>),
1914}
1915
1916impl fmt::Display for EvalError {
1917    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1918        match self {
1919            EvalError::CharacterNotValidForEncoding(v) => {
1920                write!(f, "requested character not valid for encoding: {v}")
1921            }
1922            EvalError::CharacterTooLargeForEncoding(v) => {
1923                write!(f, "requested character too large for encoding: {v}")
1924            }
1925            EvalError::DateBinOutOfRange(message) => f.write_str(message),
1926            EvalError::DivisionByZero => f.write_str("division by zero"),
1927            EvalError::Unsupported {
1928                feature,
1929                discussion_no,
1930            } => {
1931                write!(f, "{} not yet supported", feature)?;
1932                if let Some(discussion_no) = discussion_no {
1933                    write!(
1934                        f,
1935                        ", see https://github.com/MaterializeInc/materialize/discussions/{} for more details",
1936                        discussion_no
1937                    )?;
1938                }
1939                Ok(())
1940            }
1941            EvalError::FloatOverflow => f.write_str("value out of range: overflow"),
1942            EvalError::FloatUnderflow => f.write_str("value out of range: underflow"),
1943            EvalError::NumericFieldOverflow => f.write_str("numeric field overflow"),
1944            EvalError::Float32OutOfRange(val) => write!(f, "{} real out of range", val.quoted()),
1945            EvalError::Float64OutOfRange(val) => {
1946                write!(f, "{} double precision out of range", val.quoted())
1947            }
1948            EvalError::Int16OutOfRange(val) => write!(f, "{} smallint out of range", val.quoted()),
1949            EvalError::Int32OutOfRange(val) => write!(f, "{} integer out of range", val.quoted()),
1950            EvalError::Int64OutOfRange(val) => write!(f, "{} bigint out of range", val.quoted()),
1951            EvalError::UInt16OutOfRange(val) => write!(f, "{} uint2 out of range", val.quoted()),
1952            EvalError::UInt32OutOfRange(val) => write!(f, "{} uint4 out of range", val.quoted()),
1953            EvalError::UInt64OutOfRange(val) => write!(f, "{} uint8 out of range", val.quoted()),
1954            EvalError::MzTimestampOutOfRange(val) => {
1955                write!(f, "{} mz_timestamp out of range", val.quoted())
1956            }
1957            EvalError::MzTimestampStepOverflow => f.write_str("step mz_timestamp overflow"),
1958            EvalError::OidOutOfRange(val) => write!(f, "{} OID out of range", val.quoted()),
1959            EvalError::IntervalOutOfRange(val) => {
1960                write!(f, "{} interval out of range", val.quoted())
1961            }
1962            EvalError::TimestampCannotBeNan => f.write_str("timestamp cannot be NaN"),
1963            EvalError::TimestampOutOfRange => f.write_str("timestamp out of range"),
1964            EvalError::DateOutOfRange => f.write_str("date out of range"),
1965            EvalError::CharOutOfRange => f.write_str("\"char\" out of range"),
1966            EvalError::IndexOutOfRange {
1967                provided,
1968                valid_end,
1969            } => write!(f, "index {provided} out of valid range, 0..{valid_end}",),
1970            EvalError::InvalidBase64Equals => {
1971                f.write_str("unexpected \"=\" while decoding base64 sequence")
1972            }
1973            EvalError::InvalidBase64Symbol(c) => write!(
1974                f,
1975                "invalid symbol \"{}\" found while decoding base64 sequence",
1976                c.escape_default()
1977            ),
1978            EvalError::InvalidBase64EndSequence => f.write_str("invalid base64 end sequence"),
1979            EvalError::InvalidJsonbCast { from, to } => {
1980                write!(f, "cannot cast jsonb {} to type {}", from, to)
1981            }
1982            EvalError::InvalidTimezone(tz) => write!(f, "invalid time zone '{}'", tz),
1983            EvalError::InvalidTimezoneInterval => {
1984                f.write_str("timezone interval must not contain months or years")
1985            }
1986            EvalError::InvalidTimezoneConversion => f.write_str("invalid timezone conversion"),
1987            EvalError::InvalidIanaTimezoneId(tz) => {
1988                write!(f, "invalid IANA Time Zone Database identifier: '{}'", tz)
1989            }
1990            EvalError::InvalidLayer { max_layer, val } => write!(
1991                f,
1992                "invalid layer: {}; must use value within [1, {}]",
1993                val, max_layer
1994            ),
1995            EvalError::InvalidArray(e) => e.fmt(f),
1996            EvalError::InvalidEncodingName(name) => write!(f, "invalid encoding name '{}'", name),
1997            EvalError::InvalidHashAlgorithm(alg) => write!(f, "invalid hash algorithm '{}'", alg),
1998            EvalError::InvalidByteSequence {
1999                byte_sequence,
2000                encoding_name,
2001            } => write!(
2002                f,
2003                "invalid byte sequence '{}' for encoding '{}'",
2004                byte_sequence, encoding_name
2005            ),
2006            EvalError::InvalidDatePart(part) => write!(f, "invalid datepart {}", part.quoted()),
2007            EvalError::KeyCannotBeNull => f.write_str("key cannot be null"),
2008            EvalError::NegSqrt => f.write_str("cannot take square root of a negative number"),
2009            EvalError::NegLimit => f.write_str("LIMIT must not be negative"),
2010            EvalError::NullCharacterNotPermitted => f.write_str("null character not permitted"),
2011            EvalError::InvalidRegex(e) => write!(f, "invalid regular expression: {}", e),
2012            EvalError::InvalidRegexFlag(c) => write!(f, "invalid regular expression flag: {}", c),
2013            EvalError::InvalidParameterValue(s) => f.write_str(s),
2014            EvalError::UnknownUnits(units) => write!(f, "unit '{}' not recognized", units),
2015            EvalError::UnsupportedUnits(units, typ) => {
2016                write!(f, "unit '{}' not supported for type {}", units, typ)
2017            }
2018            EvalError::UnterminatedLikeEscapeSequence => {
2019                f.write_str("unterminated escape sequence in LIKE")
2020            }
2021            EvalError::Parse(e) => e.fmt(f),
2022            EvalError::PrettyError(e) => e.fmt(f),
2023            EvalError::RedactError(e) => e.fmt(f),
2024            EvalError::ParseHex(e) => e.fmt(f),
2025            EvalError::Internal(s) => write!(f, "internal error: {}", s),
2026            EvalError::InfinityOutOfDomain(s) => {
2027                write!(f, "function {} is only defined for finite arguments", s)
2028            }
2029            EvalError::NegativeOutOfDomain(s) => {
2030                write!(f, "function {} is not defined for negative numbers", s)
2031            }
2032            EvalError::ZeroOutOfDomain(s) => {
2033                write!(f, "function {} is not defined for zero", s)
2034            }
2035            EvalError::OutOfDomain(lower, upper, s) => {
2036                use DomainLimit::*;
2037                write!(f, "function {s} is defined for numbers ")?;
2038                match (lower, upper) {
2039                    (Inclusive(n), None) => write!(f, "greater than or equal to {n}"),
2040                    (Exclusive(n), None) => write!(f, "greater than {n}"),
2041                    (None, Inclusive(n)) => write!(f, "less than or equal to {n}"),
2042                    (None, Exclusive(n)) => write!(f, "less than {n}"),
2043                    (Inclusive(lo), Inclusive(hi)) => write!(f, "between {lo} and {hi} inclusive"),
2044                    (Exclusive(lo), Exclusive(hi)) => write!(f, "between {lo} and {hi} exclusive"),
2045                    (Inclusive(lo), Exclusive(hi)) => {
2046                        write!(f, "between {lo} inclusive and {hi} exclusive")
2047                    }
2048                    (Exclusive(lo), Inclusive(hi)) => {
2049                        write!(f, "between {lo} exclusive and {hi} inclusive")
2050                    }
2051                    // No caller constructs an unbounded domain, but a corrupted or
2052                    // forged `ProtoEvalError` decodes into one. Render it instead of
2053                    // panicking: `DataflowErrorSer::Display` decodes errors straight
2054                    // out of a persist shard and Displays them on the index peek
2055                    // path, so a panicking arm here wedges the dataflow on every
2056                    // retry rather than producing a bad error message once.
2057                    (None, None) => write!(f, "in an unspecified range"),
2058                }
2059            }
2060            EvalError::ComplexOutOfRange(s) => {
2061                write!(f, "function {} cannot return complex numbers", s)
2062            }
2063            EvalError::MultipleRowsFromSubquery => {
2064                write!(f, "more than one record produced in subquery")
2065            }
2066            EvalError::NegativeRowsFromSubquery => {
2067                write!(f, "negative number of rows produced in subquery")
2068            }
2069            EvalError::Undefined(s) => {
2070                write!(f, "{} is undefined", s)
2071            }
2072            EvalError::LikePatternTooLong => {
2073                write!(f, "LIKE pattern exceeds maximum length")
2074            }
2075            EvalError::LikeEscapeTooLong => {
2076                write!(f, "invalid escape string")
2077            }
2078            EvalError::StringValueTooLong {
2079                target_type,
2080                length,
2081            } => {
2082                write!(f, "value too long for type {}({})", target_type, length)
2083            }
2084            EvalError::MultidimensionalArrayRemovalNotSupported => {
2085                write!(
2086                    f,
2087                    "removing elements from multidimensional arrays is not supported"
2088                )
2089            }
2090            EvalError::IncompatibleArrayDimensions { dims: _ } => {
2091                write!(f, "cannot concatenate incompatible arrays")
2092            }
2093            EvalError::TypeFromOid(msg) => write!(f, "{msg}"),
2094            EvalError::InvalidRange(e) => e.fmt(f),
2095            EvalError::InvalidRoleId(msg) => write!(f, "{msg}"),
2096            EvalError::InvalidPrivileges(privilege) => {
2097                write!(f, "unrecognized privilege type: {privilege}")
2098            }
2099            EvalError::InvalidCatalogJson(msg) => {
2100                write!(f, "invalid catalog JSON: {msg}")
2101            }
2102            EvalError::LetRecLimitExceeded(max_iters) => {
2103                write!(
2104                    f,
2105                    "Recursive query exceeded the recursion limit {}. (Use RETURN AT RECURSION LIMIT to not error, but return the current state as the final result when reaching the limit.)",
2106                    max_iters
2107                )
2108            }
2109            EvalError::MultiDimensionalArraySearch => write!(
2110                f,
2111                "searching for elements in multidimensional arrays is not supported"
2112            ),
2113            EvalError::MustNotBeNull(v) => write!(f, "{v} must not be null"),
2114            EvalError::InvalidIdentifier { ident, .. } => {
2115                write!(f, "string is not a valid identifier: {}", ident.quoted())
2116            }
2117            EvalError::ArrayFillWrongArraySubscripts => {
2118                f.write_str("wrong number of array subscripts")
2119            }
2120            EvalError::MaxArraySizeExceeded(max_size) => {
2121                write!(
2122                    f,
2123                    "array size exceeds the maximum allowed ({max_size} bytes)"
2124                )
2125            }
2126            EvalError::DateDiffOverflow { unit, a, b } => {
2127                write!(f, "datediff overflow, {unit} of {a}, {b}")
2128            }
2129            EvalError::IfNullError(s) => f.write_str(s),
2130            EvalError::LengthTooLarge => write!(f, "requested length too large"),
2131            EvalError::TempStorageBudgetExceeded => {
2132                write!(f, "expression exceeded its temporary storage limit")
2133            }
2134            EvalError::AclArrayNullElement => write!(f, "ACL arrays must not contain null values"),
2135            EvalError::MzAclArrayNullElement => {
2136                write!(f, "MZ_ACL arrays must not contain null values")
2137            }
2138        }
2139    }
2140}
2141
2142impl EvalError {
2143    pub fn detail(&self) -> Option<String> {
2144        match self {
2145            EvalError::IncompatibleArrayDimensions { dims: None } => Some(
2146                "Arrays with differing dimensions are not compatible for concatenation.".into(),
2147            ),
2148            EvalError::IncompatibleArrayDimensions {
2149                dims: Some((a_dims, b_dims)),
2150            } => Some(format!(
2151                "Arrays of {} and {} dimensions are not compatible for concatenation.",
2152                a_dims, b_dims
2153            )),
2154            EvalError::InvalidIdentifier { detail, .. } => detail.as_deref().map(Into::into),
2155            EvalError::ArrayFillWrongArraySubscripts => {
2156                Some("Low bound array has different size than dimensions array.".into())
2157            }
2158            _ => None,
2159        }
2160    }
2161
2162    pub fn hint(&self) -> Option<String> {
2163        match self {
2164            EvalError::InvalidBase64EndSequence => Some(
2165                "Input data is missing padding, is truncated, or is otherwise corrupted.".into(),
2166            ),
2167            EvalError::LikeEscapeTooLong => {
2168                Some("Escape string must be empty or one character.".into())
2169            }
2170            EvalError::MzTimestampOutOfRange(_) => Some(
2171                "Integer, numeric, and text casts to mz_timestamp must be in the form of whole \
2172                milliseconds since the Unix epoch. Values with fractional parts cannot be \
2173                converted to mz_timestamp."
2174                    .into(),
2175            ),
2176            _ => None,
2177        }
2178    }
2179}
2180
2181impl std::error::Error for EvalError {}
2182
2183impl From<ParseError> for EvalError {
2184    fn from(e: ParseError) -> EvalError {
2185        EvalError::Parse(e)
2186    }
2187}
2188
2189impl From<ParseHexError> for EvalError {
2190    fn from(e: ParseHexError) -> EvalError {
2191        EvalError::ParseHex(e)
2192    }
2193}
2194
2195impl From<InvalidArrayError> for EvalError {
2196    fn from(e: InvalidArrayError) -> EvalError {
2197        EvalError::InvalidArray(e)
2198    }
2199}
2200
2201impl From<RegexCompilationError> for EvalError {
2202    fn from(e: RegexCompilationError) -> EvalError {
2203        EvalError::InvalidRegex(e.to_string().into())
2204    }
2205}
2206
2207impl From<TypeFromOidError> for EvalError {
2208    fn from(e: TypeFromOidError) -> EvalError {
2209        EvalError::TypeFromOid(e.to_string().into())
2210    }
2211}
2212
2213impl From<DateError> for EvalError {
2214    fn from(e: DateError) -> EvalError {
2215        match e {
2216            DateError::OutOfRange => EvalError::DateOutOfRange,
2217        }
2218    }
2219}
2220
2221impl From<TimestampError> for EvalError {
2222    fn from(e: TimestampError) -> EvalError {
2223        match e {
2224            TimestampError::OutOfRange => EvalError::TimestampOutOfRange,
2225        }
2226    }
2227}
2228
2229impl From<InvalidRangeError> for EvalError {
2230    fn from(e: InvalidRangeError) -> EvalError {
2231        EvalError::InvalidRange(e)
2232    }
2233}
2234
2235impl RustType<ProtoEvalError> for EvalError {
2236    fn into_proto(&self) -> ProtoEvalError {
2237        use proto_eval_error::Kind::*;
2238        use proto_eval_error::*;
2239        let kind = match self {
2240            EvalError::CharacterNotValidForEncoding(v) => CharacterNotValidForEncoding(*v),
2241            EvalError::CharacterTooLargeForEncoding(v) => CharacterTooLargeForEncoding(*v),
2242            EvalError::DateBinOutOfRange(v) => DateBinOutOfRange(v.into_proto()),
2243            EvalError::DivisionByZero => DivisionByZero(()),
2244            EvalError::Unsupported {
2245                feature,
2246                discussion_no,
2247            } => Unsupported(ProtoUnsupported {
2248                feature: feature.into_proto(),
2249                discussion_no: discussion_no.into_proto(),
2250            }),
2251            EvalError::FloatOverflow => FloatOverflow(()),
2252            EvalError::FloatUnderflow => FloatUnderflow(()),
2253            EvalError::NumericFieldOverflow => NumericFieldOverflow(()),
2254            EvalError::Float32OutOfRange(val) => Float32OutOfRange(ProtoValueOutOfRange {
2255                value: val.to_string(),
2256            }),
2257            EvalError::Float64OutOfRange(val) => Float64OutOfRange(ProtoValueOutOfRange {
2258                value: val.to_string(),
2259            }),
2260            EvalError::Int16OutOfRange(val) => Int16OutOfRange(ProtoValueOutOfRange {
2261                value: val.to_string(),
2262            }),
2263            EvalError::Int32OutOfRange(val) => Int32OutOfRange(ProtoValueOutOfRange {
2264                value: val.to_string(),
2265            }),
2266            EvalError::Int64OutOfRange(val) => Int64OutOfRange(ProtoValueOutOfRange {
2267                value: val.to_string(),
2268            }),
2269            EvalError::UInt16OutOfRange(val) => Uint16OutOfRange(ProtoValueOutOfRange {
2270                value: val.to_string(),
2271            }),
2272            EvalError::UInt32OutOfRange(val) => Uint32OutOfRange(ProtoValueOutOfRange {
2273                value: val.to_string(),
2274            }),
2275            EvalError::UInt64OutOfRange(val) => Uint64OutOfRange(ProtoValueOutOfRange {
2276                value: val.to_string(),
2277            }),
2278            EvalError::MzTimestampOutOfRange(val) => MzTimestampOutOfRange(ProtoValueOutOfRange {
2279                value: val.to_string(),
2280            }),
2281            EvalError::MzTimestampStepOverflow => MzTimestampStepOverflow(()),
2282            EvalError::OidOutOfRange(val) => OidOutOfRange(ProtoValueOutOfRange {
2283                value: val.to_string(),
2284            }),
2285            EvalError::IntervalOutOfRange(val) => IntervalOutOfRange(ProtoValueOutOfRange {
2286                value: val.to_string(),
2287            }),
2288            EvalError::TimestampCannotBeNan => TimestampCannotBeNan(()),
2289            EvalError::TimestampOutOfRange => TimestampOutOfRange(()),
2290            EvalError::DateOutOfRange => DateOutOfRange(()),
2291            EvalError::CharOutOfRange => CharOutOfRange(()),
2292            EvalError::IndexOutOfRange {
2293                provided,
2294                valid_end,
2295            } => IndexOutOfRange(ProtoIndexOutOfRange {
2296                provided: *provided,
2297                valid_end: *valid_end,
2298            }),
2299            EvalError::InvalidBase64Equals => InvalidBase64Equals(()),
2300            EvalError::InvalidBase64Symbol(sym) => InvalidBase64Symbol(sym.into_proto()),
2301            EvalError::InvalidBase64EndSequence => InvalidBase64EndSequence(()),
2302            EvalError::InvalidTimezone(tz) => InvalidTimezone(tz.into_proto()),
2303            EvalError::InvalidTimezoneInterval => InvalidTimezoneInterval(()),
2304            EvalError::InvalidTimezoneConversion => InvalidTimezoneConversion(()),
2305            EvalError::InvalidLayer { max_layer, val } => InvalidLayer(ProtoInvalidLayer {
2306                max_layer: max_layer.into_proto(),
2307                val: *val,
2308            }),
2309            EvalError::InvalidArray(error) => InvalidArray(error.into_proto()),
2310            EvalError::InvalidEncodingName(v) => InvalidEncodingName(v.into_proto()),
2311            EvalError::InvalidHashAlgorithm(v) => InvalidHashAlgorithm(v.into_proto()),
2312            EvalError::InvalidByteSequence {
2313                byte_sequence,
2314                encoding_name,
2315            } => InvalidByteSequence(ProtoInvalidByteSequence {
2316                byte_sequence: byte_sequence.into_proto(),
2317                encoding_name: encoding_name.into_proto(),
2318            }),
2319            EvalError::InvalidJsonbCast { from, to } => InvalidJsonbCast(ProtoInvalidJsonbCast {
2320                from: from.into_proto(),
2321                to: to.into_proto(),
2322            }),
2323            EvalError::InvalidRegex(v) => InvalidRegex(v.into_proto()),
2324            EvalError::InvalidRegexFlag(v) => InvalidRegexFlag(v.into_proto()),
2325            EvalError::InvalidParameterValue(v) => InvalidParameterValue(v.into_proto()),
2326            EvalError::InvalidDatePart(part) => InvalidDatePart(part.into_proto()),
2327            EvalError::KeyCannotBeNull => KeyCannotBeNull(()),
2328            EvalError::NegSqrt => NegSqrt(()),
2329            EvalError::NegLimit => NegLimit(()),
2330            EvalError::NullCharacterNotPermitted => NullCharacterNotPermitted(()),
2331            EvalError::UnknownUnits(v) => UnknownUnits(v.into_proto()),
2332            EvalError::UnsupportedUnits(units, typ) => UnsupportedUnits(ProtoUnsupportedUnits {
2333                units: units.into_proto(),
2334                typ: typ.into_proto(),
2335            }),
2336            EvalError::UnterminatedLikeEscapeSequence => UnterminatedLikeEscapeSequence(()),
2337            EvalError::Parse(error) => Parse(error.into_proto()),
2338            EvalError::PrettyError(error) => PrettyError(error.into_proto()),
2339            EvalError::RedactError(error) => RedactError(error.into_proto()),
2340            EvalError::ParseHex(error) => ParseHex(error.into_proto()),
2341            EvalError::Internal(v) => Internal(v.into_proto()),
2342            EvalError::InfinityOutOfDomain(v) => InfinityOutOfDomain(v.into_proto()),
2343            EvalError::NegativeOutOfDomain(v) => NegativeOutOfDomain(v.into_proto()),
2344            EvalError::ZeroOutOfDomain(v) => ZeroOutOfDomain(v.into_proto()),
2345            EvalError::OutOfDomain(lower, upper, id) => OutOfDomain(ProtoOutOfDomain {
2346                lower: Some(lower.into_proto()),
2347                upper: Some(upper.into_proto()),
2348                id: id.into_proto(),
2349            }),
2350            EvalError::ComplexOutOfRange(v) => ComplexOutOfRange(v.into_proto()),
2351            EvalError::MultipleRowsFromSubquery => MultipleRowsFromSubquery(()),
2352            EvalError::NegativeRowsFromSubquery => NegativeRowsFromSubquery(()),
2353            EvalError::Undefined(v) => Undefined(v.into_proto()),
2354            EvalError::LikePatternTooLong => LikePatternTooLong(()),
2355            EvalError::LikeEscapeTooLong => LikeEscapeTooLong(()),
2356            EvalError::StringValueTooLong {
2357                target_type,
2358                length,
2359            } => StringValueTooLong(ProtoStringValueTooLong {
2360                target_type: target_type.into_proto(),
2361                length: length.into_proto(),
2362            }),
2363            EvalError::MultidimensionalArrayRemovalNotSupported => {
2364                MultidimensionalArrayRemovalNotSupported(())
2365            }
2366            EvalError::IncompatibleArrayDimensions { dims } => {
2367                IncompatibleArrayDimensions(ProtoIncompatibleArrayDimensions {
2368                    dims: dims.into_proto(),
2369                })
2370            }
2371            EvalError::TypeFromOid(v) => TypeFromOid(v.into_proto()),
2372            EvalError::InvalidRange(error) => InvalidRange(error.into_proto()),
2373            EvalError::InvalidRoleId(v) => InvalidRoleId(v.into_proto()),
2374            EvalError::InvalidPrivileges(v) => InvalidPrivileges(v.into_proto()),
2375            EvalError::InvalidCatalogJson(v) => InvalidCatalogJson(v.into_proto()),
2376            EvalError::LetRecLimitExceeded(v) => WmrRecursionLimitExceeded(v.into_proto()),
2377            EvalError::MultiDimensionalArraySearch => MultiDimensionalArraySearch(()),
2378            EvalError::MustNotBeNull(v) => MustNotBeNull(v.into_proto()),
2379            EvalError::InvalidIdentifier { ident, detail } => {
2380                InvalidIdentifier(ProtoInvalidIdentifier {
2381                    ident: ident.into_proto(),
2382                    detail: detail.into_proto(),
2383                })
2384            }
2385            EvalError::ArrayFillWrongArraySubscripts => ArrayFillWrongArraySubscripts(()),
2386            EvalError::MaxArraySizeExceeded(max_size) => {
2387                MaxArraySizeExceeded(u64::cast_from(*max_size))
2388            }
2389            EvalError::DateDiffOverflow { unit, a, b } => DateDiffOverflow(ProtoDateDiffOverflow {
2390                unit: unit.into_proto(),
2391                a: a.into_proto(),
2392                b: b.into_proto(),
2393            }),
2394            EvalError::IfNullError(s) => IfNullError(s.into_proto()),
2395            EvalError::LengthTooLarge => LengthTooLarge(()),
2396            EvalError::TempStorageBudgetExceeded => TempStorageBudgetExceeded(()),
2397            EvalError::AclArrayNullElement => AclArrayNullElement(()),
2398            EvalError::MzAclArrayNullElement => MzAclArrayNullElement(()),
2399            EvalError::InvalidIanaTimezoneId(s) => InvalidIanaTimezoneId(s.into_proto()),
2400        };
2401        ProtoEvalError { kind: Some(kind) }
2402    }
2403
2404    fn from_proto(proto: ProtoEvalError) -> Result<Self, TryFromProtoError> {
2405        use proto_eval_error::Kind::*;
2406        match proto.kind {
2407            Some(kind) => match kind {
2408                CharacterNotValidForEncoding(v) => Ok(EvalError::CharacterNotValidForEncoding(v)),
2409                CharacterTooLargeForEncoding(v) => Ok(EvalError::CharacterTooLargeForEncoding(v)),
2410                DateBinOutOfRange(v) => Ok(EvalError::DateBinOutOfRange(v.into())),
2411                DivisionByZero(()) => Ok(EvalError::DivisionByZero),
2412                Unsupported(v) => Ok(EvalError::Unsupported {
2413                    feature: v.feature.into(),
2414                    discussion_no: v.discussion_no.into_rust()?,
2415                }),
2416                FloatOverflow(()) => Ok(EvalError::FloatOverflow),
2417                FloatUnderflow(()) => Ok(EvalError::FloatUnderflow),
2418                NumericFieldOverflow(()) => Ok(EvalError::NumericFieldOverflow),
2419                Float32OutOfRange(val) => Ok(EvalError::Float32OutOfRange(val.value.into())),
2420                Float64OutOfRange(val) => Ok(EvalError::Float64OutOfRange(val.value.into())),
2421                Int16OutOfRange(val) => Ok(EvalError::Int16OutOfRange(val.value.into())),
2422                Int32OutOfRange(val) => Ok(EvalError::Int32OutOfRange(val.value.into())),
2423                Int64OutOfRange(val) => Ok(EvalError::Int64OutOfRange(val.value.into())),
2424                Uint16OutOfRange(val) => Ok(EvalError::UInt16OutOfRange(val.value.into())),
2425                Uint32OutOfRange(val) => Ok(EvalError::UInt32OutOfRange(val.value.into())),
2426                Uint64OutOfRange(val) => Ok(EvalError::UInt64OutOfRange(val.value.into())),
2427                MzTimestampOutOfRange(val) => {
2428                    Ok(EvalError::MzTimestampOutOfRange(val.value.into()))
2429                }
2430                MzTimestampStepOverflow(()) => Ok(EvalError::MzTimestampStepOverflow),
2431                OidOutOfRange(val) => Ok(EvalError::OidOutOfRange(val.value.into())),
2432                IntervalOutOfRange(val) => Ok(EvalError::IntervalOutOfRange(val.value.into())),
2433                TimestampCannotBeNan(()) => Ok(EvalError::TimestampCannotBeNan),
2434                TimestampOutOfRange(()) => Ok(EvalError::TimestampOutOfRange),
2435                DateOutOfRange(()) => Ok(EvalError::DateOutOfRange),
2436                CharOutOfRange(()) => Ok(EvalError::CharOutOfRange),
2437                IndexOutOfRange(v) => Ok(EvalError::IndexOutOfRange {
2438                    provided: v.provided,
2439                    valid_end: v.valid_end,
2440                }),
2441                InvalidBase64Equals(()) => Ok(EvalError::InvalidBase64Equals),
2442                InvalidBase64Symbol(v) => char::from_proto(v).map(EvalError::InvalidBase64Symbol),
2443                InvalidBase64EndSequence(()) => Ok(EvalError::InvalidBase64EndSequence),
2444                InvalidTimezone(v) => Ok(EvalError::InvalidTimezone(v.into())),
2445                InvalidTimezoneInterval(()) => Ok(EvalError::InvalidTimezoneInterval),
2446                InvalidTimezoneConversion(()) => Ok(EvalError::InvalidTimezoneConversion),
2447                InvalidLayer(v) => Ok(EvalError::InvalidLayer {
2448                    max_layer: usize::from_proto(v.max_layer)?,
2449                    val: v.val,
2450                }),
2451                InvalidArray(error) => Ok(EvalError::InvalidArray(error.into_rust()?)),
2452                InvalidEncodingName(v) => Ok(EvalError::InvalidEncodingName(v.into())),
2453                InvalidHashAlgorithm(v) => Ok(EvalError::InvalidHashAlgorithm(v.into())),
2454                InvalidByteSequence(v) => Ok(EvalError::InvalidByteSequence {
2455                    byte_sequence: v.byte_sequence.into(),
2456                    encoding_name: v.encoding_name.into(),
2457                }),
2458                InvalidJsonbCast(v) => Ok(EvalError::InvalidJsonbCast {
2459                    from: v.from.into(),
2460                    to: v.to.into(),
2461                }),
2462                InvalidRegex(v) => Ok(EvalError::InvalidRegex(v.into())),
2463                InvalidRegexFlag(v) => Ok(EvalError::InvalidRegexFlag(char::from_proto(v)?)),
2464                InvalidParameterValue(v) => Ok(EvalError::InvalidParameterValue(v.into())),
2465                InvalidDatePart(part) => Ok(EvalError::InvalidDatePart(part.into())),
2466                KeyCannotBeNull(()) => Ok(EvalError::KeyCannotBeNull),
2467                NegSqrt(()) => Ok(EvalError::NegSqrt),
2468                NegLimit(()) => Ok(EvalError::NegLimit),
2469                NullCharacterNotPermitted(()) => Ok(EvalError::NullCharacterNotPermitted),
2470                UnknownUnits(v) => Ok(EvalError::UnknownUnits(v.into())),
2471                UnsupportedUnits(v) => {
2472                    Ok(EvalError::UnsupportedUnits(v.units.into(), v.typ.into()))
2473                }
2474                UnterminatedLikeEscapeSequence(()) => Ok(EvalError::UnterminatedLikeEscapeSequence),
2475                Parse(error) => Ok(EvalError::Parse(error.into_rust()?)),
2476                ParseHex(error) => Ok(EvalError::ParseHex(error.into_rust()?)),
2477                Internal(v) => Ok(EvalError::Internal(v.into())),
2478                InfinityOutOfDomain(v) => Ok(EvalError::InfinityOutOfDomain(v.into())),
2479                NegativeOutOfDomain(v) => Ok(EvalError::NegativeOutOfDomain(v.into())),
2480                ZeroOutOfDomain(v) => Ok(EvalError::ZeroOutOfDomain(v.into())),
2481                OutOfDomain(v) => Ok(EvalError::OutOfDomain(
2482                    v.lower.into_rust_if_some("ProtoDomainLimit::lower")?,
2483                    v.upper.into_rust_if_some("ProtoDomainLimit::upper")?,
2484                    v.id.into(),
2485                )),
2486                ComplexOutOfRange(v) => Ok(EvalError::ComplexOutOfRange(v.into())),
2487                MultipleRowsFromSubquery(()) => Ok(EvalError::MultipleRowsFromSubquery),
2488                NegativeRowsFromSubquery(()) => Ok(EvalError::NegativeRowsFromSubquery),
2489                Undefined(v) => Ok(EvalError::Undefined(v.into())),
2490                LikePatternTooLong(()) => Ok(EvalError::LikePatternTooLong),
2491                LikeEscapeTooLong(()) => Ok(EvalError::LikeEscapeTooLong),
2492                StringValueTooLong(v) => Ok(EvalError::StringValueTooLong {
2493                    target_type: v.target_type.into(),
2494                    length: usize::from_proto(v.length)?,
2495                }),
2496                MultidimensionalArrayRemovalNotSupported(()) => {
2497                    Ok(EvalError::MultidimensionalArrayRemovalNotSupported)
2498                }
2499                IncompatibleArrayDimensions(v) => Ok(EvalError::IncompatibleArrayDimensions {
2500                    dims: v.dims.into_rust()?,
2501                }),
2502                TypeFromOid(v) => Ok(EvalError::TypeFromOid(v.into())),
2503                InvalidRange(e) => Ok(EvalError::InvalidRange(e.into_rust()?)),
2504                InvalidRoleId(v) => Ok(EvalError::InvalidRoleId(v.into())),
2505                InvalidPrivileges(v) => Ok(EvalError::InvalidPrivileges(v.into())),
2506                InvalidCatalogJson(v) => Ok(EvalError::InvalidCatalogJson(v.into())),
2507                WmrRecursionLimitExceeded(v) => Ok(EvalError::LetRecLimitExceeded(v.into())),
2508                MultiDimensionalArraySearch(()) => Ok(EvalError::MultiDimensionalArraySearch),
2509                MustNotBeNull(v) => Ok(EvalError::MustNotBeNull(v.into())),
2510                InvalidIdentifier(v) => Ok(EvalError::InvalidIdentifier {
2511                    ident: v.ident.into(),
2512                    detail: v.detail.into_rust()?,
2513                }),
2514                ArrayFillWrongArraySubscripts(()) => Ok(EvalError::ArrayFillWrongArraySubscripts),
2515                MaxArraySizeExceeded(max_size) => {
2516                    Ok(EvalError::MaxArraySizeExceeded(usize::cast_from(max_size)))
2517                }
2518                DateDiffOverflow(v) => Ok(EvalError::DateDiffOverflow {
2519                    unit: v.unit.into(),
2520                    a: v.a.into(),
2521                    b: v.b.into(),
2522                }),
2523                IfNullError(v) => Ok(EvalError::IfNullError(v.into())),
2524                LengthTooLarge(()) => Ok(EvalError::LengthTooLarge),
2525                TempStorageBudgetExceeded(()) => Ok(EvalError::TempStorageBudgetExceeded),
2526                AclArrayNullElement(()) => Ok(EvalError::AclArrayNullElement),
2527                MzAclArrayNullElement(()) => Ok(EvalError::MzAclArrayNullElement),
2528                InvalidIanaTimezoneId(s) => Ok(EvalError::InvalidIanaTimezoneId(s.into())),
2529                PrettyError(s) => Ok(EvalError::PrettyError(s.into())),
2530                RedactError(s) => Ok(EvalError::RedactError(s.into())),
2531            },
2532            None => Err(TryFromProtoError::missing_field("ProtoEvalError::kind")),
2533        }
2534    }
2535}
2536
2537impl RustType<ProtoDims> for (usize, usize) {
2538    fn into_proto(&self) -> ProtoDims {
2539        ProtoDims {
2540            f0: self.0.into_proto(),
2541            f1: self.1.into_proto(),
2542        }
2543    }
2544
2545    fn from_proto(proto: ProtoDims) -> Result<Self, TryFromProtoError> {
2546        Ok((proto.f0.into_rust()?, proto.f1.into_rust()?))
2547    }
2548}
2549
2550/// An [`EvalError`] that serializes as protobuf-encoded [`ProtoEvalError`]
2551/// bytes.
2552///
2553/// `EvalError`'s own serde impl mirrors the Rust enum, so every added error
2554/// variant or payload tweak would change a durable format. Use this wrapper
2555/// instead wherever an eval error is serialized into a durable, cross-version
2556/// format, such as the stable LIR plan format (the same role [`StableRow`]
2557/// plays for rows). `ProtoEvalError` already carries the needed backward
2558/// compatibility obligation: it is embedded in `ProtoDataflowError`, which
2559/// persist stores in the error side of `SourceData`, and the proto files are
2560/// covered by the buf breaking lint.
2561///
2562/// [`StableRow`]: mz_repr::StableRow
2563#[derive(
2564    Clone,
2565    Debug,
2566    Eq,
2567    PartialEq,
2568    Ord,
2569    PartialOrd,
2570    Hash,
2571    Serialize,
2572    Deserialize
2573)]
2574pub struct StableEvalError(#[serde(with = "stable_eval_error_proto")] pub EvalError);
2575
2576/// Borrowing twin of [`StableEvalError`], to serialize without cloning. Both
2577/// serialize identically, under the `StableEvalError` container name.
2578#[derive(Debug, Serialize)]
2579#[serde(rename = "StableEvalError")]
2580pub struct StableEvalErrorRef<'a>(#[serde(with = "stable_eval_error_proto")] pub &'a EvalError);
2581
2582impl From<EvalError> for StableEvalError {
2583    fn from(err: EvalError) -> Self {
2584        StableEvalError(err)
2585    }
2586}
2587
2588impl std::ops::Deref for StableEvalError {
2589    type Target = EvalError;
2590
2591    fn deref(&self) -> &EvalError {
2592        &self.0
2593    }
2594}
2595
2596mod stable_eval_error_proto {
2597    use mz_proto::RustType;
2598    use prost::Message;
2599    use serde::de::Error;
2600    use serde::{Deserialize, Deserializer, Serializer};
2601
2602    use crate::scalar::{EvalError, ProtoEvalError};
2603
2604    pub fn serialize<S: Serializer, E: std::borrow::Borrow<EvalError>>(
2605        err: &E,
2606        serializer: S,
2607    ) -> Result<S::Ok, S::Error> {
2608        serializer.serialize_bytes(&err.borrow().into_proto().encode_to_vec())
2609    }
2610
2611    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<EvalError, D::Error> {
2612        let bytes = serde_bytes::ByteBuf::deserialize(deserializer)?;
2613        let proto = ProtoEvalError::decode(bytes.as_slice()).map_err(D::Error::custom)?;
2614        EvalError::from_proto(proto).map_err(D::Error::custom)
2615    }
2616}
2617
2618#[cfg(test)]
2619mod tests {
2620    use super::*;
2621    use crate::scalar::func::variadic::Coalesce;
2622
2623    // StableEvalError's wire format is proto bytes, so every EvalError
2624    // variant must roundtrip exactly through both a self-describing format
2625    // (JSON) and a compact binary one (bincode). This also exercises the
2626    // EvalError -> ProtoEvalError -> EvalError conversion itself, which has
2627    // no other roundtrip coverage.
2628    proptest! {
2629        #![proptest_config(ProptestConfig::with_cases(1000))]
2630
2631        #[mz_ore::test]
2632        #[cfg_attr(miri, ignore)] // too slow
2633        fn stable_eval_error_serde_roundtrip(err in any::<EvalError>()) {
2634            let stable = StableEvalError(err);
2635
2636            let json = serde_json::to_string(&stable).expect("serializes to JSON");
2637            let from_json: StableEvalError =
2638                serde_json::from_str(&json).expect("deserializes from JSON");
2639            prop_assert_eq!(&stable, &from_json);
2640
2641            let bytes = bincode::serialize(&stable).expect("serializes to bincode");
2642            let from_bincode: StableEvalError =
2643                bincode::deserialize(&bytes).expect("deserializes from bincode");
2644            prop_assert_eq!(&stable, &from_bincode);
2645        }
2646    }
2647
2648    /// An `OutOfDomain` with both limits unset is not constructible by any
2649    /// caller, but it decodes out of corrupted or forged `ProtoEvalError` bytes,
2650    /// and `DataflowErrorSer::Display` renders decoded errors on the peek path.
2651    /// Rendering it must not panic.
2652    #[mz_ore::test]
2653    fn test_unbounded_out_of_domain_renders() {
2654        let err = EvalError::OutOfDomain(DomainLimit::None, DomainLimit::None, "f".into());
2655        assert_eq!(
2656            err.to_string(),
2657            "function f is defined for numbers in an unspecified range"
2658        );
2659    }
2660
2661    /// A budgeted arena must stop an amplifying expression that the per-call constant allows
2662    /// (SQL-431). `repeat(body, 20)` on a 1 MiB body is 20 MiB: far under
2663    /// `MAX_STRING_FUNC_RESULT_BYTES`, so nothing rejects it without a budget, and it is exactly
2664    /// the shape that made a webhook `CHECK` turn a bounded request into unbounded heap.
2665    #[mz_ore::test]
2666    #[cfg_attr(miri, ignore)] // multi-MB allocations; the small-size UB coverage is in `row.rs`
2667    fn test_repeat_respects_arena_budget() {
2668        use crate::scalar::func::RepeatString;
2669
2670        let body = "a".repeat(1024 * 1024);
2671        let expr = MirScalarExpr::column(0).call_binary(
2672            MirScalarExpr::literal_ok(Datum::Int32(20), ReprScalarType::Int32),
2673            RepeatString,
2674        );
2675        let datums = [Datum::String(&body)];
2676
2677        // Unbudgeted: allowed, and the arena really does hold the 20 MiB.
2678        let arena = RowArena::new();
2679        let datum = expr
2680            .eval(&datums, &arena)
2681            .expect("under the 100 MiB ceiling");
2682        assert_eq!(datum.unwrap_str().len(), 20 * 1024 * 1024);
2683        assert!(arena.allocated_bytes() >= 20 * 1024 * 1024);
2684
2685        // Budgeted below the result: rejected, and the pre-check means the arena never grew, i.e.
2686        // the bytes were never allocated rather than allocated and then complained about.
2687        let arena = RowArena::with_budget(4 * 1024 * 1024);
2688        assert_eq!(
2689            expr.eval(&datums, &arena),
2690            Err(EvalError::LengthTooLarge),
2691            "an over-budget result must be refused"
2692        );
2693        assert_eq!(arena.allocated_bytes(), 0);
2694
2695        // Budgeted above the result: unaffected.
2696        let arena = RowArena::with_budget(64 * 1024 * 1024);
2697        let datum = expr.eval(&datums, &arena).expect("within budget");
2698        assert_eq!(datum.unwrap_str().len(), 20 * 1024 * 1024);
2699    }
2700
2701    /// The budget also has to bound both dimensions of an arena-building function: the packed
2702    /// result, and the transient it collects on its own stack first. The arena never sees the
2703    /// transient, so the post-call check can't catch it.
2704    #[mz_ore::test]
2705    #[cfg_attr(miri, ignore)] // multi-MB allocations; the small-size UB coverage is in `row.rs`
2706    fn test_arena_built_result_respects_budget() {
2707        use crate::scalar::func::variadic::{RegexpSplitToArray, StringToArray};
2708
2709        let body = "a".repeat(256 * 1024);
2710        let expr = MirScalarExpr::call_variadic(
2711            StringToArray,
2712            vec![
2713                MirScalarExpr::column(0),
2714                MirScalarExpr::literal_ok(Datum::String("a"), ReprScalarType::String),
2715            ],
2716        );
2717        let datums = [Datum::String(&body)];
2718
2719        let arena = RowArena::new();
2720        expr.eval(&datums, &arena).expect("no ceiling applies");
2721        let unbudgeted = arena.allocated_bytes();
2722        assert!(unbudgeted > 0);
2723
2724        let arena = RowArena::with_budget(unbudgeted / 2);
2725        assert_eq!(
2726            expr.eval(&datums, &arena),
2727            Err(EvalError::TempStorageBudgetExceeded),
2728            "an over-budget arena-built result must be refused"
2729        );
2730
2731        // A split collects every chunk into a `Vec<&str>` first, 16 bytes per fat pointer against
2732        // the 2 an empty chunk packs to. A budget the packed array fits under still refuses it.
2733        let intermediate = (body.len() + 1) * std::mem::size_of::<&str>();
2734        let budget = 4 * unbudgeted;
2735        assert!(
2736            unbudgeted < budget && budget < intermediate,
2737            "budget sits between"
2738        );
2739        let arena = RowArena::with_budget(budget);
2740        assert!(
2741            expr.eval(&datums, &arena).is_err(),
2742            "a split costing {intermediate} bytes to build must be refused by a {budget} byte budget"
2743        );
2744        assert_eq!(
2745            arena.allocated_bytes(),
2746            0,
2747            "refused before the transient was built"
2748        );
2749
2750        // The regexp sibling builds the same transient and takes the same bound.
2751        let regexp_expr = MirScalarExpr::call_variadic(
2752            RegexpSplitToArray,
2753            vec![
2754                MirScalarExpr::column(0),
2755                MirScalarExpr::literal_ok(Datum::String("a"), ReprScalarType::String),
2756            ],
2757        );
2758        let arena = RowArena::with_budget(budget);
2759        assert!(
2760            regexp_expr.eval(&datums, &arena).is_err(),
2761            "a regexp split costing {intermediate} bytes to build must be refused too"
2762        );
2763        assert_eq!(arena.allocated_bytes(), 0);
2764    }
2765
2766    /// `array_fill` sizes its result from a parameter rather than its input, so a budgeted arena
2767    /// has to refuse it in its own pre-check the way the string amplifiers do (SQL-431). Its result
2768    /// stays under the `array_fill` size ceiling, so without the budget-aware pre-check the only
2769    /// thing that would catch it is the evaluator's post-call check, after the spike has happened.
2770    #[mz_ore::test]
2771    #[cfg_attr(miri, ignore)] // multi-MB allocations; the small-size UB coverage is in `row.rs`
2772    fn test_array_fill_respects_arena_budget() {
2773        use crate::scalar::func::variadic::ArrayFill;
2774        use mz_repr::adt::array::ArrayDimension;
2775
2776        // array_fill(1, ARRAY[fill_count]) builds a one-dimensional int array of `fill_count` ones.
2777        let fill_count: usize = 512 * 1024;
2778        let dims_storage = RowArena::new();
2779        let dims = dims_storage
2780            .try_make_datum(|packer| {
2781                packer.try_push_array(
2782                    &[ArrayDimension {
2783                        lower_bound: 1,
2784                        length: 1,
2785                    }],
2786                    [Datum::Int32(i32::try_from(fill_count).unwrap())],
2787                )
2788            })
2789            .unwrap();
2790        let expr = MirScalarExpr::call_variadic(
2791            ArrayFill {
2792                elem_type: mz_repr::SqlScalarType::Int32,
2793            },
2794            vec![MirScalarExpr::column(0), MirScalarExpr::column(1)],
2795        );
2796        let datums = [Datum::Int32(1), dims];
2797
2798        // Unbudgeted: allowed, and the arena really holds the packed array.
2799        let arena = RowArena::new();
2800        expr.eval(&datums, &arena)
2801            .expect("under the array-size ceiling");
2802        assert!(arena.allocated_bytes() > 0);
2803
2804        // Budgeted below what the call needs: refused by the pre-check, so nothing was allocated.
2805        // The intermediate `Vec<Datum>` alone is 512 Ki elements, well over this 1 MiB budget.
2806        let arena = RowArena::with_budget(1024 * 1024);
2807        assert_eq!(
2808            expr.eval(&datums, &arena),
2809            Err(EvalError::TempStorageBudgetExceeded),
2810            "an over-budget array_fill must be refused before it allocates"
2811        );
2812        assert_eq!(arena.allocated_bytes(), 0);
2813
2814        // Budgeted well above both the packed result and the intermediate: unaffected.
2815        let arena = RowArena::with_budget(256 * 1024 * 1024);
2816        expr.eval(&datums, &arena).expect("within budget");
2817    }
2818
2819    /// `array_remove` cannot grow its result, so no packed-size check would ever fire for it. The
2820    /// transient `Vec<Datum>` it filters into is a fresh input-scaled allocation on top of the input
2821    /// array's own bytes, and only a pre-check bounds that (SQL-431).
2822    #[mz_ore::test]
2823    #[cfg_attr(miri, ignore)] // multi-MB allocations; the small-size UB coverage is in `row.rs`
2824    fn test_array_remove_respects_arena_budget() {
2825        use crate::scalar::func::ArrayRemove;
2826        use mz_repr::adt::array::ArrayDimension;
2827
2828        const ELEMS: usize = 256 * 1024;
2829        let input_storage = RowArena::new();
2830        let array = input_storage
2831            .try_make_datum(|packer| {
2832                packer.try_push_array(
2833                    &[ArrayDimension {
2834                        lower_bound: 1,
2835                        length: ELEMS,
2836                    }],
2837                    (0..ELEMS).map(|i| Datum::Int32(i32::try_from(i).unwrap())),
2838                )
2839            })
2840            .unwrap();
2841        let expr = MirScalarExpr::column(0).call_binary(MirScalarExpr::column(1), ArrayRemove);
2842        let datums = [array, Datum::Int32(0)];
2843
2844        // Unbudgeted: allowed, and the arena holds the packed result.
2845        let arena = RowArena::new();
2846        expr.eval(&datums, &arena).expect("no ceiling applies");
2847        let unbudgeted = arena.allocated_bytes();
2848        assert!(unbudgeted > 0);
2849
2850        // A `Datum` is several times what an `Int32` packs to, so a budget with room for the packed
2851        // result twice over still has to refuse the transient.
2852        let transient = ELEMS * std::mem::size_of::<Datum<'_>>();
2853        let budget = 2 * unbudgeted;
2854        assert!(budget < transient, "budget sits between");
2855        let arena = RowArena::with_budget(budget);
2856        assert_eq!(
2857            expr.eval(&datums, &arena),
2858            Err(EvalError::TempStorageBudgetExceeded),
2859            "an over-budget transient must be refused"
2860        );
2861        assert_eq!(arena.allocated_bytes(), 0);
2862
2863        // Budgeted above both: unaffected.
2864        let arena = RowArena::with_budget(64 * 1024 * 1024);
2865        expr.eval(&datums, &arena).expect("within budget");
2866    }
2867
2868    /// A budget has to bound what a single call allocates, not just what is observable between
2869    /// calls (SQL-431).
2870    ///
2871    /// The evaluator only polls the budget after `func.eval` has built its result and moved it into
2872    /// the arena, and erroring then does not give the bytes back. They stay resident until the arena
2873    /// drops, which for a webhook `CHECK` is the end of the request. So we assert on arena residency,
2874    /// not the returned `Result`. The overshoot is not a constant either. It scales with the body and
2875    /// with a multiplier the `CHECK` author picks at DDL time.
2876    #[mz_ore::test]
2877    #[cfg_attr(miri, ignore)] // multi-MB allocations; the small-size UB coverage is in `row.rs`
2878    fn test_single_call_respects_arena_budget() {
2879        use mz_ore::cast::CastLossy;
2880
2881        use crate::scalar::func::variadic::{ArrayCreate, PadLeading, Translate};
2882
2883        // Scaled down from the shipped 5 MiB body and 20 MiB budget. Every amplifier here is linear
2884        // in the body, so the ratios hold at any scale.
2885        const BODY_BYTES: usize = 1024 * 1024;
2886        const BUDGET: usize = 2 * 1024 * 1024;
2887        const WIDE: &str = "\u{1F4A5}"; // one character, four bytes
2888
2889        let str_lit = |s| MirScalarExpr::literal_ok(Datum::String(s), ReprScalarType::String);
2890        // `ARRAY[body, ...]` is not a string function, so no ceiling of its own applies. The
2891        // multiplier is just how many times the author wrote `body`.
2892        let array_of = |n| {
2893            let elem_type = mz_repr::SqlScalarType::String;
2894            let refs = vec![MirScalarExpr::column(0); n];
2895            MirScalarExpr::call_variadic(ArrayCreate { elem_type }, refs)
2896        };
2897        let cases = [
2898            ("ARRAY[body x4]", array_of(4)),
2899            ("ARRAY[body x16]", array_of(16)),
2900            // `lpad`'s pre-check is budget-aware but compares `len`, a character count, against a
2901            // budget in bytes, so a 4-byte pad passes a check for exactly the budget then writes 4x.
2902            (
2903                "lpad(body, BUDGET, wide)",
2904                MirScalarExpr::call_variadic(
2905                    PadLeading,
2906                    vec![
2907                        MirScalarExpr::column(0),
2908                        MirScalarExpr::literal_ok(
2909                            Datum::Int32(i32::try_from(BUDGET).unwrap()),
2910                            ReprScalarType::Int32,
2911                        ),
2912                        str_lit(WIDE),
2913                    ],
2914                ),
2915            ),
2916            // `translate` has no pre-check at all, and widening each body byte is a 4x amplifier
2917            // that needs no length argument to drive it.
2918            (
2919                "translate(body, 'a', wide)",
2920                MirScalarExpr::call_variadic(
2921                    Translate,
2922                    vec![MirScalarExpr::column(0), str_lit("a"), str_lit(WIDE)],
2923                ),
2924            ),
2925        ];
2926
2927        let body = "a".repeat(BODY_BYTES);
2928        let datums = [Datum::String(&body)];
2929        let mut over = Vec::new();
2930        for (name, expr) in cases {
2931            let arena = RowArena::with_budget(BUDGET);
2932            let _ = expr.eval(&datums, &arena); // refused or not, we only care what's left resident
2933            let held = arena.allocated_bytes();
2934            if held > BUDGET {
2935                let ratio = f64::cast_lossy(held) / f64::cast_lossy(BUDGET);
2936                over.push(format!("  {name}: held {held} bytes, {ratio:.1}x"));
2937            }
2938        }
2939        assert!(
2940            over.is_empty(),
2941            "a single call left a {BUDGET} byte arena holding more:\n{}",
2942            over.join("\n"),
2943        );
2944    }
2945
2946    #[mz_ore::test]
2947    #[cfg_attr(miri, ignore)] // error: unsupported operation: can't call foreign function `rust_psm_stack_pointer` on OS `linux`
2948    fn test_reduce() {
2949        let relation_type: Vec<ReprColumnType> = vec![
2950            ReprScalarType::Int64.nullable(true),
2951            ReprScalarType::Int64.nullable(true),
2952            ReprScalarType::Int64.nullable(false),
2953        ]
2954        .into_iter()
2955        .collect();
2956        let col = MirScalarExpr::column;
2957        let int64_typ = ReprScalarType::Int64;
2958        let err = |e| MirScalarExpr::literal(Err(e), int64_typ.clone());
2959        let lit = |i| MirScalarExpr::literal_ok(Datum::Int64(i), int64_typ.clone());
2960        let null = || MirScalarExpr::literal_null(int64_typ.clone());
2961
2962        struct TestCase {
2963            input: MirScalarExpr,
2964            output: MirScalarExpr,
2965        }
2966
2967        let test_cases = vec![
2968            TestCase {
2969                input: MirScalarExpr::call_variadic(Coalesce, vec![lit(1)]),
2970                output: lit(1),
2971            },
2972            TestCase {
2973                input: MirScalarExpr::call_variadic(Coalesce, vec![lit(1), lit(2)]),
2974                output: lit(1),
2975            },
2976            TestCase {
2977                input: MirScalarExpr::call_variadic(Coalesce, vec![null(), lit(2), null()]),
2978                output: lit(2),
2979            },
2980            TestCase {
2981                input: MirScalarExpr::call_variadic(
2982                    Coalesce,
2983                    vec![null(), col(0), null(), col(1), lit(2), lit(3)],
2984                ),
2985                output: MirScalarExpr::call_variadic(Coalesce, vec![col(0), col(1), lit(2)]),
2986            },
2987            TestCase {
2988                input: MirScalarExpr::call_variadic(Coalesce, vec![col(0), col(2), col(1)]),
2989                output: MirScalarExpr::call_variadic(Coalesce, vec![col(0), col(2)]),
2990            },
2991            TestCase {
2992                input: MirScalarExpr::call_variadic(
2993                    Coalesce,
2994                    vec![lit(1), err(EvalError::DivisionByZero)],
2995                ),
2996                output: lit(1),
2997            },
2998            TestCase {
2999                input: MirScalarExpr::call_variadic(
3000                    Coalesce,
3001                    vec![
3002                        null(),
3003                        err(EvalError::DivisionByZero),
3004                        err(EvalError::NumericFieldOverflow),
3005                    ],
3006                ),
3007                output: err(EvalError::DivisionByZero),
3008            },
3009        ];
3010
3011        for tc in test_cases {
3012            let mut actual = tc.input.clone();
3013            actual.reduce(&relation_type);
3014            assert!(
3015                actual == tc.output,
3016                "input: {}\nactual: {}\nexpected: {}",
3017                tc.input,
3018                actual,
3019                tc.output
3020            );
3021        }
3022    }
3023
3024    /// Exercises the `unsafe` pointer stack in [`Visit::visit_mut_post`] with a
3025    /// closure that *replaces subtrees* (`*expr = ...`). Miri's aliasing model
3026    /// should shout if the "stack mirrors the call stack" becomes untrue.
3027    #[mz_ore::test]
3028    fn test_visit_mut_post_replace_subtrees() {
3029        let col = MirScalarExpr::column;
3030        let mut expr = col(0).if_then_else(col(1).if_then_else(col(2), col(3)), col(4));
3031
3032        expr.visit_mut_post(&mut |expr: &mut MirScalarExpr| match expr {
3033            MirScalarExpr::Column(n, _) => *n += 1,
3034            MirScalarExpr::If { then, .. } => {
3035                let then = then.take();
3036                *expr = then;
3037            }
3038            _ => {}
3039        });
3040
3041        // collapses to then-most branch
3042        assert_eq!(expr, col(3));
3043    }
3044
3045    /// Exercises the `unsafe` pointer stack in [`Visit::visit_mut_pre_post`] with
3046    /// a `pre` that both *replaces the visited node wholesale* (`*expr = ...`)
3047    /// and *returns an explicit child set* borrowed from the freshly written
3048    /// value. Miri's aliasing model should shout if the "stack mirrors the call
3049    /// stack" becomes untrue.
3050    #[mz_ore::test]
3051    fn test_visit_mut_pre_post_explicit_children() {
3052        let col = MirScalarExpr::column;
3053        let mut expr = col(5)
3054            .if_then_else(col(6), col(7))
3055            .if_then_else(col(1).if_then_else(col(2), col(3)), col(4));
3056
3057        // turns conditions into column 0 in pre
3058        // doesn't traverse conditions of ifs
3059        // adds 10 to all column refs in post (but not in conditions!)
3060        expr.visit_mut_pre_post(
3061            &mut |expr: &mut MirScalarExpr| -> Option<Vec<&mut MirScalarExpr>> {
3062                if let MirScalarExpr::If { .. } = expr {
3063                    let MirScalarExpr::If { then, els, .. } = expr else {
3064                        unreachable!()
3065                    };
3066                    let then = then.take();
3067                    let els = els.take();
3068                    *expr = MirScalarExpr::column(0).if_then_else(then, els);
3069
3070                    let MirScalarExpr::If { then, els, .. } = expr else {
3071                        unreachable!()
3072                    };
3073                    Some(vec![then.as_mut(), els.as_mut()])
3074                } else {
3075                    // Leaves recurse with their default (empty) child set.
3076                    None
3077                }
3078            },
3079            &mut |expr: &mut MirScalarExpr| {
3080                if let MirScalarExpr::Column(n, _) = expr {
3081                    *n += 10;
3082                }
3083            },
3084        );
3085
3086        // conditions become 0; everyone else += 10
3087        let expected = col(0).if_then_else(col(0).if_then_else(col(12), col(13)), col(14));
3088        assert_eq!(expr, expected);
3089    }
3090}