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 an `UnmaterializableFunc` that is not in the `exceptions`
1116    /// list.
1117    pub fn contains_unmaterializable_except(&self, exceptions: &[UnmaterializableFunc]) -> bool {
1118        let mut contains = false;
1119        self.visit_pre(|e| match e {
1120            MirScalarExpr::CallUnmaterializable(f) if !exceptions.contains(f) => contains = true,
1121            _ => (),
1122        });
1123        contains
1124    }
1125
1126    /// True iff the expression contains a `Column`.
1127    pub fn contains_column(&self) -> bool {
1128        let mut contains = false;
1129        self.visit_pre(|e| {
1130            if let MirScalarExpr::Column(_col, _name) = e {
1131                contains = true;
1132            }
1133        });
1134        contains
1135    }
1136
1137    /// True iff the expression contains a `Dummy`.
1138    pub fn contains_dummy(&self) -> bool {
1139        let mut contains = false;
1140        self.visit_pre(|e| {
1141            if let MirScalarExpr::Literal(row, _) = e {
1142                if let Ok(row) = row {
1143                    contains |= row.iter().any(|d| d.contains_dummy());
1144                }
1145            }
1146        });
1147        contains
1148    }
1149
1150    /// The size of the expression as a tree.
1151    pub fn size(&self) -> usize {
1152        let mut size = 0;
1153        self.visit_pre(&mut |_: &MirScalarExpr| {
1154            size += 1;
1155        });
1156        size
1157    }
1158}
1159
1160/// Fails once `temp_storage` holds more than the budget it was built with.
1161///
1162/// Checked after each function call rather than inside the arena, because `RowArena`'s pushes are
1163/// infallible: refusing one would hand back a truncated value. Between calls is the innermost point
1164/// that can return an error, so a budgeted arena reaches at most its budget plus whatever the call
1165/// that crossed it allocated. Functions that can predict their own size cut that overshoot by
1166/// consulting [`crate::func::max_string_func_result_bytes`] first.
1167///
1168/// An unbudgeted arena, which is every arena in a dataflow, costs one branch on a `None`.
1169fn check_temp_storage_budget(temp_storage: &RowArena) -> Result<(), EvalError> {
1170    if temp_storage.over_budget() {
1171        return Err(EvalError::TempStorageBudgetExceeded);
1172    }
1173    Ok(())
1174}
1175
1176impl Eval for MirScalarExpr {
1177    fn eval<'a>(
1178        &'a self,
1179        datums: &[Datum<'a>],
1180        temp_storage: &'a RowArena,
1181    ) -> Result<Datum<'a>, EvalError> {
1182        match self {
1183            MirScalarExpr::Column(index, _name) => Ok(datums[*index]),
1184            MirScalarExpr::Literal(res, _column_type) => match res {
1185                Ok(row) => Ok(row.unpack_first()),
1186                Err(e) => Err(e.clone()),
1187            },
1188            // Unmaterializable functions must be transformed away before
1189            // evaluation. Their purpose is as a placeholder for data that is
1190            // not known at plan time but can be inlined before runtime.
1191            MirScalarExpr::CallUnmaterializable(x) => Err(EvalError::Internal(
1192                format!("cannot evaluate unmaterializable function: {:?}", x).into(),
1193            )),
1194            MirScalarExpr::CallUnary { func, expr } => {
1195                let datum = func.eval(datums, temp_storage, expr.as_ref())?;
1196                check_temp_storage_budget(temp_storage)?;
1197                Ok(datum)
1198            }
1199            MirScalarExpr::CallBinary { func, expr1, expr2 } => {
1200                let datum = func.eval(datums, temp_storage, &[expr1.as_ref(), expr2.as_ref()])?;
1201                check_temp_storage_budget(temp_storage)?;
1202                Ok(datum)
1203            }
1204            MirScalarExpr::CallVariadic { func, exprs } => {
1205                let datum = func.eval(datums, temp_storage, exprs.as_slice())?;
1206                check_temp_storage_budget(temp_storage)?;
1207                Ok(datum)
1208            }
1209            MirScalarExpr::If { cond, then, els } => match cond.eval(datums, temp_storage)? {
1210                Datum::True => then.eval(datums, temp_storage),
1211                Datum::False | Datum::Null => els.eval(datums, temp_storage),
1212                d => Err(EvalError::Internal(
1213                    format!("if condition evaluated to non-boolean datum: {:?}", d).into(),
1214                )),
1215            },
1216        }
1217    }
1218
1219    fn could_error(&self) -> bool {
1220        match self {
1221            MirScalarExpr::Column(_col, _name) => false,
1222            MirScalarExpr::Literal(row, ..) => row.is_err(),
1223            MirScalarExpr::CallUnmaterializable(_) => true,
1224            MirScalarExpr::CallUnary { func, expr } => func.could_error() || expr.could_error(),
1225            MirScalarExpr::CallBinary { func, expr1, expr2 } => {
1226                func.could_error() || expr1.could_error() || expr2.could_error()
1227            }
1228            MirScalarExpr::CallVariadic { func, exprs } => {
1229                func.could_error() || exprs.iter().any(|e| e.could_error())
1230            }
1231            MirScalarExpr::If { cond, then, els } => {
1232                cond.could_error() || then.could_error() || els.could_error()
1233            }
1234        }
1235    }
1236}
1237
1238impl Columns for MirScalarExpr {
1239    fn column(c: usize) -> Self {
1240        MirScalarExpr::column(c)
1241    }
1242
1243    fn is_column(&self) -> bool {
1244        matches!(self, MirScalarExpr::Column(_col, _name))
1245    }
1246
1247    fn as_column(&self) -> Option<usize> {
1248        if let MirScalarExpr::Column(c, _) = self {
1249            Some(*c)
1250        } else {
1251            None
1252        }
1253    }
1254
1255    fn as_column_mut(&mut self) -> Option<&mut usize> {
1256        if let MirScalarExpr::Column(c, _) = self {
1257            Some(c)
1258        } else {
1259            None
1260        }
1261    }
1262
1263    fn support_into(&self, support: &mut BTreeSet<usize>) {
1264        self.visit_pre(|e| {
1265            if let MirScalarExpr::Column(i, _) = e {
1266                support.insert(*i);
1267            }
1268        });
1269    }
1270
1271    fn visit_columns<F>(&mut self, mut action: F)
1272    where
1273        F: FnMut(&mut usize),
1274    {
1275        self.visit_pre_mut(|e| {
1276            if let MirScalarExpr::Column(col, _) = e {
1277                action(col);
1278            }
1279        });
1280    }
1281}
1282
1283impl VisitChildren<Self> for MirScalarExpr {
1284    fn visit_children<F>(&self, mut f: F)
1285    where
1286        F: FnMut(&Self),
1287    {
1288        use MirScalarExpr::*;
1289        match self {
1290            Column(_, _) | Literal(_, _) | CallUnmaterializable(_) => (),
1291            CallUnary { expr, .. } => {
1292                f(expr);
1293            }
1294            CallBinary { expr1, expr2, .. } => {
1295                f(expr1);
1296                f(expr2);
1297            }
1298            CallVariadic { exprs, .. } => {
1299                for expr in exprs {
1300                    f(expr);
1301                }
1302            }
1303            If { cond, then, els } => {
1304                f(cond);
1305                f(then);
1306                f(els);
1307            }
1308        }
1309    }
1310
1311    fn visit_mut_children<F>(&mut self, mut f: F)
1312    where
1313        F: FnMut(&mut Self),
1314    {
1315        use MirScalarExpr::*;
1316        match self {
1317            Column(_, _) | Literal(_, _) | CallUnmaterializable(_) => (),
1318            CallUnary { expr, .. } => {
1319                f(expr);
1320            }
1321            CallBinary { expr1, expr2, .. } => {
1322                f(expr1);
1323                f(expr2);
1324            }
1325            CallVariadic { exprs, .. } => {
1326                for expr in exprs {
1327                    f(expr);
1328                }
1329            }
1330            If { cond, then, els } => {
1331                f(cond);
1332                f(then);
1333                f(els);
1334            }
1335        }
1336    }
1337
1338    fn try_visit_children<F, E>(&self, mut f: F) -> Result<(), E>
1339    where
1340        F: FnMut(&Self) -> Result<(), E>,
1341    {
1342        use MirScalarExpr::*;
1343        match self {
1344            Column(_, _) | Literal(_, _) | CallUnmaterializable(_) => (),
1345            CallUnary { expr, .. } => {
1346                f(expr)?;
1347            }
1348            CallBinary { expr1, expr2, .. } => {
1349                f(expr1)?;
1350                f(expr2)?;
1351            }
1352            CallVariadic { exprs, .. } => {
1353                for expr in exprs {
1354                    f(expr)?;
1355                }
1356            }
1357            If { cond, then, els } => {
1358                f(cond)?;
1359                f(then)?;
1360                f(els)?;
1361            }
1362        }
1363        Ok(())
1364    }
1365
1366    fn try_visit_mut_children<F, E>(&mut self, mut f: F) -> Result<(), E>
1367    where
1368        F: FnMut(&mut Self) -> Result<(), E>,
1369    {
1370        use MirScalarExpr::*;
1371        match self {
1372            Column(_, _) | Literal(_, _) | CallUnmaterializable(_) => (),
1373            CallUnary { expr, .. } => {
1374                f(expr)?;
1375            }
1376            CallBinary { expr1, expr2, .. } => {
1377                f(expr1)?;
1378                f(expr2)?;
1379            }
1380            CallVariadic { exprs, .. } => {
1381                for expr in exprs {
1382                    f(expr)?;
1383                }
1384            }
1385            If { cond, then, els } => {
1386                f(cond)?;
1387                f(then)?;
1388                f(els)?;
1389            }
1390        }
1391        Ok(())
1392    }
1393
1394    fn children<'a>(&'a self) -> impl DoubleEndedIterator<Item = &'a Self>
1395    where
1396        Self: 'a,
1397    {
1398        self.children()
1399    }
1400
1401    fn children_mut<'a>(&'a mut self) -> impl DoubleEndedIterator<Item = &'a mut Self>
1402    where
1403        Self: 'a,
1404    {
1405        self.children_mut()
1406    }
1407}
1408
1409impl MirScalarExpr {
1410    /// Reports whether this expression contains a non-strict variadic call with an
1411    /// operand that could error, the shape of the open bug CLU-137.
1412    ///
1413    /// `And`, `Or` and `ErrorIfNull` do not evaluate every operand: `Or::eval`
1414    /// returns `true` the moment it sees a true operand and drops any error it
1415    /// collected, `And::eval` does the same for `false`, and `ErrorIfNull`
1416    /// evaluates its message operand only when the first operand is NULL. Yet
1417    /// `reduce_call_variadic`'s generic error propagation replaces the whole call
1418    /// with any operand's literal error, wherever it sits. So `reduce` can turn a
1419    /// row the expression should evaluate into an error, and, because that literal
1420    /// is typed non-nullable, it can go on to license a nullability-dependent
1421    /// rewrite and yield a different *value* rather than an error.
1422    ///
1423    /// Fuzz oracles that compare evaluation across `reduce` use this to skip the
1424    /// shape rather than rediscover CLU-137 on every run. It lives here, next to
1425    /// the fold it describes, so the several oracles that need it cannot drift
1426    /// apart on which functions count as non-strict.
1427    ///
1428    /// Deliberately conservative: it asks whether an operand *could* error rather
1429    /// than whether it already holds a literal error, because `reduce` folds a
1430    /// column-free fallible operand (`1 / 0`) to a literal error first and absorbs
1431    /// it after.
1432    pub fn could_hit_nonstrict_error_fold(&self) -> bool {
1433        let mut hit = false;
1434        self.visit_pre(|e| {
1435            if let MirScalarExpr::CallVariadic { func, exprs } = e {
1436                let non_strict = matches!(
1437                    func,
1438                    VariadicFunc::And(_) | VariadicFunc::Or(_) | VariadicFunc::ErrorIfNull(_)
1439                );
1440                if non_strict && exprs.iter().any(|operand| operand.could_error()) {
1441                    hit = true;
1442                }
1443            }
1444        });
1445        hit
1446    }
1447
1448    /// Iterates through references to child expressions.
1449    pub fn children(&self) -> impl DoubleEndedIterator<Item = &Self> {
1450        let mut first = None;
1451        let mut second = None;
1452        let mut third = None;
1453        let mut variadic = None;
1454
1455        use MirScalarExpr::*;
1456        match self {
1457            Column(_, _) | Literal(_, _) | CallUnmaterializable(_) => (),
1458            CallUnary { expr, .. } => {
1459                first = Some(&**expr);
1460            }
1461            CallBinary { expr1, expr2, .. } => {
1462                first = Some(&**expr1);
1463                second = Some(&**expr2);
1464            }
1465            CallVariadic { exprs, .. } => {
1466                variadic = Some(exprs);
1467            }
1468            If { cond, then, els } => {
1469                first = Some(&**cond);
1470                second = Some(&**then);
1471                third = Some(&**els);
1472            }
1473        }
1474
1475        first
1476            .into_iter()
1477            .chain(second)
1478            .chain(third)
1479            .chain(variadic.into_iter().flatten())
1480    }
1481
1482    /// Iterates through mutable references to child expressions.
1483    pub fn children_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut Self> {
1484        let mut first = None;
1485        let mut second = None;
1486        let mut third = None;
1487        let mut variadic = None;
1488
1489        use MirScalarExpr::*;
1490        match self {
1491            Column(_, _) | Literal(_, _) | CallUnmaterializable(_) => (),
1492            CallUnary { expr, .. } => {
1493                first = Some(&mut **expr);
1494            }
1495            CallBinary { expr1, expr2, .. } => {
1496                first = Some(&mut **expr1);
1497                second = Some(&mut **expr2);
1498            }
1499            CallVariadic { exprs, .. } => {
1500                variadic = Some(exprs);
1501            }
1502            If { cond, then, els } => {
1503                first = Some(&mut **cond);
1504                second = Some(&mut **then);
1505                third = Some(&mut **els);
1506            }
1507        }
1508
1509        first
1510            .into_iter()
1511            .chain(second)
1512            .chain(third)
1513            .chain(variadic.into_iter().flatten())
1514    }
1515
1516    /// Visits all subexpressions in DFS preorder.
1517    pub fn visit_pre<F>(&self, mut f: F)
1518    where
1519        F: FnMut(&Self),
1520    {
1521        let mut worklist = vec![self];
1522        while let Some(e) = worklist.pop() {
1523            f(e);
1524            worklist.extend(e.children().rev());
1525        }
1526    }
1527
1528    /// Iterative pre-order visitor.
1529    pub fn visit_pre_mut<F: FnMut(&mut Self)>(&mut self, mut f: F) {
1530        let mut worklist = vec![self];
1531        while let Some(expr) = worklist.pop() {
1532            f(expr);
1533            worklist.extend(expr.children_mut().rev());
1534        }
1535    }
1536}
1537
1538/// Filter characteristics that are used for ordering join inputs.
1539/// This can be created for a `Vec<MirScalarExpr>`, which represents an AND of predicates.
1540///
1541/// The fields are ordered based on heuristic assumptions about their typical selectivity, so that
1542/// Ord gives the right ordering for join inputs. Bigger is better, i.e., will tend to come earlier
1543/// than other inputs.
1544#[derive(
1545    Eq,
1546    PartialEq,
1547    Ord,
1548    PartialOrd,
1549    Debug,
1550    Clone,
1551    Serialize,
1552    Deserialize,
1553    Hash
1554)]
1555pub struct FilterCharacteristics {
1556    // `<expr> = <literal>` appears in the filter.
1557    // Excludes cases where NOT appears anywhere above the literal equality.
1558    literal_equality: bool,
1559    // (Assuming a random string of lower-case characters, `LIKE 'a%'` has a selectivity of 1/26.)
1560    like: bool,
1561    is_null: bool,
1562    // Number of Vec elements that involve inequality predicates. (A BETWEEN is represented as two
1563    // inequality predicates.)
1564    // Excludes cases where NOT appears around the literal inequality.
1565    // Note that for inequality predicates, some databases assume 1/3 selectivity in the absence of
1566    // concrete statistics.
1567    literal_inequality: usize,
1568    /// Any filter, except ones involving `IS NOT NULL`, because those are too common.
1569    /// Can be true by itself, or any other field being true can also make this true.
1570    /// `NOT LIKE` is only in this category.
1571    /// `!=` is only in this category.
1572    /// `NOT (a = b)` is turned into `!=` by `reduce` before us!
1573    any_filter: bool,
1574}
1575
1576impl BitOrAssign for FilterCharacteristics {
1577    fn bitor_assign(&mut self, rhs: Self) {
1578        self.literal_equality |= rhs.literal_equality;
1579        self.like |= rhs.like;
1580        self.is_null |= rhs.is_null;
1581        self.literal_inequality += rhs.literal_inequality;
1582        self.any_filter |= rhs.any_filter;
1583    }
1584}
1585
1586impl FilterCharacteristics {
1587    pub fn none() -> FilterCharacteristics {
1588        FilterCharacteristics {
1589            literal_equality: false,
1590            like: false,
1591            is_null: false,
1592            literal_inequality: 0,
1593            any_filter: false,
1594        }
1595    }
1596
1597    pub fn explain(&self) -> String {
1598        let mut e = "".to_owned();
1599        if self.literal_equality {
1600            e.push_str("e");
1601        }
1602        if self.like {
1603            e.push_str("l");
1604        }
1605        if self.is_null {
1606            e.push_str("n");
1607        }
1608        for _ in 0..self.literal_inequality {
1609            e.push_str("i");
1610        }
1611        if self.any_filter {
1612            e.push_str("f");
1613        }
1614        e
1615    }
1616
1617    pub fn filter_characteristics(
1618        filters: &Vec<MirScalarExpr>,
1619    ) -> Result<FilterCharacteristics, RecursionLimitError> {
1620        let mut literal_equality = false;
1621        let mut like = false;
1622        let mut is_null = false;
1623        let mut literal_inequality = 0;
1624        let mut any_filter = false;
1625        filters.iter().try_for_each(|f| {
1626            let mut literal_inequality_in_current_filter = false;
1627            let mut is_not_null_in_current_filter = false;
1628            f.visit_pre_with_context(
1629                false,
1630                &mut |not_in_parent_chain, expr| {
1631                    not_in_parent_chain
1632                        || matches!(
1633                            expr,
1634                            MirScalarExpr::CallUnary {
1635                                func: UnaryFunc::Not(func::Not),
1636                                ..
1637                            }
1638                        )
1639                },
1640                &mut |not_in_parent_chain, expr| {
1641                    if !not_in_parent_chain {
1642                        if expr.any_expr_eq_literal().is_some() {
1643                            literal_equality = true;
1644                        }
1645                        if expr.any_expr_ineq_literal() {
1646                            literal_inequality_in_current_filter = true;
1647                        }
1648                        if matches!(
1649                            expr,
1650                            MirScalarExpr::CallUnary {
1651                                func: UnaryFunc::IsLikeMatch(_),
1652                                ..
1653                            }
1654                        ) {
1655                            like = true;
1656                        }
1657                    };
1658                    if matches!(
1659                        expr,
1660                        MirScalarExpr::CallUnary {
1661                            func: UnaryFunc::IsNull(crate::func::IsNull),
1662                            ..
1663                        }
1664                    ) {
1665                        if *not_in_parent_chain {
1666                            is_not_null_in_current_filter = true;
1667                        } else {
1668                            is_null = true;
1669                        }
1670                    }
1671                },
1672            );
1673            if literal_inequality_in_current_filter {
1674                literal_inequality += 1;
1675            }
1676            if !is_not_null_in_current_filter {
1677                // We want to ignore `IS NOT NULL` for `any_filter`.
1678                any_filter = true;
1679            }
1680            Ok(())
1681        })?;
1682        Ok(FilterCharacteristics {
1683            literal_equality,
1684            like,
1685            is_null,
1686            literal_inequality,
1687            any_filter,
1688        })
1689    }
1690
1691    pub fn add_literal_equality(&mut self) {
1692        self.literal_equality = true;
1693    }
1694
1695    pub fn worst_case_scaling_factor(&self) -> f64 {
1696        let mut factor = 1.0;
1697
1698        if self.literal_equality {
1699            factor *= 0.1;
1700        }
1701
1702        if self.is_null {
1703            factor *= 0.1;
1704        }
1705
1706        if self.literal_inequality >= 2 {
1707            factor *= 0.25;
1708        } else if self.literal_inequality == 1 {
1709            factor *= 0.33;
1710        }
1711
1712        // catch various negated filters, treat them pessimistically
1713        if !(self.literal_equality || self.is_null || self.literal_inequality > 0)
1714            && self.any_filter
1715        {
1716            factor *= 0.9;
1717        }
1718
1719        factor
1720    }
1721}
1722
1723#[derive(
1724    Ord,
1725    PartialOrd,
1726    Copy,
1727    Clone,
1728    Debug,
1729    Eq,
1730    PartialEq,
1731    Serialize,
1732    Deserialize,
1733    Hash
1734)]
1735#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))]
1736pub enum DomainLimit {
1737    None,
1738    Inclusive(i64),
1739    Exclusive(i64),
1740}
1741
1742impl RustType<ProtoDomainLimit> for DomainLimit {
1743    fn into_proto(&self) -> ProtoDomainLimit {
1744        use proto_domain_limit::Kind::*;
1745        let kind = match self {
1746            DomainLimit::None => None(()),
1747            DomainLimit::Inclusive(v) => Inclusive(*v),
1748            DomainLimit::Exclusive(v) => Exclusive(*v),
1749        };
1750        ProtoDomainLimit { kind: Some(kind) }
1751    }
1752
1753    fn from_proto(proto: ProtoDomainLimit) -> Result<Self, TryFromProtoError> {
1754        use proto_domain_limit::Kind::*;
1755        if let Some(kind) = proto.kind {
1756            match kind {
1757                None(()) => Ok(DomainLimit::None),
1758                Inclusive(v) => Ok(DomainLimit::Inclusive(v)),
1759                Exclusive(v) => Ok(DomainLimit::Exclusive(v)),
1760            }
1761        } else {
1762            Err(TryFromProtoError::missing_field("ProtoDomainLimit::kind"))
1763        }
1764    }
1765}
1766
1767#[derive(
1768    Ord,
1769    PartialOrd,
1770    Clone,
1771    Debug,
1772    Eq,
1773    PartialEq,
1774    Serialize,
1775    Deserialize,
1776    Hash
1777)]
1778#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))]
1779pub enum EvalError {
1780    CharacterNotValidForEncoding(i32),
1781    CharacterTooLargeForEncoding(i32),
1782    DateBinOutOfRange(Box<str>),
1783    DivisionByZero,
1784    Unsupported {
1785        feature: Box<str>,
1786        discussion_no: Option<usize>,
1787    },
1788    FloatOverflow,
1789    FloatUnderflow,
1790    NumericFieldOverflow,
1791    Float32OutOfRange(Box<str>),
1792    Float64OutOfRange(Box<str>),
1793    Int16OutOfRange(Box<str>),
1794    Int32OutOfRange(Box<str>),
1795    Int64OutOfRange(Box<str>),
1796    UInt16OutOfRange(Box<str>),
1797    UInt32OutOfRange(Box<str>),
1798    UInt64OutOfRange(Box<str>),
1799    MzTimestampOutOfRange(Box<str>),
1800    MzTimestampStepOverflow,
1801    OidOutOfRange(Box<str>),
1802    IntervalOutOfRange(Box<str>),
1803    TimestampCannotBeNan,
1804    TimestampOutOfRange,
1805    DateOutOfRange,
1806    CharOutOfRange,
1807    IndexOutOfRange {
1808        provided: i32,
1809        // The last valid index position, i.e. `v.len() - 1`
1810        valid_end: i32,
1811    },
1812    InvalidBase64Equals,
1813    InvalidBase64Symbol(char),
1814    InvalidBase64EndSequence,
1815    InvalidTimezone(Box<str>),
1816    InvalidTimezoneInterval,
1817    InvalidTimezoneConversion,
1818    InvalidIanaTimezoneId(Box<str>),
1819    InvalidLayer {
1820        max_layer: usize,
1821        val: i64,
1822    },
1823    InvalidArray(InvalidArrayError),
1824    InvalidEncodingName(Box<str>),
1825    InvalidHashAlgorithm(Box<str>),
1826    InvalidByteSequence {
1827        byte_sequence: Box<str>,
1828        encoding_name: Box<str>,
1829    },
1830    InvalidJsonbCast {
1831        from: Box<str>,
1832        to: Box<str>,
1833    },
1834    InvalidRegex(Box<str>),
1835    InvalidRegexFlag(char),
1836    InvalidParameterValue(Box<str>),
1837    InvalidDatePart(Box<str>),
1838    KeyCannotBeNull,
1839    NegSqrt,
1840    NegLimit,
1841    NullCharacterNotPermitted,
1842    UnknownUnits(Box<str>),
1843    UnsupportedUnits(Box<str>, Box<str>),
1844    UnterminatedLikeEscapeSequence,
1845    Parse(ParseError),
1846    ParseHex(ParseHexError),
1847    Internal(Box<str>),
1848    InfinityOutOfDomain(Box<str>),
1849    NegativeOutOfDomain(Box<str>),
1850    ZeroOutOfDomain(Box<str>),
1851    OutOfDomain(DomainLimit, DomainLimit, Box<str>),
1852    ComplexOutOfRange(Box<str>),
1853    MultipleRowsFromSubquery,
1854    NegativeRowsFromSubquery,
1855    Undefined(Box<str>),
1856    LikePatternTooLong,
1857    LikeEscapeTooLong,
1858    StringValueTooLong {
1859        target_type: Box<str>,
1860        length: usize,
1861    },
1862    MultidimensionalArrayRemovalNotSupported,
1863    IncompatibleArrayDimensions {
1864        dims: Option<(usize, usize)>,
1865    },
1866    TypeFromOid(Box<str>),
1867    InvalidRange(InvalidRangeError),
1868    InvalidRoleId(Box<str>),
1869    InvalidPrivileges(Box<str>),
1870    InvalidCatalogJson(Box<str>),
1871    LetRecLimitExceeded(Box<str>),
1872    MultiDimensionalArraySearch,
1873    MustNotBeNull(Box<str>),
1874    InvalidIdentifier {
1875        ident: Box<str>,
1876        detail: Option<Box<str>>,
1877    },
1878    ArrayFillWrongArraySubscripts,
1879    // TODO: propagate this check more widely throughout the expr crate
1880    MaxArraySizeExceeded(usize),
1881    DateDiffOverflow {
1882        unit: Box<str>,
1883        a: Box<str>,
1884        b: Box<str>,
1885    },
1886    // The error for ErrorIfNull; this should not be used in other contexts as a generic error
1887    // printer.
1888    IfNullError(Box<str>),
1889    LengthTooLarge,
1890    // A budgeted `RowArena` (`mz_repr::RowArena::with_budget`) exceeded its budget while an
1891    // expression was being evaluated. Only a budgeted arena raises this, so it never arises in a
1892    // dataflow, only on the webhook `CHECK` path that runs user expressions in `environmentd`.
1893    TempStorageBudgetExceeded,
1894    AclArrayNullElement,
1895    MzAclArrayNullElement,
1896    PrettyError(Box<str>),
1897    RedactError(Box<str>),
1898}
1899
1900impl fmt::Display for EvalError {
1901    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1902        match self {
1903            EvalError::CharacterNotValidForEncoding(v) => {
1904                write!(f, "requested character not valid for encoding: {v}")
1905            }
1906            EvalError::CharacterTooLargeForEncoding(v) => {
1907                write!(f, "requested character too large for encoding: {v}")
1908            }
1909            EvalError::DateBinOutOfRange(message) => f.write_str(message),
1910            EvalError::DivisionByZero => f.write_str("division by zero"),
1911            EvalError::Unsupported {
1912                feature,
1913                discussion_no,
1914            } => {
1915                write!(f, "{} not yet supported", feature)?;
1916                if let Some(discussion_no) = discussion_no {
1917                    write!(
1918                        f,
1919                        ", see https://github.com/MaterializeInc/materialize/discussions/{} for more details",
1920                        discussion_no
1921                    )?;
1922                }
1923                Ok(())
1924            }
1925            EvalError::FloatOverflow => f.write_str("value out of range: overflow"),
1926            EvalError::FloatUnderflow => f.write_str("value out of range: underflow"),
1927            EvalError::NumericFieldOverflow => f.write_str("numeric field overflow"),
1928            EvalError::Float32OutOfRange(val) => write!(f, "{} real out of range", val.quoted()),
1929            EvalError::Float64OutOfRange(val) => {
1930                write!(f, "{} double precision out of range", val.quoted())
1931            }
1932            EvalError::Int16OutOfRange(val) => write!(f, "{} smallint out of range", val.quoted()),
1933            EvalError::Int32OutOfRange(val) => write!(f, "{} integer out of range", val.quoted()),
1934            EvalError::Int64OutOfRange(val) => write!(f, "{} bigint out of range", val.quoted()),
1935            EvalError::UInt16OutOfRange(val) => write!(f, "{} uint2 out of range", val.quoted()),
1936            EvalError::UInt32OutOfRange(val) => write!(f, "{} uint4 out of range", val.quoted()),
1937            EvalError::UInt64OutOfRange(val) => write!(f, "{} uint8 out of range", val.quoted()),
1938            EvalError::MzTimestampOutOfRange(val) => {
1939                write!(f, "{} mz_timestamp out of range", val.quoted())
1940            }
1941            EvalError::MzTimestampStepOverflow => f.write_str("step mz_timestamp overflow"),
1942            EvalError::OidOutOfRange(val) => write!(f, "{} OID out of range", val.quoted()),
1943            EvalError::IntervalOutOfRange(val) => {
1944                write!(f, "{} interval out of range", val.quoted())
1945            }
1946            EvalError::TimestampCannotBeNan => f.write_str("timestamp cannot be NaN"),
1947            EvalError::TimestampOutOfRange => f.write_str("timestamp out of range"),
1948            EvalError::DateOutOfRange => f.write_str("date out of range"),
1949            EvalError::CharOutOfRange => f.write_str("\"char\" out of range"),
1950            EvalError::IndexOutOfRange {
1951                provided,
1952                valid_end,
1953            } => write!(f, "index {provided} out of valid range, 0..{valid_end}",),
1954            EvalError::InvalidBase64Equals => {
1955                f.write_str("unexpected \"=\" while decoding base64 sequence")
1956            }
1957            EvalError::InvalidBase64Symbol(c) => write!(
1958                f,
1959                "invalid symbol \"{}\" found while decoding base64 sequence",
1960                c.escape_default()
1961            ),
1962            EvalError::InvalidBase64EndSequence => f.write_str("invalid base64 end sequence"),
1963            EvalError::InvalidJsonbCast { from, to } => {
1964                write!(f, "cannot cast jsonb {} to type {}", from, to)
1965            }
1966            EvalError::InvalidTimezone(tz) => write!(f, "invalid time zone '{}'", tz),
1967            EvalError::InvalidTimezoneInterval => {
1968                f.write_str("timezone interval must not contain months or years")
1969            }
1970            EvalError::InvalidTimezoneConversion => f.write_str("invalid timezone conversion"),
1971            EvalError::InvalidIanaTimezoneId(tz) => {
1972                write!(f, "invalid IANA Time Zone Database identifier: '{}'", tz)
1973            }
1974            EvalError::InvalidLayer { max_layer, val } => write!(
1975                f,
1976                "invalid layer: {}; must use value within [1, {}]",
1977                val, max_layer
1978            ),
1979            EvalError::InvalidArray(e) => e.fmt(f),
1980            EvalError::InvalidEncodingName(name) => write!(f, "invalid encoding name '{}'", name),
1981            EvalError::InvalidHashAlgorithm(alg) => write!(f, "invalid hash algorithm '{}'", alg),
1982            EvalError::InvalidByteSequence {
1983                byte_sequence,
1984                encoding_name,
1985            } => write!(
1986                f,
1987                "invalid byte sequence '{}' for encoding '{}'",
1988                byte_sequence, encoding_name
1989            ),
1990            EvalError::InvalidDatePart(part) => write!(f, "invalid datepart {}", part.quoted()),
1991            EvalError::KeyCannotBeNull => f.write_str("key cannot be null"),
1992            EvalError::NegSqrt => f.write_str("cannot take square root of a negative number"),
1993            EvalError::NegLimit => f.write_str("LIMIT must not be negative"),
1994            EvalError::NullCharacterNotPermitted => f.write_str("null character not permitted"),
1995            EvalError::InvalidRegex(e) => write!(f, "invalid regular expression: {}", e),
1996            EvalError::InvalidRegexFlag(c) => write!(f, "invalid regular expression flag: {}", c),
1997            EvalError::InvalidParameterValue(s) => f.write_str(s),
1998            EvalError::UnknownUnits(units) => write!(f, "unit '{}' not recognized", units),
1999            EvalError::UnsupportedUnits(units, typ) => {
2000                write!(f, "unit '{}' not supported for type {}", units, typ)
2001            }
2002            EvalError::UnterminatedLikeEscapeSequence => {
2003                f.write_str("unterminated escape sequence in LIKE")
2004            }
2005            EvalError::Parse(e) => e.fmt(f),
2006            EvalError::PrettyError(e) => e.fmt(f),
2007            EvalError::RedactError(e) => e.fmt(f),
2008            EvalError::ParseHex(e) => e.fmt(f),
2009            EvalError::Internal(s) => write!(f, "internal error: {}", s),
2010            EvalError::InfinityOutOfDomain(s) => {
2011                write!(f, "function {} is only defined for finite arguments", s)
2012            }
2013            EvalError::NegativeOutOfDomain(s) => {
2014                write!(f, "function {} is not defined for negative numbers", s)
2015            }
2016            EvalError::ZeroOutOfDomain(s) => {
2017                write!(f, "function {} is not defined for zero", s)
2018            }
2019            EvalError::OutOfDomain(lower, upper, s) => {
2020                use DomainLimit::*;
2021                write!(f, "function {s} is defined for numbers ")?;
2022                match (lower, upper) {
2023                    (Inclusive(n), None) => write!(f, "greater than or equal to {n}"),
2024                    (Exclusive(n), None) => write!(f, "greater than {n}"),
2025                    (None, Inclusive(n)) => write!(f, "less than or equal to {n}"),
2026                    (None, Exclusive(n)) => write!(f, "less than {n}"),
2027                    (Inclusive(lo), Inclusive(hi)) => write!(f, "between {lo} and {hi} inclusive"),
2028                    (Exclusive(lo), Exclusive(hi)) => write!(f, "between {lo} and {hi} exclusive"),
2029                    (Inclusive(lo), Exclusive(hi)) => {
2030                        write!(f, "between {lo} inclusive and {hi} exclusive")
2031                    }
2032                    (Exclusive(lo), Inclusive(hi)) => {
2033                        write!(f, "between {lo} exclusive and {hi} inclusive")
2034                    }
2035                    // No caller constructs an unbounded domain, but a corrupted or
2036                    // forged `ProtoEvalError` decodes into one. Render it instead of
2037                    // panicking: `DataflowErrorSer::Display` decodes errors straight
2038                    // out of a persist shard and Displays them on the index peek
2039                    // path, so a panicking arm here wedges the dataflow on every
2040                    // retry rather than producing a bad error message once.
2041                    (None, None) => write!(f, "in an unspecified range"),
2042                }
2043            }
2044            EvalError::ComplexOutOfRange(s) => {
2045                write!(f, "function {} cannot return complex numbers", s)
2046            }
2047            EvalError::MultipleRowsFromSubquery => {
2048                write!(f, "more than one record produced in subquery")
2049            }
2050            EvalError::NegativeRowsFromSubquery => {
2051                write!(f, "negative number of rows produced in subquery")
2052            }
2053            EvalError::Undefined(s) => {
2054                write!(f, "{} is undefined", s)
2055            }
2056            EvalError::LikePatternTooLong => {
2057                write!(f, "LIKE pattern exceeds maximum length")
2058            }
2059            EvalError::LikeEscapeTooLong => {
2060                write!(f, "invalid escape string")
2061            }
2062            EvalError::StringValueTooLong {
2063                target_type,
2064                length,
2065            } => {
2066                write!(f, "value too long for type {}({})", target_type, length)
2067            }
2068            EvalError::MultidimensionalArrayRemovalNotSupported => {
2069                write!(
2070                    f,
2071                    "removing elements from multidimensional arrays is not supported"
2072                )
2073            }
2074            EvalError::IncompatibleArrayDimensions { dims: _ } => {
2075                write!(f, "cannot concatenate incompatible arrays")
2076            }
2077            EvalError::TypeFromOid(msg) => write!(f, "{msg}"),
2078            EvalError::InvalidRange(e) => e.fmt(f),
2079            EvalError::InvalidRoleId(msg) => write!(f, "{msg}"),
2080            EvalError::InvalidPrivileges(privilege) => {
2081                write!(f, "unrecognized privilege type: {privilege}")
2082            }
2083            EvalError::InvalidCatalogJson(msg) => {
2084                write!(f, "invalid catalog JSON: {msg}")
2085            }
2086            EvalError::LetRecLimitExceeded(max_iters) => {
2087                write!(
2088                    f,
2089                    "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.)",
2090                    max_iters
2091                )
2092            }
2093            EvalError::MultiDimensionalArraySearch => write!(
2094                f,
2095                "searching for elements in multidimensional arrays is not supported"
2096            ),
2097            EvalError::MustNotBeNull(v) => write!(f, "{v} must not be null"),
2098            EvalError::InvalidIdentifier { ident, .. } => {
2099                write!(f, "string is not a valid identifier: {}", ident.quoted())
2100            }
2101            EvalError::ArrayFillWrongArraySubscripts => {
2102                f.write_str("wrong number of array subscripts")
2103            }
2104            EvalError::MaxArraySizeExceeded(max_size) => {
2105                write!(
2106                    f,
2107                    "array size exceeds the maximum allowed ({max_size} bytes)"
2108                )
2109            }
2110            EvalError::DateDiffOverflow { unit, a, b } => {
2111                write!(f, "datediff overflow, {unit} of {a}, {b}")
2112            }
2113            EvalError::IfNullError(s) => f.write_str(s),
2114            EvalError::LengthTooLarge => write!(f, "requested length too large"),
2115            EvalError::TempStorageBudgetExceeded => {
2116                write!(f, "expression exceeded its temporary storage limit")
2117            }
2118            EvalError::AclArrayNullElement => write!(f, "ACL arrays must not contain null values"),
2119            EvalError::MzAclArrayNullElement => {
2120                write!(f, "MZ_ACL arrays must not contain null values")
2121            }
2122        }
2123    }
2124}
2125
2126impl EvalError {
2127    pub fn detail(&self) -> Option<String> {
2128        match self {
2129            EvalError::IncompatibleArrayDimensions { dims: None } => Some(
2130                "Arrays with differing dimensions are not compatible for concatenation.".into(),
2131            ),
2132            EvalError::IncompatibleArrayDimensions {
2133                dims: Some((a_dims, b_dims)),
2134            } => Some(format!(
2135                "Arrays of {} and {} dimensions are not compatible for concatenation.",
2136                a_dims, b_dims
2137            )),
2138            EvalError::InvalidIdentifier { detail, .. } => detail.as_deref().map(Into::into),
2139            EvalError::ArrayFillWrongArraySubscripts => {
2140                Some("Low bound array has different size than dimensions array.".into())
2141            }
2142            _ => None,
2143        }
2144    }
2145
2146    pub fn hint(&self) -> Option<String> {
2147        match self {
2148            EvalError::InvalidBase64EndSequence => Some(
2149                "Input data is missing padding, is truncated, or is otherwise corrupted.".into(),
2150            ),
2151            EvalError::LikeEscapeTooLong => {
2152                Some("Escape string must be empty or one character.".into())
2153            }
2154            EvalError::MzTimestampOutOfRange(_) => Some(
2155                "Integer, numeric, and text casts to mz_timestamp must be in the form of whole \
2156                milliseconds since the Unix epoch. Values with fractional parts cannot be \
2157                converted to mz_timestamp."
2158                    .into(),
2159            ),
2160            _ => None,
2161        }
2162    }
2163}
2164
2165impl std::error::Error for EvalError {}
2166
2167impl From<ParseError> for EvalError {
2168    fn from(e: ParseError) -> EvalError {
2169        EvalError::Parse(e)
2170    }
2171}
2172
2173impl From<ParseHexError> for EvalError {
2174    fn from(e: ParseHexError) -> EvalError {
2175        EvalError::ParseHex(e)
2176    }
2177}
2178
2179impl From<InvalidArrayError> for EvalError {
2180    fn from(e: InvalidArrayError) -> EvalError {
2181        EvalError::InvalidArray(e)
2182    }
2183}
2184
2185impl From<RegexCompilationError> for EvalError {
2186    fn from(e: RegexCompilationError) -> EvalError {
2187        EvalError::InvalidRegex(e.to_string().into())
2188    }
2189}
2190
2191impl From<TypeFromOidError> for EvalError {
2192    fn from(e: TypeFromOidError) -> EvalError {
2193        EvalError::TypeFromOid(e.to_string().into())
2194    }
2195}
2196
2197impl From<DateError> for EvalError {
2198    fn from(e: DateError) -> EvalError {
2199        match e {
2200            DateError::OutOfRange => EvalError::DateOutOfRange,
2201        }
2202    }
2203}
2204
2205impl From<TimestampError> for EvalError {
2206    fn from(e: TimestampError) -> EvalError {
2207        match e {
2208            TimestampError::OutOfRange => EvalError::TimestampOutOfRange,
2209        }
2210    }
2211}
2212
2213impl From<InvalidRangeError> for EvalError {
2214    fn from(e: InvalidRangeError) -> EvalError {
2215        EvalError::InvalidRange(e)
2216    }
2217}
2218
2219impl RustType<ProtoEvalError> for EvalError {
2220    fn into_proto(&self) -> ProtoEvalError {
2221        use proto_eval_error::Kind::*;
2222        use proto_eval_error::*;
2223        let kind = match self {
2224            EvalError::CharacterNotValidForEncoding(v) => CharacterNotValidForEncoding(*v),
2225            EvalError::CharacterTooLargeForEncoding(v) => CharacterTooLargeForEncoding(*v),
2226            EvalError::DateBinOutOfRange(v) => DateBinOutOfRange(v.into_proto()),
2227            EvalError::DivisionByZero => DivisionByZero(()),
2228            EvalError::Unsupported {
2229                feature,
2230                discussion_no,
2231            } => Unsupported(ProtoUnsupported {
2232                feature: feature.into_proto(),
2233                discussion_no: discussion_no.into_proto(),
2234            }),
2235            EvalError::FloatOverflow => FloatOverflow(()),
2236            EvalError::FloatUnderflow => FloatUnderflow(()),
2237            EvalError::NumericFieldOverflow => NumericFieldOverflow(()),
2238            EvalError::Float32OutOfRange(val) => Float32OutOfRange(ProtoValueOutOfRange {
2239                value: val.to_string(),
2240            }),
2241            EvalError::Float64OutOfRange(val) => Float64OutOfRange(ProtoValueOutOfRange {
2242                value: val.to_string(),
2243            }),
2244            EvalError::Int16OutOfRange(val) => Int16OutOfRange(ProtoValueOutOfRange {
2245                value: val.to_string(),
2246            }),
2247            EvalError::Int32OutOfRange(val) => Int32OutOfRange(ProtoValueOutOfRange {
2248                value: val.to_string(),
2249            }),
2250            EvalError::Int64OutOfRange(val) => Int64OutOfRange(ProtoValueOutOfRange {
2251                value: val.to_string(),
2252            }),
2253            EvalError::UInt16OutOfRange(val) => Uint16OutOfRange(ProtoValueOutOfRange {
2254                value: val.to_string(),
2255            }),
2256            EvalError::UInt32OutOfRange(val) => Uint32OutOfRange(ProtoValueOutOfRange {
2257                value: val.to_string(),
2258            }),
2259            EvalError::UInt64OutOfRange(val) => Uint64OutOfRange(ProtoValueOutOfRange {
2260                value: val.to_string(),
2261            }),
2262            EvalError::MzTimestampOutOfRange(val) => MzTimestampOutOfRange(ProtoValueOutOfRange {
2263                value: val.to_string(),
2264            }),
2265            EvalError::MzTimestampStepOverflow => MzTimestampStepOverflow(()),
2266            EvalError::OidOutOfRange(val) => OidOutOfRange(ProtoValueOutOfRange {
2267                value: val.to_string(),
2268            }),
2269            EvalError::IntervalOutOfRange(val) => IntervalOutOfRange(ProtoValueOutOfRange {
2270                value: val.to_string(),
2271            }),
2272            EvalError::TimestampCannotBeNan => TimestampCannotBeNan(()),
2273            EvalError::TimestampOutOfRange => TimestampOutOfRange(()),
2274            EvalError::DateOutOfRange => DateOutOfRange(()),
2275            EvalError::CharOutOfRange => CharOutOfRange(()),
2276            EvalError::IndexOutOfRange {
2277                provided,
2278                valid_end,
2279            } => IndexOutOfRange(ProtoIndexOutOfRange {
2280                provided: *provided,
2281                valid_end: *valid_end,
2282            }),
2283            EvalError::InvalidBase64Equals => InvalidBase64Equals(()),
2284            EvalError::InvalidBase64Symbol(sym) => InvalidBase64Symbol(sym.into_proto()),
2285            EvalError::InvalidBase64EndSequence => InvalidBase64EndSequence(()),
2286            EvalError::InvalidTimezone(tz) => InvalidTimezone(tz.into_proto()),
2287            EvalError::InvalidTimezoneInterval => InvalidTimezoneInterval(()),
2288            EvalError::InvalidTimezoneConversion => InvalidTimezoneConversion(()),
2289            EvalError::InvalidLayer { max_layer, val } => InvalidLayer(ProtoInvalidLayer {
2290                max_layer: max_layer.into_proto(),
2291                val: *val,
2292            }),
2293            EvalError::InvalidArray(error) => InvalidArray(error.into_proto()),
2294            EvalError::InvalidEncodingName(v) => InvalidEncodingName(v.into_proto()),
2295            EvalError::InvalidHashAlgorithm(v) => InvalidHashAlgorithm(v.into_proto()),
2296            EvalError::InvalidByteSequence {
2297                byte_sequence,
2298                encoding_name,
2299            } => InvalidByteSequence(ProtoInvalidByteSequence {
2300                byte_sequence: byte_sequence.into_proto(),
2301                encoding_name: encoding_name.into_proto(),
2302            }),
2303            EvalError::InvalidJsonbCast { from, to } => InvalidJsonbCast(ProtoInvalidJsonbCast {
2304                from: from.into_proto(),
2305                to: to.into_proto(),
2306            }),
2307            EvalError::InvalidRegex(v) => InvalidRegex(v.into_proto()),
2308            EvalError::InvalidRegexFlag(v) => InvalidRegexFlag(v.into_proto()),
2309            EvalError::InvalidParameterValue(v) => InvalidParameterValue(v.into_proto()),
2310            EvalError::InvalidDatePart(part) => InvalidDatePart(part.into_proto()),
2311            EvalError::KeyCannotBeNull => KeyCannotBeNull(()),
2312            EvalError::NegSqrt => NegSqrt(()),
2313            EvalError::NegLimit => NegLimit(()),
2314            EvalError::NullCharacterNotPermitted => NullCharacterNotPermitted(()),
2315            EvalError::UnknownUnits(v) => UnknownUnits(v.into_proto()),
2316            EvalError::UnsupportedUnits(units, typ) => UnsupportedUnits(ProtoUnsupportedUnits {
2317                units: units.into_proto(),
2318                typ: typ.into_proto(),
2319            }),
2320            EvalError::UnterminatedLikeEscapeSequence => UnterminatedLikeEscapeSequence(()),
2321            EvalError::Parse(error) => Parse(error.into_proto()),
2322            EvalError::PrettyError(error) => PrettyError(error.into_proto()),
2323            EvalError::RedactError(error) => RedactError(error.into_proto()),
2324            EvalError::ParseHex(error) => ParseHex(error.into_proto()),
2325            EvalError::Internal(v) => Internal(v.into_proto()),
2326            EvalError::InfinityOutOfDomain(v) => InfinityOutOfDomain(v.into_proto()),
2327            EvalError::NegativeOutOfDomain(v) => NegativeOutOfDomain(v.into_proto()),
2328            EvalError::ZeroOutOfDomain(v) => ZeroOutOfDomain(v.into_proto()),
2329            EvalError::OutOfDomain(lower, upper, id) => OutOfDomain(ProtoOutOfDomain {
2330                lower: Some(lower.into_proto()),
2331                upper: Some(upper.into_proto()),
2332                id: id.into_proto(),
2333            }),
2334            EvalError::ComplexOutOfRange(v) => ComplexOutOfRange(v.into_proto()),
2335            EvalError::MultipleRowsFromSubquery => MultipleRowsFromSubquery(()),
2336            EvalError::NegativeRowsFromSubquery => NegativeRowsFromSubquery(()),
2337            EvalError::Undefined(v) => Undefined(v.into_proto()),
2338            EvalError::LikePatternTooLong => LikePatternTooLong(()),
2339            EvalError::LikeEscapeTooLong => LikeEscapeTooLong(()),
2340            EvalError::StringValueTooLong {
2341                target_type,
2342                length,
2343            } => StringValueTooLong(ProtoStringValueTooLong {
2344                target_type: target_type.into_proto(),
2345                length: length.into_proto(),
2346            }),
2347            EvalError::MultidimensionalArrayRemovalNotSupported => {
2348                MultidimensionalArrayRemovalNotSupported(())
2349            }
2350            EvalError::IncompatibleArrayDimensions { dims } => {
2351                IncompatibleArrayDimensions(ProtoIncompatibleArrayDimensions {
2352                    dims: dims.into_proto(),
2353                })
2354            }
2355            EvalError::TypeFromOid(v) => TypeFromOid(v.into_proto()),
2356            EvalError::InvalidRange(error) => InvalidRange(error.into_proto()),
2357            EvalError::InvalidRoleId(v) => InvalidRoleId(v.into_proto()),
2358            EvalError::InvalidPrivileges(v) => InvalidPrivileges(v.into_proto()),
2359            EvalError::InvalidCatalogJson(v) => InvalidCatalogJson(v.into_proto()),
2360            EvalError::LetRecLimitExceeded(v) => WmrRecursionLimitExceeded(v.into_proto()),
2361            EvalError::MultiDimensionalArraySearch => MultiDimensionalArraySearch(()),
2362            EvalError::MustNotBeNull(v) => MustNotBeNull(v.into_proto()),
2363            EvalError::InvalidIdentifier { ident, detail } => {
2364                InvalidIdentifier(ProtoInvalidIdentifier {
2365                    ident: ident.into_proto(),
2366                    detail: detail.into_proto(),
2367                })
2368            }
2369            EvalError::ArrayFillWrongArraySubscripts => ArrayFillWrongArraySubscripts(()),
2370            EvalError::MaxArraySizeExceeded(max_size) => {
2371                MaxArraySizeExceeded(u64::cast_from(*max_size))
2372            }
2373            EvalError::DateDiffOverflow { unit, a, b } => DateDiffOverflow(ProtoDateDiffOverflow {
2374                unit: unit.into_proto(),
2375                a: a.into_proto(),
2376                b: b.into_proto(),
2377            }),
2378            EvalError::IfNullError(s) => IfNullError(s.into_proto()),
2379            EvalError::LengthTooLarge => LengthTooLarge(()),
2380            EvalError::TempStorageBudgetExceeded => TempStorageBudgetExceeded(()),
2381            EvalError::AclArrayNullElement => AclArrayNullElement(()),
2382            EvalError::MzAclArrayNullElement => MzAclArrayNullElement(()),
2383            EvalError::InvalidIanaTimezoneId(s) => InvalidIanaTimezoneId(s.into_proto()),
2384        };
2385        ProtoEvalError { kind: Some(kind) }
2386    }
2387
2388    fn from_proto(proto: ProtoEvalError) -> Result<Self, TryFromProtoError> {
2389        use proto_eval_error::Kind::*;
2390        match proto.kind {
2391            Some(kind) => match kind {
2392                CharacterNotValidForEncoding(v) => Ok(EvalError::CharacterNotValidForEncoding(v)),
2393                CharacterTooLargeForEncoding(v) => Ok(EvalError::CharacterTooLargeForEncoding(v)),
2394                DateBinOutOfRange(v) => Ok(EvalError::DateBinOutOfRange(v.into())),
2395                DivisionByZero(()) => Ok(EvalError::DivisionByZero),
2396                Unsupported(v) => Ok(EvalError::Unsupported {
2397                    feature: v.feature.into(),
2398                    discussion_no: v.discussion_no.into_rust()?,
2399                }),
2400                FloatOverflow(()) => Ok(EvalError::FloatOverflow),
2401                FloatUnderflow(()) => Ok(EvalError::FloatUnderflow),
2402                NumericFieldOverflow(()) => Ok(EvalError::NumericFieldOverflow),
2403                Float32OutOfRange(val) => Ok(EvalError::Float32OutOfRange(val.value.into())),
2404                Float64OutOfRange(val) => Ok(EvalError::Float64OutOfRange(val.value.into())),
2405                Int16OutOfRange(val) => Ok(EvalError::Int16OutOfRange(val.value.into())),
2406                Int32OutOfRange(val) => Ok(EvalError::Int32OutOfRange(val.value.into())),
2407                Int64OutOfRange(val) => Ok(EvalError::Int64OutOfRange(val.value.into())),
2408                Uint16OutOfRange(val) => Ok(EvalError::UInt16OutOfRange(val.value.into())),
2409                Uint32OutOfRange(val) => Ok(EvalError::UInt32OutOfRange(val.value.into())),
2410                Uint64OutOfRange(val) => Ok(EvalError::UInt64OutOfRange(val.value.into())),
2411                MzTimestampOutOfRange(val) => {
2412                    Ok(EvalError::MzTimestampOutOfRange(val.value.into()))
2413                }
2414                MzTimestampStepOverflow(()) => Ok(EvalError::MzTimestampStepOverflow),
2415                OidOutOfRange(val) => Ok(EvalError::OidOutOfRange(val.value.into())),
2416                IntervalOutOfRange(val) => Ok(EvalError::IntervalOutOfRange(val.value.into())),
2417                TimestampCannotBeNan(()) => Ok(EvalError::TimestampCannotBeNan),
2418                TimestampOutOfRange(()) => Ok(EvalError::TimestampOutOfRange),
2419                DateOutOfRange(()) => Ok(EvalError::DateOutOfRange),
2420                CharOutOfRange(()) => Ok(EvalError::CharOutOfRange),
2421                IndexOutOfRange(v) => Ok(EvalError::IndexOutOfRange {
2422                    provided: v.provided,
2423                    valid_end: v.valid_end,
2424                }),
2425                InvalidBase64Equals(()) => Ok(EvalError::InvalidBase64Equals),
2426                InvalidBase64Symbol(v) => char::from_proto(v).map(EvalError::InvalidBase64Symbol),
2427                InvalidBase64EndSequence(()) => Ok(EvalError::InvalidBase64EndSequence),
2428                InvalidTimezone(v) => Ok(EvalError::InvalidTimezone(v.into())),
2429                InvalidTimezoneInterval(()) => Ok(EvalError::InvalidTimezoneInterval),
2430                InvalidTimezoneConversion(()) => Ok(EvalError::InvalidTimezoneConversion),
2431                InvalidLayer(v) => Ok(EvalError::InvalidLayer {
2432                    max_layer: usize::from_proto(v.max_layer)?,
2433                    val: v.val,
2434                }),
2435                InvalidArray(error) => Ok(EvalError::InvalidArray(error.into_rust()?)),
2436                InvalidEncodingName(v) => Ok(EvalError::InvalidEncodingName(v.into())),
2437                InvalidHashAlgorithm(v) => Ok(EvalError::InvalidHashAlgorithm(v.into())),
2438                InvalidByteSequence(v) => Ok(EvalError::InvalidByteSequence {
2439                    byte_sequence: v.byte_sequence.into(),
2440                    encoding_name: v.encoding_name.into(),
2441                }),
2442                InvalidJsonbCast(v) => Ok(EvalError::InvalidJsonbCast {
2443                    from: v.from.into(),
2444                    to: v.to.into(),
2445                }),
2446                InvalidRegex(v) => Ok(EvalError::InvalidRegex(v.into())),
2447                InvalidRegexFlag(v) => Ok(EvalError::InvalidRegexFlag(char::from_proto(v)?)),
2448                InvalidParameterValue(v) => Ok(EvalError::InvalidParameterValue(v.into())),
2449                InvalidDatePart(part) => Ok(EvalError::InvalidDatePart(part.into())),
2450                KeyCannotBeNull(()) => Ok(EvalError::KeyCannotBeNull),
2451                NegSqrt(()) => Ok(EvalError::NegSqrt),
2452                NegLimit(()) => Ok(EvalError::NegLimit),
2453                NullCharacterNotPermitted(()) => Ok(EvalError::NullCharacterNotPermitted),
2454                UnknownUnits(v) => Ok(EvalError::UnknownUnits(v.into())),
2455                UnsupportedUnits(v) => {
2456                    Ok(EvalError::UnsupportedUnits(v.units.into(), v.typ.into()))
2457                }
2458                UnterminatedLikeEscapeSequence(()) => Ok(EvalError::UnterminatedLikeEscapeSequence),
2459                Parse(error) => Ok(EvalError::Parse(error.into_rust()?)),
2460                ParseHex(error) => Ok(EvalError::ParseHex(error.into_rust()?)),
2461                Internal(v) => Ok(EvalError::Internal(v.into())),
2462                InfinityOutOfDomain(v) => Ok(EvalError::InfinityOutOfDomain(v.into())),
2463                NegativeOutOfDomain(v) => Ok(EvalError::NegativeOutOfDomain(v.into())),
2464                ZeroOutOfDomain(v) => Ok(EvalError::ZeroOutOfDomain(v.into())),
2465                OutOfDomain(v) => Ok(EvalError::OutOfDomain(
2466                    v.lower.into_rust_if_some("ProtoDomainLimit::lower")?,
2467                    v.upper.into_rust_if_some("ProtoDomainLimit::upper")?,
2468                    v.id.into(),
2469                )),
2470                ComplexOutOfRange(v) => Ok(EvalError::ComplexOutOfRange(v.into())),
2471                MultipleRowsFromSubquery(()) => Ok(EvalError::MultipleRowsFromSubquery),
2472                NegativeRowsFromSubquery(()) => Ok(EvalError::NegativeRowsFromSubquery),
2473                Undefined(v) => Ok(EvalError::Undefined(v.into())),
2474                LikePatternTooLong(()) => Ok(EvalError::LikePatternTooLong),
2475                LikeEscapeTooLong(()) => Ok(EvalError::LikeEscapeTooLong),
2476                StringValueTooLong(v) => Ok(EvalError::StringValueTooLong {
2477                    target_type: v.target_type.into(),
2478                    length: usize::from_proto(v.length)?,
2479                }),
2480                MultidimensionalArrayRemovalNotSupported(()) => {
2481                    Ok(EvalError::MultidimensionalArrayRemovalNotSupported)
2482                }
2483                IncompatibleArrayDimensions(v) => Ok(EvalError::IncompatibleArrayDimensions {
2484                    dims: v.dims.into_rust()?,
2485                }),
2486                TypeFromOid(v) => Ok(EvalError::TypeFromOid(v.into())),
2487                InvalidRange(e) => Ok(EvalError::InvalidRange(e.into_rust()?)),
2488                InvalidRoleId(v) => Ok(EvalError::InvalidRoleId(v.into())),
2489                InvalidPrivileges(v) => Ok(EvalError::InvalidPrivileges(v.into())),
2490                InvalidCatalogJson(v) => Ok(EvalError::InvalidCatalogJson(v.into())),
2491                WmrRecursionLimitExceeded(v) => Ok(EvalError::LetRecLimitExceeded(v.into())),
2492                MultiDimensionalArraySearch(()) => Ok(EvalError::MultiDimensionalArraySearch),
2493                MustNotBeNull(v) => Ok(EvalError::MustNotBeNull(v.into())),
2494                InvalidIdentifier(v) => Ok(EvalError::InvalidIdentifier {
2495                    ident: v.ident.into(),
2496                    detail: v.detail.into_rust()?,
2497                }),
2498                ArrayFillWrongArraySubscripts(()) => Ok(EvalError::ArrayFillWrongArraySubscripts),
2499                MaxArraySizeExceeded(max_size) => {
2500                    Ok(EvalError::MaxArraySizeExceeded(usize::cast_from(max_size)))
2501                }
2502                DateDiffOverflow(v) => Ok(EvalError::DateDiffOverflow {
2503                    unit: v.unit.into(),
2504                    a: v.a.into(),
2505                    b: v.b.into(),
2506                }),
2507                IfNullError(v) => Ok(EvalError::IfNullError(v.into())),
2508                LengthTooLarge(()) => Ok(EvalError::LengthTooLarge),
2509                TempStorageBudgetExceeded(()) => Ok(EvalError::TempStorageBudgetExceeded),
2510                AclArrayNullElement(()) => Ok(EvalError::AclArrayNullElement),
2511                MzAclArrayNullElement(()) => Ok(EvalError::MzAclArrayNullElement),
2512                InvalidIanaTimezoneId(s) => Ok(EvalError::InvalidIanaTimezoneId(s.into())),
2513                PrettyError(s) => Ok(EvalError::PrettyError(s.into())),
2514                RedactError(s) => Ok(EvalError::RedactError(s.into())),
2515            },
2516            None => Err(TryFromProtoError::missing_field("ProtoEvalError::kind")),
2517        }
2518    }
2519}
2520
2521impl RustType<ProtoDims> for (usize, usize) {
2522    fn into_proto(&self) -> ProtoDims {
2523        ProtoDims {
2524            f0: self.0.into_proto(),
2525            f1: self.1.into_proto(),
2526        }
2527    }
2528
2529    fn from_proto(proto: ProtoDims) -> Result<Self, TryFromProtoError> {
2530        Ok((proto.f0.into_rust()?, proto.f1.into_rust()?))
2531    }
2532}
2533
2534/// An [`EvalError`] that serializes as protobuf-encoded [`ProtoEvalError`]
2535/// bytes.
2536///
2537/// `EvalError`'s own serde impl mirrors the Rust enum, so every added error
2538/// variant or payload tweak would change a durable format. Use this wrapper
2539/// instead wherever an eval error is serialized into a durable, cross-version
2540/// format, such as the stable LIR plan format (the same role [`StableRow`]
2541/// plays for rows). `ProtoEvalError` already carries the needed backward
2542/// compatibility obligation: it is embedded in `ProtoDataflowError`, which
2543/// persist stores in the error side of `SourceData`, and the proto files are
2544/// covered by the buf breaking lint.
2545///
2546/// [`StableRow`]: mz_repr::StableRow
2547#[derive(
2548    Clone,
2549    Debug,
2550    Eq,
2551    PartialEq,
2552    Ord,
2553    PartialOrd,
2554    Hash,
2555    Serialize,
2556    Deserialize
2557)]
2558pub struct StableEvalError(#[serde(with = "stable_eval_error_proto")] pub EvalError);
2559
2560/// Borrowing twin of [`StableEvalError`], to serialize without cloning. Both
2561/// serialize identically, under the `StableEvalError` container name.
2562#[derive(Debug, Serialize)]
2563#[serde(rename = "StableEvalError")]
2564pub struct StableEvalErrorRef<'a>(#[serde(with = "stable_eval_error_proto")] pub &'a EvalError);
2565
2566impl From<EvalError> for StableEvalError {
2567    fn from(err: EvalError) -> Self {
2568        StableEvalError(err)
2569    }
2570}
2571
2572impl std::ops::Deref for StableEvalError {
2573    type Target = EvalError;
2574
2575    fn deref(&self) -> &EvalError {
2576        &self.0
2577    }
2578}
2579
2580mod stable_eval_error_proto {
2581    use mz_proto::RustType;
2582    use prost::Message;
2583    use serde::de::Error;
2584    use serde::{Deserialize, Deserializer, Serializer};
2585
2586    use crate::scalar::{EvalError, ProtoEvalError};
2587
2588    pub fn serialize<S: Serializer, E: std::borrow::Borrow<EvalError>>(
2589        err: &E,
2590        serializer: S,
2591    ) -> Result<S::Ok, S::Error> {
2592        serializer.serialize_bytes(&err.borrow().into_proto().encode_to_vec())
2593    }
2594
2595    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<EvalError, D::Error> {
2596        let bytes = serde_bytes::ByteBuf::deserialize(deserializer)?;
2597        let proto = ProtoEvalError::decode(bytes.as_slice()).map_err(D::Error::custom)?;
2598        EvalError::from_proto(proto).map_err(D::Error::custom)
2599    }
2600}
2601
2602#[cfg(test)]
2603mod tests {
2604    use super::*;
2605    use crate::scalar::func::variadic::Coalesce;
2606
2607    // StableEvalError's wire format is proto bytes, so every EvalError
2608    // variant must roundtrip exactly through both a self-describing format
2609    // (JSON) and a compact binary one (bincode). This also exercises the
2610    // EvalError -> ProtoEvalError -> EvalError conversion itself, which has
2611    // no other roundtrip coverage.
2612    proptest! {
2613        #![proptest_config(ProptestConfig::with_cases(1000))]
2614
2615        #[mz_ore::test]
2616        #[cfg_attr(miri, ignore)] // too slow
2617        fn stable_eval_error_serde_roundtrip(err in any::<EvalError>()) {
2618            let stable = StableEvalError(err);
2619
2620            let json = serde_json::to_string(&stable).expect("serializes to JSON");
2621            let from_json: StableEvalError =
2622                serde_json::from_str(&json).expect("deserializes from JSON");
2623            prop_assert_eq!(&stable, &from_json);
2624
2625            let bytes = bincode::serialize(&stable).expect("serializes to bincode");
2626            let from_bincode: StableEvalError =
2627                bincode::deserialize(&bytes).expect("deserializes from bincode");
2628            prop_assert_eq!(&stable, &from_bincode);
2629        }
2630    }
2631
2632    /// An `OutOfDomain` with both limits unset is not constructible by any
2633    /// caller, but it decodes out of corrupted or forged `ProtoEvalError` bytes,
2634    /// and `DataflowErrorSer::Display` renders decoded errors on the peek path.
2635    /// Rendering it must not panic.
2636    #[mz_ore::test]
2637    fn test_unbounded_out_of_domain_renders() {
2638        let err = EvalError::OutOfDomain(DomainLimit::None, DomainLimit::None, "f".into());
2639        assert_eq!(
2640            err.to_string(),
2641            "function f is defined for numbers in an unspecified range"
2642        );
2643    }
2644
2645    /// A budgeted arena must stop an amplifying expression that the per-call constant allows
2646    /// (SQL-431). `repeat(body, 20)` on a 1 MiB body is 20 MiB: far under
2647    /// `MAX_STRING_FUNC_RESULT_BYTES`, so nothing rejects it without a budget, and it is exactly
2648    /// the shape that made a webhook `CHECK` turn a bounded request into unbounded heap.
2649    #[mz_ore::test]
2650    #[cfg_attr(miri, ignore)] // multi-MB allocations; the small-size UB coverage is in `row.rs`
2651    fn test_repeat_respects_arena_budget() {
2652        use crate::scalar::func::RepeatString;
2653
2654        let body = "a".repeat(1024 * 1024);
2655        let expr = MirScalarExpr::column(0).call_binary(
2656            MirScalarExpr::literal_ok(Datum::Int32(20), ReprScalarType::Int32),
2657            RepeatString,
2658        );
2659        let datums = [Datum::String(&body)];
2660
2661        // Unbudgeted: allowed, and the arena really does hold the 20 MiB.
2662        let arena = RowArena::new();
2663        let datum = expr
2664            .eval(&datums, &arena)
2665            .expect("under the 100 MiB ceiling");
2666        assert_eq!(datum.unwrap_str().len(), 20 * 1024 * 1024);
2667        assert!(arena.allocated_bytes() >= 20 * 1024 * 1024);
2668
2669        // Budgeted below the result: rejected, and the pre-check means the arena never grew, i.e.
2670        // the bytes were never allocated rather than allocated and then complained about.
2671        let arena = RowArena::with_budget(4 * 1024 * 1024);
2672        assert_eq!(
2673            expr.eval(&datums, &arena),
2674            Err(EvalError::LengthTooLarge),
2675            "an over-budget result must be refused"
2676        );
2677        assert_eq!(arena.allocated_bytes(), 0);
2678
2679        // Budgeted above the result: unaffected.
2680        let arena = RowArena::with_budget(64 * 1024 * 1024);
2681        let datum = expr.eval(&datums, &arena).expect("within budget");
2682        assert_eq!(datum.unwrap_str().len(), 20 * 1024 * 1024);
2683    }
2684
2685    /// The budget also has to bound both dimensions of an arena-building function: the packed
2686    /// result, and the transient it collects on its own stack first. The arena never sees the
2687    /// transient, so the post-call check can't catch it.
2688    #[mz_ore::test]
2689    #[cfg_attr(miri, ignore)] // multi-MB allocations; the small-size UB coverage is in `row.rs`
2690    fn test_arena_built_result_respects_budget() {
2691        use crate::scalar::func::variadic::{RegexpSplitToArray, StringToArray};
2692
2693        let body = "a".repeat(256 * 1024);
2694        let expr = MirScalarExpr::call_variadic(
2695            StringToArray,
2696            vec![
2697                MirScalarExpr::column(0),
2698                MirScalarExpr::literal_ok(Datum::String("a"), ReprScalarType::String),
2699            ],
2700        );
2701        let datums = [Datum::String(&body)];
2702
2703        let arena = RowArena::new();
2704        expr.eval(&datums, &arena).expect("no ceiling applies");
2705        let unbudgeted = arena.allocated_bytes();
2706        assert!(unbudgeted > 0);
2707
2708        let arena = RowArena::with_budget(unbudgeted / 2);
2709        assert_eq!(
2710            expr.eval(&datums, &arena),
2711            Err(EvalError::TempStorageBudgetExceeded),
2712            "an over-budget arena-built result must be refused"
2713        );
2714
2715        // A split collects every chunk into a `Vec<&str>` first, 16 bytes per fat pointer against
2716        // the 2 an empty chunk packs to. A budget the packed array fits under still refuses it.
2717        let intermediate = (body.len() + 1) * std::mem::size_of::<&str>();
2718        let budget = 4 * unbudgeted;
2719        assert!(
2720            unbudgeted < budget && budget < intermediate,
2721            "budget sits between"
2722        );
2723        let arena = RowArena::with_budget(budget);
2724        assert!(
2725            expr.eval(&datums, &arena).is_err(),
2726            "a split costing {intermediate} bytes to build must be refused by a {budget} byte budget"
2727        );
2728        assert_eq!(
2729            arena.allocated_bytes(),
2730            0,
2731            "refused before the transient was built"
2732        );
2733
2734        // The regexp sibling builds the same transient and takes the same bound.
2735        let regexp_expr = MirScalarExpr::call_variadic(
2736            RegexpSplitToArray,
2737            vec![
2738                MirScalarExpr::column(0),
2739                MirScalarExpr::literal_ok(Datum::String("a"), ReprScalarType::String),
2740            ],
2741        );
2742        let arena = RowArena::with_budget(budget);
2743        assert!(
2744            regexp_expr.eval(&datums, &arena).is_err(),
2745            "a regexp split costing {intermediate} bytes to build must be refused too"
2746        );
2747        assert_eq!(arena.allocated_bytes(), 0);
2748    }
2749
2750    /// `array_fill` sizes its result from a parameter rather than its input, so a budgeted arena
2751    /// has to refuse it in its own pre-check the way the string amplifiers do (SQL-431). Its result
2752    /// stays under the `array_fill` size ceiling, so without the budget-aware pre-check the only
2753    /// thing that would catch it is the evaluator's post-call check, after the spike has happened.
2754    #[mz_ore::test]
2755    #[cfg_attr(miri, ignore)] // multi-MB allocations; the small-size UB coverage is in `row.rs`
2756    fn test_array_fill_respects_arena_budget() {
2757        use crate::scalar::func::variadic::ArrayFill;
2758        use mz_repr::adt::array::ArrayDimension;
2759
2760        // array_fill(1, ARRAY[fill_count]) builds a one-dimensional int array of `fill_count` ones.
2761        let fill_count: usize = 512 * 1024;
2762        let dims_storage = RowArena::new();
2763        let dims = dims_storage
2764            .try_make_datum(|packer| {
2765                packer.try_push_array(
2766                    &[ArrayDimension {
2767                        lower_bound: 1,
2768                        length: 1,
2769                    }],
2770                    [Datum::Int32(i32::try_from(fill_count).unwrap())],
2771                )
2772            })
2773            .unwrap();
2774        let expr = MirScalarExpr::call_variadic(
2775            ArrayFill {
2776                elem_type: mz_repr::SqlScalarType::Int32,
2777            },
2778            vec![MirScalarExpr::column(0), MirScalarExpr::column(1)],
2779        );
2780        let datums = [Datum::Int32(1), dims];
2781
2782        // Unbudgeted: allowed, and the arena really holds the packed array.
2783        let arena = RowArena::new();
2784        expr.eval(&datums, &arena)
2785            .expect("under the array-size ceiling");
2786        assert!(arena.allocated_bytes() > 0);
2787
2788        // Budgeted below what the call needs: refused by the pre-check, so nothing was allocated.
2789        // The intermediate `Vec<Datum>` alone is 512 Ki elements, well over this 1 MiB budget.
2790        let arena = RowArena::with_budget(1024 * 1024);
2791        assert_eq!(
2792            expr.eval(&datums, &arena),
2793            Err(EvalError::TempStorageBudgetExceeded),
2794            "an over-budget array_fill must be refused before it allocates"
2795        );
2796        assert_eq!(arena.allocated_bytes(), 0);
2797
2798        // Budgeted well above both the packed result and the intermediate: unaffected.
2799        let arena = RowArena::with_budget(256 * 1024 * 1024);
2800        expr.eval(&datums, &arena).expect("within budget");
2801    }
2802
2803    /// `array_remove` cannot grow its result, so no packed-size check would ever fire for it. The
2804    /// transient `Vec<Datum>` it filters into is a fresh input-scaled allocation on top of the input
2805    /// array's own bytes, and only a pre-check bounds that (SQL-431).
2806    #[mz_ore::test]
2807    #[cfg_attr(miri, ignore)] // multi-MB allocations; the small-size UB coverage is in `row.rs`
2808    fn test_array_remove_respects_arena_budget() {
2809        use crate::scalar::func::ArrayRemove;
2810        use mz_repr::adt::array::ArrayDimension;
2811
2812        const ELEMS: usize = 256 * 1024;
2813        let input_storage = RowArena::new();
2814        let array = input_storage
2815            .try_make_datum(|packer| {
2816                packer.try_push_array(
2817                    &[ArrayDimension {
2818                        lower_bound: 1,
2819                        length: ELEMS,
2820                    }],
2821                    (0..ELEMS).map(|i| Datum::Int32(i32::try_from(i).unwrap())),
2822                )
2823            })
2824            .unwrap();
2825        let expr = MirScalarExpr::column(0).call_binary(MirScalarExpr::column(1), ArrayRemove);
2826        let datums = [array, Datum::Int32(0)];
2827
2828        // Unbudgeted: allowed, and the arena holds the packed result.
2829        let arena = RowArena::new();
2830        expr.eval(&datums, &arena).expect("no ceiling applies");
2831        let unbudgeted = arena.allocated_bytes();
2832        assert!(unbudgeted > 0);
2833
2834        // A `Datum` is several times what an `Int32` packs to, so a budget with room for the packed
2835        // result twice over still has to refuse the transient.
2836        let transient = ELEMS * std::mem::size_of::<Datum<'_>>();
2837        let budget = 2 * unbudgeted;
2838        assert!(budget < transient, "budget sits between");
2839        let arena = RowArena::with_budget(budget);
2840        assert_eq!(
2841            expr.eval(&datums, &arena),
2842            Err(EvalError::TempStorageBudgetExceeded),
2843            "an over-budget transient must be refused"
2844        );
2845        assert_eq!(arena.allocated_bytes(), 0);
2846
2847        // Budgeted above both: unaffected.
2848        let arena = RowArena::with_budget(64 * 1024 * 1024);
2849        expr.eval(&datums, &arena).expect("within budget");
2850    }
2851
2852    /// A budget has to bound what a single call allocates, not just what is observable between
2853    /// calls (SQL-431).
2854    ///
2855    /// The evaluator only polls the budget after `func.eval` has built its result and moved it into
2856    /// the arena, and erroring then does not give the bytes back. They stay resident until the arena
2857    /// drops, which for a webhook `CHECK` is the end of the request. So we assert on arena residency,
2858    /// not the returned `Result`. The overshoot is not a constant either. It scales with the body and
2859    /// with a multiplier the `CHECK` author picks at DDL time.
2860    #[mz_ore::test]
2861    #[cfg_attr(miri, ignore)] // multi-MB allocations; the small-size UB coverage is in `row.rs`
2862    fn test_single_call_respects_arena_budget() {
2863        use mz_ore::cast::CastLossy;
2864
2865        use crate::scalar::func::variadic::{ArrayCreate, PadLeading, Translate};
2866
2867        // Scaled down from the shipped 5 MiB body and 20 MiB budget. Every amplifier here is linear
2868        // in the body, so the ratios hold at any scale.
2869        const BODY_BYTES: usize = 1024 * 1024;
2870        const BUDGET: usize = 2 * 1024 * 1024;
2871        const WIDE: &str = "\u{1F4A5}"; // one character, four bytes
2872
2873        let str_lit = |s| MirScalarExpr::literal_ok(Datum::String(s), ReprScalarType::String);
2874        // `ARRAY[body, ...]` is not a string function, so no ceiling of its own applies. The
2875        // multiplier is just how many times the author wrote `body`.
2876        let array_of = |n| {
2877            let elem_type = mz_repr::SqlScalarType::String;
2878            let refs = vec![MirScalarExpr::column(0); n];
2879            MirScalarExpr::call_variadic(ArrayCreate { elem_type }, refs)
2880        };
2881        let cases = [
2882            ("ARRAY[body x4]", array_of(4)),
2883            ("ARRAY[body x16]", array_of(16)),
2884            // `lpad`'s pre-check is budget-aware but compares `len`, a character count, against a
2885            // budget in bytes, so a 4-byte pad passes a check for exactly the budget then writes 4x.
2886            (
2887                "lpad(body, BUDGET, wide)",
2888                MirScalarExpr::call_variadic(
2889                    PadLeading,
2890                    vec![
2891                        MirScalarExpr::column(0),
2892                        MirScalarExpr::literal_ok(
2893                            Datum::Int32(i32::try_from(BUDGET).unwrap()),
2894                            ReprScalarType::Int32,
2895                        ),
2896                        str_lit(WIDE),
2897                    ],
2898                ),
2899            ),
2900            // `translate` has no pre-check at all, and widening each body byte is a 4x amplifier
2901            // that needs no length argument to drive it.
2902            (
2903                "translate(body, 'a', wide)",
2904                MirScalarExpr::call_variadic(
2905                    Translate,
2906                    vec![MirScalarExpr::column(0), str_lit("a"), str_lit(WIDE)],
2907                ),
2908            ),
2909        ];
2910
2911        let body = "a".repeat(BODY_BYTES);
2912        let datums = [Datum::String(&body)];
2913        let mut over = Vec::new();
2914        for (name, expr) in cases {
2915            let arena = RowArena::with_budget(BUDGET);
2916            let _ = expr.eval(&datums, &arena); // refused or not, we only care what's left resident
2917            let held = arena.allocated_bytes();
2918            if held > BUDGET {
2919                let ratio = f64::cast_lossy(held) / f64::cast_lossy(BUDGET);
2920                over.push(format!("  {name}: held {held} bytes, {ratio:.1}x"));
2921            }
2922        }
2923        assert!(
2924            over.is_empty(),
2925            "a single call left a {BUDGET} byte arena holding more:\n{}",
2926            over.join("\n"),
2927        );
2928    }
2929
2930    #[mz_ore::test]
2931    #[cfg_attr(miri, ignore)] // error: unsupported operation: can't call foreign function `rust_psm_stack_pointer` on OS `linux`
2932    fn test_reduce() {
2933        let relation_type: Vec<ReprColumnType> = vec![
2934            ReprScalarType::Int64.nullable(true),
2935            ReprScalarType::Int64.nullable(true),
2936            ReprScalarType::Int64.nullable(false),
2937        ]
2938        .into_iter()
2939        .collect();
2940        let col = MirScalarExpr::column;
2941        let int64_typ = ReprScalarType::Int64;
2942        let err = |e| MirScalarExpr::literal(Err(e), int64_typ.clone());
2943        let lit = |i| MirScalarExpr::literal_ok(Datum::Int64(i), int64_typ.clone());
2944        let null = || MirScalarExpr::literal_null(int64_typ.clone());
2945
2946        struct TestCase {
2947            input: MirScalarExpr,
2948            output: MirScalarExpr,
2949        }
2950
2951        let test_cases = vec![
2952            TestCase {
2953                input: MirScalarExpr::call_variadic(Coalesce, vec![lit(1)]),
2954                output: lit(1),
2955            },
2956            TestCase {
2957                input: MirScalarExpr::call_variadic(Coalesce, vec![lit(1), lit(2)]),
2958                output: lit(1),
2959            },
2960            TestCase {
2961                input: MirScalarExpr::call_variadic(Coalesce, vec![null(), lit(2), null()]),
2962                output: lit(2),
2963            },
2964            TestCase {
2965                input: MirScalarExpr::call_variadic(
2966                    Coalesce,
2967                    vec![null(), col(0), null(), col(1), lit(2), lit(3)],
2968                ),
2969                output: MirScalarExpr::call_variadic(Coalesce, vec![col(0), col(1), lit(2)]),
2970            },
2971            TestCase {
2972                input: MirScalarExpr::call_variadic(Coalesce, vec![col(0), col(2), col(1)]),
2973                output: MirScalarExpr::call_variadic(Coalesce, vec![col(0), col(2)]),
2974            },
2975            TestCase {
2976                input: MirScalarExpr::call_variadic(
2977                    Coalesce,
2978                    vec![lit(1), err(EvalError::DivisionByZero)],
2979                ),
2980                output: lit(1),
2981            },
2982            TestCase {
2983                input: MirScalarExpr::call_variadic(
2984                    Coalesce,
2985                    vec![
2986                        null(),
2987                        err(EvalError::DivisionByZero),
2988                        err(EvalError::NumericFieldOverflow),
2989                    ],
2990                ),
2991                output: err(EvalError::DivisionByZero),
2992            },
2993        ];
2994
2995        for tc in test_cases {
2996            let mut actual = tc.input.clone();
2997            actual.reduce(&relation_type);
2998            assert!(
2999                actual == tc.output,
3000                "input: {}\nactual: {}\nexpected: {}",
3001                tc.input,
3002                actual,
3003                tc.output
3004            );
3005        }
3006    }
3007
3008    /// Exercises the `unsafe` pointer stack in [`Visit::visit_mut_post`] with a
3009    /// closure that *replaces subtrees* (`*expr = ...`). Miri's aliasing model
3010    /// should shout if the "stack mirrors the call stack" becomes untrue.
3011    #[mz_ore::test]
3012    fn test_visit_mut_post_replace_subtrees() {
3013        let col = MirScalarExpr::column;
3014        let mut expr = col(0).if_then_else(col(1).if_then_else(col(2), col(3)), col(4));
3015
3016        expr.visit_mut_post(&mut |expr: &mut MirScalarExpr| match expr {
3017            MirScalarExpr::Column(n, _) => *n += 1,
3018            MirScalarExpr::If { then, .. } => {
3019                let then = then.take();
3020                *expr = then;
3021            }
3022            _ => {}
3023        });
3024
3025        // collapses to then-most branch
3026        assert_eq!(expr, col(3));
3027    }
3028
3029    /// Exercises the `unsafe` pointer stack in [`Visit::visit_mut_pre_post`] with
3030    /// a `pre` that both *replaces the visited node wholesale* (`*expr = ...`)
3031    /// and *returns an explicit child set* borrowed from the freshly written
3032    /// value. Miri's aliasing model should shout if the "stack mirrors the call
3033    /// stack" becomes untrue.
3034    #[mz_ore::test]
3035    fn test_visit_mut_pre_post_explicit_children() {
3036        let col = MirScalarExpr::column;
3037        let mut expr = col(5)
3038            .if_then_else(col(6), col(7))
3039            .if_then_else(col(1).if_then_else(col(2), col(3)), col(4));
3040
3041        // turns conditions into column 0 in pre
3042        // doesn't traverse conditions of ifs
3043        // adds 10 to all column refs in post (but not in conditions!)
3044        expr.visit_mut_pre_post(
3045            &mut |expr: &mut MirScalarExpr| -> Option<Vec<&mut MirScalarExpr>> {
3046                if let MirScalarExpr::If { .. } = expr {
3047                    let MirScalarExpr::If { then, els, .. } = expr else {
3048                        unreachable!()
3049                    };
3050                    let then = then.take();
3051                    let els = els.take();
3052                    *expr = MirScalarExpr::column(0).if_then_else(then, els);
3053
3054                    let MirScalarExpr::If { then, els, .. } = expr else {
3055                        unreachable!()
3056                    };
3057                    Some(vec![then.as_mut(), els.as_mut()])
3058                } else {
3059                    // Leaves recurse with their default (empty) child set.
3060                    None
3061                }
3062            },
3063            &mut |expr: &mut MirScalarExpr| {
3064                if let MirScalarExpr::Column(n, _) = expr {
3065                    *n += 10;
3066                }
3067            },
3068        );
3069
3070        // conditions become 0; everyone else += 10
3071        let expected = col(0).if_then_else(col(0).if_then_else(col(12), col(13)), col(14));
3072        assert_eq!(expr, expected);
3073    }
3074}