Skip to main content

mz_compute_types/plan/
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
10//! Scalar expressions in a stable format.
11//! These are closely related to [mz_expr::MirScalarExpr], but:
12//!   (1) these are a stable type we write down, and
13//!   (2) these do not have unmaterializable functions in them.
14
15use std::fmt::Display as _;
16use std::sync::Arc;
17
18use itertools::Itertools;
19use mz_expr::explain::{HumanizedExplain, HumanizedExpr, HumanizerMode};
20use mz_expr::{
21    BinaryFunc, Columns, Eval, EvalError, MapFilterProject, MfpPlan, MirScalarExpr,
22    OptimizableExpr, SafeMfpPlan, UnaryFunc, UnmaterializableFunc, VariadicFunc,
23};
24use mz_ore::str::separated;
25use mz_ore::treat_as_equal::TreatAsEqual;
26use mz_repr::explain::ScalarOps;
27use mz_repr::{Datum, ReprColumnType, ReprScalarType, Row, RowArena, StableRow};
28use serde::{Deserialize, Serialize};
29
30/// Scalar expressions, as appear in MFPs.
31/// This is the stable, low-level, LIR definition of scalr expressions.
32#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
33pub enum LirScalarExpr {
34    /// A column of the input row
35    Column(usize, TreatAsEqual<Option<Arc<str>>>),
36    /// A literal value.
37    /// (Stored as a row, because we can't own a Datum)
38    Literal(
39        #[serde(with = "literal_value_serde")] Result<StableRow, EvalError>,
40        ReprColumnType,
41    ),
42    /// A function call that takes one expression as an argument.
43    CallUnary {
44        /// Function. This instantiation stores no MirScalarExpr.
45        func: UnaryFunc<LirScalarExpr>,
46        /// Argument
47        expr: Box<LirScalarExpr>,
48    },
49    /// A function call that takes two expressions as arguments.
50    CallBinary {
51        /// Function
52        func: BinaryFunc,
53        /// First argument
54        expr1: Box<LirScalarExpr>,
55        /// Second argument
56        expr2: Box<LirScalarExpr>,
57    },
58    /// A function call that takes an arbitrary number of arguments.
59    CallVariadic {
60        /// Function
61        func: VariadicFunc,
62        /// Arguments
63        exprs: Vec<LirScalarExpr>,
64    },
65    /// Conditionally evaluated expressions.
66    ///
67    /// It is important that `then` and `els` only be evaluated if
68    /// `cond` is true or not, respectively. This is the only way
69    /// users can guard execution (other logical operator do not
70    /// short-circuit) and we need to preserve that.
71    If {
72        /// Condition
73        cond: Box<LirScalarExpr>,
74        /// Then branch
75        then: Box<LirScalarExpr>,
76        /// Else branch
77        els: Box<LirScalarExpr>,
78    },
79}
80
81pub use literal_value_serde::LiteralValue;
82
83/// Serializes `LirScalarExpr::Literal`'s value through the named
84/// [`LiteralValue`] mirror enum instead of std `Result`.
85///
86/// The stable LIR schema registry maps each container name to a single
87/// format, and `Result` would clash with the differently instantiated
88/// `Result` in `LirRelationNode::Constant`. The mirror has the same variant
89/// order as `Result`, so the encoded bytes are unchanged.
90mod literal_value_serde {
91    use mz_expr::{EvalError, StableEvalError, StableEvalErrorRef};
92    use mz_repr::StableRow;
93    use serde::{Deserialize, Deserializer, Serialize, Serializer};
94
95    /// The serialized form of `LirScalarExpr::Literal`'s value.
96    #[derive(Debug, Serialize, Deserialize)]
97    pub enum LiteralValue {
98        /// See `Result::Ok`.
99        Ok(StableRow),
100        /// See `Result::Err`.
101        Err(StableEvalError),
102    }
103
104    /// Borrowing mirror of [`LiteralValue`], to serialize without cloning.
105    #[derive(Serialize)]
106    #[serde(rename = "LiteralValue")]
107    enum LiteralValueRef<'a> {
108        Ok(&'a StableRow),
109        Err(StableEvalErrorRef<'a>),
110    }
111
112    pub fn serialize<S: Serializer>(
113        value: &Result<StableRow, EvalError>,
114        serializer: S,
115    ) -> Result<S::Ok, S::Error> {
116        let mirror = match value {
117            Ok(row) => LiteralValueRef::Ok(row),
118            Err(err) => LiteralValueRef::Err(StableEvalErrorRef(err)),
119        };
120        mirror.serialize(serializer)
121    }
122
123    pub fn deserialize<'de, D: Deserializer<'de>>(
124        deserializer: D,
125    ) -> Result<Result<StableRow, EvalError>, D::Error> {
126        Ok(match LiteralValue::deserialize(deserializer)? {
127            LiteralValue::Ok(row) => Ok(row),
128            LiteralValue::Err(err) => Err(err.0),
129        })
130    }
131}
132
133impl LirScalarExpr {
134    /// Generates an LSE representing the given column reference.
135    pub fn column(c: usize) -> Self {
136        LirScalarExpr::Column(c, TreatAsEqual(None))
137    }
138
139    /// Packs a `Datum` or `EvalError` into a literal row of the given type.
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| StableRow(Row::pack_slice(&[datum])));
146        LirScalarExpr::Literal(row, typ)
147    }
148
149    /// Generates a literal of the given type.
150    pub fn literal_ok(datum: Datum, typ: ReprScalarType) -> Self {
151        LirScalarExpr::literal(Ok(datum), typ)
152    }
153
154    /// If the expression is a literal, this returns the literal's Datum or the literal's EvalError.
155    /// Otherwise, it returns None.
156    pub fn as_literal(&self) -> Option<Result<Datum<'_>, &EvalError>> {
157        if let LirScalarExpr::Literal(lit, _column_type) = self {
158            Some(lit.as_ref().map(|row| row.unpack_first()))
159        } else {
160            None
161        }
162    }
163
164    /// Returns true if the expression is a literal true.
165    pub fn is_literal_true(&self) -> bool {
166        Some(Ok(Datum::True)) == self.as_literal()
167    }
168
169    /// If the expression is an int64, returns the literal.
170    pub fn as_literal_int64(&self) -> Option<i64> {
171        match self.as_literal() {
172            Some(Ok(Datum::Int64(i))) => Some(i),
173            _ => None,
174        }
175    }
176
177    /// Calls a unary function, with `self` as the argument.
178    pub fn call_unary<U: Into<UnaryFunc<LirScalarExpr>>>(self, func: U) -> Self {
179        LirScalarExpr::CallUnary {
180            func: func.into(),
181            expr: Box::new(self),
182        }
183    }
184
185    /// Calls a binary function, with `self` as the first argument `other` as the second.
186    pub fn call_binary<B: Into<BinaryFunc>>(self, other: Self, func: B) -> Self {
187        LirScalarExpr::CallBinary {
188            func: func.into(),
189            expr1: Box::new(self),
190            expr2: Box::new(other),
191        }
192    }
193
194    /// Visits all subexpressions in DFS preorder.
195    pub fn visit_pre<F>(&self, mut f: F)
196    where
197        F: FnMut(&Self),
198    {
199        let mut worklist = vec![self];
200        while let Some(e) = worklist.pop() {
201            f(e);
202            worklist.extend(e.children().rev());
203        }
204    }
205
206    /// Iterative pre-order visitor.
207    pub fn visit_pre_mut<F: FnMut(&mut Self)>(&mut self, mut f: F) {
208        let mut worklist = vec![self];
209        while let Some(expr) = worklist.pop() {
210            f(expr);
211            worklist.extend(expr.children_mut().rev());
212        }
213    }
214
215    /// Iterates through references to child expressions.
216    pub fn children(&self) -> impl DoubleEndedIterator<Item = &Self> {
217        let mut first = None;
218        let mut second = None;
219        let mut third = None;
220        let mut variadic = None;
221
222        use LirScalarExpr::*;
223        match self {
224            Column(_, _) | Literal(_, _) => (),
225            CallUnary { expr, .. } => {
226                first = Some(&**expr);
227            }
228            CallBinary { expr1, expr2, .. } => {
229                first = Some(&**expr1);
230                second = Some(&**expr2);
231            }
232            CallVariadic { exprs, .. } => {
233                variadic = Some(exprs);
234            }
235            If { cond, then, els } => {
236                first = Some(&**cond);
237                second = Some(&**then);
238                third = Some(&**els);
239            }
240        }
241
242        first
243            .into_iter()
244            .chain(second)
245            .chain(third)
246            .chain(variadic.into_iter().flatten())
247    }
248
249    /// Iterates through mutable references to child expressions.
250    pub fn children_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut Self> {
251        let mut first = None;
252        let mut second = None;
253        let mut third = None;
254        let mut variadic = None;
255
256        use LirScalarExpr::*;
257        match self {
258            Column(_, _) | Literal(_, _) => (),
259            CallUnary { expr, .. } => {
260                first = Some(&mut **expr);
261            }
262            CallBinary { expr1, expr2, .. } => {
263                first = Some(&mut **expr1);
264                second = Some(&mut **expr2);
265            }
266            CallVariadic { exprs, .. } => {
267                variadic = Some(exprs);
268            }
269            If { cond, then, els } => {
270                first = Some(&mut **cond);
271                second = Some(&mut **then);
272                third = Some(&mut **els);
273            }
274        }
275
276        first
277            .into_iter()
278            .chain(second)
279            .chain(third)
280            .chain(variadic.into_iter().flatten())
281    }
282}
283
284impl mz_expr::visit::VisitChildren<LirScalarExpr> for LirScalarExpr {
285    fn visit_children<F>(&self, f: F)
286    where
287        F: FnMut(&Self),
288    {
289        self.children().for_each(f);
290    }
291
292    fn visit_mut_children<F>(&mut self, f: F)
293    where
294        F: FnMut(&mut Self),
295    {
296        self.children_mut().for_each(f);
297    }
298
299    fn try_visit_children<F, E>(&self, mut f: F) -> Result<(), E>
300    where
301        F: FnMut(&Self) -> Result<(), E>,
302    {
303        for child in self.children() {
304            f(child)?;
305        }
306        Ok(())
307    }
308
309    fn try_visit_mut_children<F, E>(&mut self, mut f: F) -> Result<(), E>
310    where
311        F: FnMut(&mut Self) -> Result<(), E>,
312    {
313        use LirScalarExpr::*;
314        match self {
315            Column(_, _) | Literal(_, _) => (),
316            CallUnary { expr, .. } => f(expr)?,
317            CallBinary { expr1, expr2, .. } => {
318                f(expr1)?;
319                f(expr2)?;
320            }
321            CallVariadic { exprs, .. } => {
322                for expr in exprs {
323                    f(expr)?;
324                }
325            }
326            If { cond, then, els } => {
327                f(cond)?;
328                f(then)?;
329                f(els)?;
330            }
331        }
332        Ok(())
333    }
334
335    fn children<'a>(&'a self) -> impl DoubleEndedIterator<Item = &'a LirScalarExpr>
336    where
337        LirScalarExpr: 'a,
338    {
339        LirScalarExpr::children(self)
340    }
341
342    fn children_mut<'a>(&'a mut self) -> impl DoubleEndedIterator<Item = &'a mut LirScalarExpr>
343    where
344        LirScalarExpr: 'a,
345    {
346        LirScalarExpr::children_mut(self)
347    }
348}
349
350impl Columns for LirScalarExpr {
351    fn column(c: usize) -> Self {
352        LirScalarExpr::Column(c, TreatAsEqual(None))
353    }
354
355    /// Visits each column reference and applies `action` to the column.
356    ///
357    /// Useful for remapping columns, or for collecting expression support.
358    fn visit_columns<F>(&mut self, mut action: F)
359    where
360        F: FnMut(&mut usize),
361    {
362        self.visit_pre_mut(|e| {
363            if let LirScalarExpr::Column(col, _) = e {
364                action(col);
365            }
366        });
367    }
368
369    fn is_column(&self) -> bool {
370        matches!(self, LirScalarExpr::Column(_, _))
371    }
372
373    fn as_column(&self) -> Option<usize> {
374        if let LirScalarExpr::Column(i, _) = self {
375            Some(*i)
376        } else {
377            None
378        }
379    }
380
381    fn as_column_mut(&mut self) -> Option<&mut usize> {
382        if let LirScalarExpr::Column(i, _) = self {
383            Some(i)
384        } else {
385            None
386        }
387    }
388
389    fn support_into(&self, support: &mut std::collections::BTreeSet<usize>) {
390        self.visit_pre(|e| {
391            if let LirScalarExpr::Column(i, _) = e {
392                support.insert(*i);
393            }
394        });
395    }
396}
397
398impl Eval for LirScalarExpr {
399    fn eval<'a>(
400        &'a self,
401        datums: &[Datum<'a>],
402        temp_storage: &'a RowArena,
403    ) -> Result<Datum<'a>, EvalError> {
404        use LirScalarExpr::*;
405        match self {
406            Column(index, _name) => Ok(datums[*index]),
407            Literal(res, _column_type) => match res {
408                Ok(row) => Ok(row.unpack_first()),
409                Err(e) => Err(e.clone()),
410            },
411            CallUnary { func, expr } => func.eval(datums, temp_storage, expr.as_ref()),
412            CallBinary { func, expr1, expr2 } => {
413                func.eval(datums, temp_storage, &[expr1.as_ref(), expr2.as_ref()])
414            }
415            CallVariadic { func, exprs } => func.eval(datums, temp_storage, exprs.as_slice()),
416            If { cond, then, els } => match cond.eval(datums, temp_storage)? {
417                Datum::True => then.eval(datums, temp_storage),
418                Datum::False | Datum::Null => els.eval(datums, temp_storage),
419                d => Err(EvalError::Internal(
420                    format!("if condition evaluated to non-boolean datum: {:?}", d).into(),
421                )),
422            },
423        }
424    }
425
426    /// True iff evaluation could possibly error on non-error input `Datum`.
427    fn could_error(&self) -> bool {
428        use LirScalarExpr::*;
429        match self {
430            Column(_col, _name) => false,
431            Literal(row, ..) => row.is_err(),
432            CallUnary { func, expr } => func.could_error() || expr.could_error(),
433            CallBinary { func, expr1, expr2 } => {
434                func.could_error() || expr1.could_error() || expr2.could_error()
435            }
436            CallVariadic { func, exprs } => {
437                func.could_error() || exprs.iter().any(|e| e.could_error())
438            }
439            If { cond, then, els } => cond.could_error() || then.could_error() || els.could_error(),
440        }
441    }
442}
443
444impl OptimizableExpr for LirScalarExpr {
445    fn is_literal(&self) -> bool {
446        matches!(self, LirScalarExpr::Literal(_, _))
447    }
448
449    fn is_literal_err(&self) -> bool {
450        matches!(self, LirScalarExpr::Literal(Err(_), _))
451    }
452
453    fn contains_temporal(&self) -> bool {
454        false // LIR has no CallUnmaterializable, so no mz_now()
455    }
456
457    fn size(&self) -> usize {
458        let mut size = 0;
459        self.visit_pre(|_| size += 1);
460        size
461    }
462
463    fn eager_children(&mut self) -> Option<Vec<&mut Self>> {
464        // Do not eagerly memoize `if` branches that might not be taken.
465        if let LirScalarExpr::If { cond, .. } = self {
466            return Some(vec![cond]);
467        }
468
469        // Do not eagerly memoize `COALESCE` expressions after the first.
470        if let LirScalarExpr::CallVariadic {
471            func: VariadicFunc::Coalesce(_),
472            exprs,
473        } = self
474        {
475            return Some(exprs.iter_mut().take(1).collect());
476        }
477
478        // No temporal filters in LIR.
479        None
480    }
481
482    fn equality_column_alias(predicate: &Self, expr: &Self, threshold: usize) -> Option<Self> {
483        if let LirScalarExpr::CallBinary {
484            func: BinaryFunc::Eq(_),
485            expr1,
486            expr2,
487        } = predicate
488        {
489            if let LirScalarExpr::Column(c, name) = &**expr1 {
490                if *c < threshold && &**expr2 == expr {
491                    return Some(LirScalarExpr::Column(*c, name.clone()));
492                }
493            }
494            if let LirScalarExpr::Column(c, name) = &**expr2 {
495                if *c < threshold && &**expr1 == expr {
496                    return Some(LirScalarExpr::Column(*c, name.clone()));
497                }
498            }
499        }
500        None
501    }
502
503    fn extract_temporal_bounds(temporal: Vec<Self>) -> Result<(Vec<Self>, Vec<Self>), String> {
504        if temporal.is_empty() {
505            Ok((Vec::new(), Vec::new()))
506        } else {
507            Err("LIR expressions do not support temporal predicates".into())
508        }
509    }
510}
511
512// We need a custom Debug because we don't want to show `None` for name information.
513// Sadly, the `derivative` crate doesn't support this use case.
514impl std::fmt::Debug for LirScalarExpr {
515    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
516        match self {
517            LirScalarExpr::Column(i, TreatAsEqual(Some(name))) => {
518                write!(f, "Column({i}, {name:?})")
519            }
520            LirScalarExpr::Column(i, TreatAsEqual(None)) => write!(f, "Column({i})"),
521            LirScalarExpr::Literal(lit, typ) => write!(f, "Literal({lit:?}, {typ:?})"),
522            LirScalarExpr::CallUnary { func, expr } => {
523                write!(f, "CallUnary({func:?}, {expr:?})")
524            }
525            LirScalarExpr::CallBinary { func, expr1, expr2 } => {
526                write!(f, "CallBinary({func:?}, {expr1:?}, {expr2:?})")
527            }
528            LirScalarExpr::CallVariadic { func, exprs } => {
529                write!(f, "CallVariadic({func:?}, {exprs:?})")
530            }
531            LirScalarExpr::If { cond, then, els } => {
532                write!(f, "If({cond:?}, {then:?}, {els:?})")
533            }
534        }
535    }
536}
537
538impl ScalarOps for LirScalarExpr {
539    fn match_col_ref(&self) -> Option<usize> {
540        match self {
541            LirScalarExpr::Column(c, _name) => Some(*c),
542            _ => None,
543        }
544    }
545
546    fn references(&self, column: usize) -> bool {
547        match self {
548            LirScalarExpr::Column(c, _name) => *c == column,
549            _ => false,
550        }
551    }
552}
553
554impl mz_expr::explain::HumanizeDisplay for LirScalarExpr {
555    fn humanize<'a, M: HumanizerMode>(
556        e: &HumanizedExpr<'a, Self, M>,
557        f: &mut std::fmt::Formatter<'_>,
558    ) -> std::fmt::Result {
559        use LirScalarExpr::*;
560
561        match e.expr {
562            Column(i, TreatAsEqual(None)) => {
563                // Delegate to the `HumanizedExpr<'a, _>` implementation (plain column reference).
564                e.child(i).fmt(f)
565            }
566            Column(i, TreatAsEqual(Some(name))) => {
567                // Delegate to the `HumanizedExpr<'a, _>` implementation (with stored name information)
568                e.child(&(i, name)).fmt(f)
569            }
570            Literal(row, _) => {
571                // Delegate to the `HumanizedExpr<'a, _>` implementation.
572                e.child(row).fmt(f)
573            }
574            CallUnary { func, expr } => {
575                if let UnaryFunc::Not(_) = *func {
576                    if let CallUnary { func, expr } = expr.as_ref() {
577                        if let Some(is) = func.is() {
578                            let expr = e.child::<LirScalarExpr>(&*expr);
579                            return write!(f, "({}) IS NOT {}", expr, is);
580                        }
581                    }
582                }
583                if let Some(is) = func.is() {
584                    let expr = e.child::<LirScalarExpr>(&*expr);
585                    write!(f, "({}) IS {}", expr, is)
586                } else {
587                    let expr = e.child::<LirScalarExpr>(&*expr);
588                    write!(f, "{}({})", func, expr)
589                }
590            }
591            CallBinary { func, expr1, expr2 } => {
592                let expr1 = e.child::<LirScalarExpr>(&*expr1);
593                let expr2 = e.child::<LirScalarExpr>(&*expr2);
594                if func.is_infix_op() {
595                    write!(f, "({} {} {})", expr1, func, expr2)
596                } else {
597                    write!(f, "{}({}, {})", func, expr1, expr2)
598                }
599            }
600            CallVariadic { func, exprs } => {
601                use VariadicFunc::*;
602                match func {
603                    CaseLiteral(cl) => {
604                        let input = e.child::<LirScalarExpr>(&exprs[0]);
605                        write!(f, "case_lookup {}", input)?;
606                        for entry in &cl.lookup {
607                            let result = e.child::<LirScalarExpr>(&exprs[entry.expr_index]);
608                            write!(f, " when ")?;
609                            e.mode.humanize_datum(entry.literal.unpack_first(), f)?;
610                            write!(f, " then {}", result)?;
611                        }
612                        let els = e.child::<LirScalarExpr>(exprs.last().unwrap());
613                        write!(f, " else {} end", els)
614                    }
615                    ArrayCreate(..) => {
616                        let exprs = exprs.iter().map(|expr| e.child(expr));
617                        let exprs = separated(", ", exprs);
618                        write!(f, "array[{}]", exprs)
619                    }
620                    ListCreate(..) => {
621                        let exprs = exprs.iter().map(|expr| e.child(expr));
622                        let exprs = separated(", ", exprs);
623                        write!(f, "list[{}]", exprs)
624                    }
625                    RecordCreate(..) => {
626                        let exprs = exprs.iter().map(|expr| e.child(expr));
627                        let exprs = separated(", ", exprs);
628                        write!(f, "row({})", exprs)
629                    }
630                    func if func.is_infix_op() && exprs.len() > 1 => {
631                        let exprs = exprs.iter().map(|expr| e.child(expr));
632                        let func = format!(" {} ", func);
633                        let exprs = separated(&func, exprs);
634                        write!(f, "({})", exprs)
635                    }
636                    func => {
637                        let exprs = exprs.iter().map(|expr| e.child(expr));
638                        let exprs = separated(", ", exprs);
639                        write!(f, "{}({})", func, exprs)
640                    }
641                }
642            }
643            If { cond, then, els } => {
644                let cond = e.child::<LirScalarExpr>(&*cond);
645                let then = e.child::<LirScalarExpr>(&*then);
646                let els = e.child::<LirScalarExpr>(&*els);
647                write!(f, "case when {} then {} else {} end", cond, then, els)
648            }
649        }
650    }
651}
652
653impl std::fmt::Display for LirScalarExpr {
654    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
655        let mode = HumanizedExplain::default();
656        std::fmt::Display::fmt(&mode.expr(self, None), f)
657    }
658}
659
660impl From<&LirScalarExpr> for MirScalarExpr {
661    fn from(value: &LirScalarExpr) -> Self {
662        use LirScalarExpr::*;
663        match value {
664            Column(c, treat_as_equal) => MirScalarExpr::Column(c.clone(), treat_as_equal.clone()),
665            Literal(row, repr_column_type) => {
666                MirScalarExpr::Literal(row.clone().map(|row| row.0), repr_column_type.clone())
667            }
668            CallUnary { func, expr } => MirScalarExpr::CallUnary {
669                func: func.map_expr(),
670                expr: Box::new(MirScalarExpr::from(expr.as_ref())),
671            },
672            CallBinary { func, expr1, expr2 } => MirScalarExpr::CallBinary {
673                func: func.clone(),
674                expr1: Box::new(MirScalarExpr::from(expr1.as_ref())),
675                expr2: Box::new(MirScalarExpr::from(expr2.as_ref())),
676            },
677            CallVariadic { func, exprs } => MirScalarExpr::CallVariadic {
678                func: func.clone(),
679                exprs: exprs.iter().map(MirScalarExpr::from).collect(),
680            },
681            If { cond, then, els } => MirScalarExpr::If {
682                cond: Box::new(MirScalarExpr::from(cond.as_ref())),
683                then: Box::new(MirScalarExpr::from(then.as_ref())),
684                els: Box::new(MirScalarExpr::from(els.as_ref())),
685            },
686        }
687    }
688}
689
690impl TryFrom<&MirScalarExpr> for LirScalarExpr {
691    // MIR-to-LIR failures come from unmaterializable functions that haven't been dealt with yet.
692    type Error = Vec<UnmaterializableFunc>;
693
694    fn try_from(value: &MirScalarExpr) -> Result<Self, Self::Error> {
695        use MirScalarExpr::*;
696        match value {
697            Column(c, treat_as_equal) => Ok(LirScalarExpr::Column(*c, treat_as_equal.clone())),
698            Literal(row, repr_column_type) => Ok(LirScalarExpr::Literal(
699                row.clone().map(StableRow),
700                repr_column_type.clone(),
701            )),
702            CallUnary { func, expr } => Ok(LirScalarExpr::CallUnary {
703                func: func.try_map_expr()?,
704                expr: Box::new(LirScalarExpr::try_from(expr.as_ref())?),
705            }),
706            CallBinary { func, expr1, expr2 } => {
707                match (
708                    LirScalarExpr::try_from(expr1.as_ref()),
709                    LirScalarExpr::try_from(expr2.as_ref()),
710                ) {
711                    (Ok(expr1), Ok(expr2)) => Ok(LirScalarExpr::CallBinary {
712                        func: func.clone(),
713                        expr1: Box::new(expr1),
714                        expr2: Box::new(expr2),
715                    }),
716                    (Ok(_), Err(e)) | (Err(e), Ok(_)) => Err(e),
717                    (Err(mut e1), Err(mut e2)) => {
718                        e1.append(&mut e2);
719                        Err(e1)
720                    }
721                }
722            }
723            CallVariadic { func, exprs } => {
724                let (exprs, errors): (Vec<LirScalarExpr>, Vec<Vec<UnmaterializableFunc>>) = exprs
725                    .into_iter()
726                    .map(LirScalarExpr::try_from)
727                    .partition_result();
728
729                if errors.is_empty() {
730                    Ok(LirScalarExpr::CallVariadic {
731                        func: func.clone(),
732                        exprs,
733                    })
734                } else {
735                    Err(errors.concat())
736                }
737            }
738            If { cond, then, els } => {
739                let cond = LirScalarExpr::try_from(cond.as_ref());
740                let then = LirScalarExpr::try_from(then.as_ref());
741                let els = LirScalarExpr::try_from(els.as_ref());
742
743                match (cond, then, els) {
744                    (Ok(cond), Ok(then), Ok(els)) => Ok(LirScalarExpr::If {
745                        cond: Box::new(cond),
746                        then: Box::new(then),
747                        els: Box::new(els),
748                    }),
749                    (Err(e), Ok(_), Ok(_)) | (Ok(_), Err(e), Ok(_)) | (Ok(_), Ok(_), Err(e)) => {
750                        Err(e)
751                    }
752                    (Err(mut e1), Err(mut e2), Ok(_))
753                    | (Err(mut e1), Ok(_), Err(mut e2))
754                    | (Ok(_), Err(mut e1), Err(mut e2)) => {
755                        e1.append(&mut e2);
756                        Err(e1)
757                    }
758                    (Err(mut e1), Err(mut e2), Err(mut e3)) => {
759                        e1.append(&mut e2);
760                        e1.append(&mut e3);
761                        Err(e1)
762                    }
763                }
764            }
765            CallUnmaterializable(f) => Err(vec![f.clone()]),
766        }
767    }
768}
769
770/// Convert a MIR `MapFilterProject` to LIR.
771///
772/// Panics if any expression contains unmaterializable functions.
773pub fn mfp_mir_to_lir(mfp: MapFilterProject<MirScalarExpr>) -> MapFilterProject<LirScalarExpr> {
774    let expressions = lses_from_mses(&mfp.expressions);
775    let predicates = mfp
776        .predicates
777        .iter()
778        .map(|(pos, pred)| {
779            (
780                *pos,
781                LirScalarExpr::try_from(pred).expect("unmaterializable in MFP predicate"),
782            )
783        })
784        .collect();
785    MapFilterProject::<LirScalarExpr> {
786        expressions,
787        predicates,
788        projection: mfp.projection,
789        input_arity: mfp.input_arity,
790    }
791}
792
793/// Convert a MIR `SafeMfpPlan` to LIR.
794///
795/// Panics if any expression contains unmaterializable functions.
796pub fn safe_mfp_mir_to_lir(plan: SafeMfpPlan<MirScalarExpr>) -> SafeMfpPlan<LirScalarExpr> {
797    SafeMfpPlan::from_mfp(mfp_mir_to_lir(plan.into_mfp()))
798}
799
800/// Convert a MIR `MapFilterProject` into an LIR `MfpPlan`.
801///
802/// The temporal bounds and the inner SafeMfpPlan are all `mz_now()`-free
803/// after temporal extraction, so conversion always succeeds.
804/// Panics if any expression unexpectedly contains unmaterializable functions.
805pub fn mfp_mir_to_lir_plan(mfp: MapFilterProject<MirScalarExpr>) -> MfpPlan<LirScalarExpr> {
806    let plan = mfp.into_plan().expect("MFP planning failed");
807    mfp_plan_mir_to_lir(plan)
808}
809
810/// Convert a MIR `MfpPlan` to LIR.
811///
812/// The temporal bounds and the inner SafeMfpPlan are all `mz_now()`-free
813/// after temporal extraction, so conversion always succeeds.
814/// Panics if any expression unexpectedly contains unmaterializable functions.
815pub fn mfp_plan_mir_to_lir(plan: MfpPlan<MirScalarExpr>) -> MfpPlan<LirScalarExpr> {
816    let (safe, lower, upper) = plan.into_parts();
817    MfpPlan::from_parts(
818        safe_mfp_mir_to_lir(safe),
819        lses_from_mses(&lower),
820        lses_from_mses(&upper),
821    )
822}
823
824/// Convert a LIR `MfpPlan` to MIR (always succeeds).
825pub fn mfp_plan_lir_to_mir(plan: MfpPlan<LirScalarExpr>) -> MfpPlan<MirScalarExpr> {
826    let (safe, lower, upper) = plan.into_parts();
827
828    let mfp = safe.into_mfp();
829    let expressions = mfp.expressions.iter().map(MirScalarExpr::from).collect();
830    let predicates = mfp
831        .predicates
832        .iter()
833        .map(|(pos, pred)| (*pos, MirScalarExpr::from(pred)))
834        .collect();
835    let mir_mfp = MapFilterProject::<MirScalarExpr> {
836        expressions,
837        predicates,
838        projection: mfp.projection,
839        input_arity: mfp.input_arity,
840    };
841
842    let lower = lower.iter().map(MirScalarExpr::from).collect();
843    let upper = upper.iter().map(MirScalarExpr::from).collect();
844    MfpPlan::from_parts(SafeMfpPlan::from_mfp(mir_mfp), lower, upper)
845}
846
847/// Translates a `&Vec<MirScalarExpr>` (or similar) to a `Vec<LirScalarExpr>`.
848///
849/// LIR-level expressions never contain unmaterializable functions, so this
850/// conversion is total in practice. The function follows the convention that
851/// the non-`try_` variant panics on failure: a panic here indicates a lowering
852/// bug, not a recoverable condition.
853pub(crate) fn lses_from_mses<'a>(
854    exprs: impl IntoIterator<Item = &'a MirScalarExpr>,
855) -> Vec<LirScalarExpr> {
856    match exprs
857        .into_iter()
858        .map(LirScalarExpr::try_from)
859        .collect::<Result<Vec<LirScalarExpr>, _>>()
860    {
861        Ok(exprs) => exprs,
862        Err(funcs) => {
863            panic!(
864                "unmaterializable functions cannot be translated to LirScalarExpr: {}",
865                separated(", ", &funcs)
866            )
867        }
868    }
869}