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