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, Interpretable, Interpreter, MapFilterProject, MfpPlan,
22    MirScalarExpr, 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 Interpretable for LirScalarExpr {
445    fn interpret<I: Interpreter + ?Sized>(&self, interpreter: &I) -> I::Summary {
446        use LirScalarExpr::*;
447        match self {
448            Column(id, _name) => interpreter.column(*id),
449            Literal(value, col_type) => {
450                // `StableRow` only changes how the row serializes.
451                let value = value.clone().map(|row| row.0);
452                interpreter.literal(&value, col_type)
453            }
454            CallUnary { func, expr } => {
455                // The interpreter inspects MIR funcs. Only the cast funcs
456                // carry an expression payload, and `map_expr` converts it.
457                let expr = expr.interpret(interpreter);
458                interpreter.unary(&func.map_expr(), expr)
459            }
460            CallBinary { func, expr1, expr2 } => {
461                let expr1 = expr1.interpret(interpreter);
462                let expr2 = expr2.interpret(interpreter);
463                interpreter.binary(func, expr1, expr2)
464            }
465            CallVariadic { func, exprs } => {
466                let exprs = exprs.iter().map(|e| e.interpret(interpreter)).collect();
467                interpreter.variadic(func, exprs)
468            }
469            If { cond, then, els } => {
470                let cond = cond.interpret(interpreter);
471                let then = then.interpret(interpreter);
472                let els = els.interpret(interpreter);
473                interpreter.cond(cond, then, els)
474            }
475        }
476    }
477}
478
479impl OptimizableExpr for LirScalarExpr {
480    fn is_literal(&self) -> bool {
481        matches!(self, LirScalarExpr::Literal(_, _))
482    }
483
484    fn is_literal_err(&self) -> bool {
485        matches!(self, LirScalarExpr::Literal(Err(_), _))
486    }
487
488    fn contains_temporal(&self) -> bool {
489        false // LIR has no CallUnmaterializable, so no mz_now()
490    }
491
492    fn size(&self) -> usize {
493        let mut size = 0;
494        self.visit_pre(|_| size += 1);
495        size
496    }
497
498    fn eager_children(&mut self) -> Option<Vec<&mut Self>> {
499        // Do not eagerly memoize `if` branches that might not be taken.
500        if let LirScalarExpr::If { cond, .. } = self {
501            return Some(vec![cond]);
502        }
503
504        // Do not eagerly memoize `COALESCE` expressions after the first.
505        if let LirScalarExpr::CallVariadic {
506            func: VariadicFunc::Coalesce(_),
507            exprs,
508        } = self
509        {
510            return Some(exprs.iter_mut().take(1).collect());
511        }
512
513        // No temporal filters in LIR.
514        None
515    }
516
517    fn equality_column_alias(predicate: &Self, expr: &Self, threshold: usize) -> Option<Self> {
518        if let LirScalarExpr::CallBinary {
519            func: BinaryFunc::Eq(_),
520            expr1,
521            expr2,
522        } = predicate
523        {
524            if let LirScalarExpr::Column(c, name) = &**expr1 {
525                if *c < threshold && &**expr2 == expr {
526                    return Some(LirScalarExpr::Column(*c, name.clone()));
527                }
528            }
529            if let LirScalarExpr::Column(c, name) = &**expr2 {
530                if *c < threshold && &**expr1 == expr {
531                    return Some(LirScalarExpr::Column(*c, name.clone()));
532                }
533            }
534        }
535        None
536    }
537
538    fn extract_temporal_bounds(temporal: Vec<Self>) -> Result<(Vec<Self>, Vec<Self>), String> {
539        if temporal.is_empty() {
540            Ok((Vec::new(), Vec::new()))
541        } else {
542            Err("LIR expressions do not support temporal predicates".into())
543        }
544    }
545}
546
547// We need a custom Debug because we don't want to show `None` for name information.
548// Sadly, the `derivative` crate doesn't support this use case.
549impl std::fmt::Debug for LirScalarExpr {
550    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
551        match self {
552            LirScalarExpr::Column(i, TreatAsEqual(Some(name))) => {
553                write!(f, "Column({i}, {name:?})")
554            }
555            LirScalarExpr::Column(i, TreatAsEqual(None)) => write!(f, "Column({i})"),
556            LirScalarExpr::Literal(lit, typ) => write!(f, "Literal({lit:?}, {typ:?})"),
557            LirScalarExpr::CallUnary { func, expr } => {
558                write!(f, "CallUnary({func:?}, {expr:?})")
559            }
560            LirScalarExpr::CallBinary { func, expr1, expr2 } => {
561                write!(f, "CallBinary({func:?}, {expr1:?}, {expr2:?})")
562            }
563            LirScalarExpr::CallVariadic { func, exprs } => {
564                write!(f, "CallVariadic({func:?}, {exprs:?})")
565            }
566            LirScalarExpr::If { cond, then, els } => {
567                write!(f, "If({cond:?}, {then:?}, {els:?})")
568            }
569        }
570    }
571}
572
573impl ScalarOps for LirScalarExpr {
574    fn match_col_ref(&self) -> Option<usize> {
575        match self {
576            LirScalarExpr::Column(c, _name) => Some(*c),
577            _ => None,
578        }
579    }
580
581    fn references(&self, column: usize) -> bool {
582        match self {
583            LirScalarExpr::Column(c, _name) => *c == column,
584            _ => false,
585        }
586    }
587}
588
589impl mz_expr::explain::HumanizeDisplay for LirScalarExpr {
590    fn humanize<'a, M: HumanizerMode>(
591        e: &HumanizedExpr<'a, Self, M>,
592        f: &mut std::fmt::Formatter<'_>,
593    ) -> std::fmt::Result {
594        use LirScalarExpr::*;
595
596        match e.expr {
597            Column(i, TreatAsEqual(None)) => {
598                // Delegate to the `HumanizedExpr<'a, _>` implementation (plain column reference).
599                e.child(i).fmt(f)
600            }
601            Column(i, TreatAsEqual(Some(name))) => {
602                // Delegate to the `HumanizedExpr<'a, _>` implementation (with stored name information)
603                e.child(&(i, name)).fmt(f)
604            }
605            Literal(row, _) => {
606                // Delegate to the `HumanizedExpr<'a, _>` implementation.
607                e.child(row).fmt(f)
608            }
609            CallUnary { func, expr } => {
610                if let UnaryFunc::Not(_) = *func {
611                    if let CallUnary { func, expr } = expr.as_ref() {
612                        if let Some(is) = func.is() {
613                            let expr = e.child::<LirScalarExpr>(&*expr);
614                            return write!(f, "({}) IS NOT {}", expr, is);
615                        }
616                    }
617                }
618                if let Some(is) = func.is() {
619                    let expr = e.child::<LirScalarExpr>(&*expr);
620                    write!(f, "({}) IS {}", expr, is)
621                } else {
622                    let expr = e.child::<LirScalarExpr>(&*expr);
623                    write!(f, "{}({})", func, expr)
624                }
625            }
626            CallBinary { func, expr1, expr2 } => {
627                let expr1 = e.child::<LirScalarExpr>(&*expr1);
628                let expr2 = e.child::<LirScalarExpr>(&*expr2);
629                if func.is_infix_op() {
630                    write!(f, "({} {} {})", expr1, func, expr2)
631                } else {
632                    write!(f, "{}({}, {})", func, expr1, expr2)
633                }
634            }
635            CallVariadic { func, exprs } => {
636                use VariadicFunc::*;
637                match func {
638                    CaseLiteral(cl) => {
639                        let input = e.child::<LirScalarExpr>(&exprs[0]);
640                        write!(f, "case_lookup {}", input)?;
641                        for entry in &cl.lookup {
642                            let result = e.child::<LirScalarExpr>(&exprs[entry.expr_index]);
643                            write!(f, " when ")?;
644                            e.mode.humanize_datum(entry.literal.unpack_first(), f)?;
645                            write!(f, " then {}", result)?;
646                        }
647                        let els = e.child::<LirScalarExpr>(exprs.last().unwrap());
648                        write!(f, " else {} end", els)
649                    }
650                    ArrayCreate(..) => {
651                        let exprs = exprs.iter().map(|expr| e.child(expr));
652                        let exprs = separated(", ", exprs);
653                        write!(f, "array[{}]", exprs)
654                    }
655                    ListCreate(..) => {
656                        let exprs = exprs.iter().map(|expr| e.child(expr));
657                        let exprs = separated(", ", exprs);
658                        write!(f, "list[{}]", exprs)
659                    }
660                    RecordCreate(..) => {
661                        let exprs = exprs.iter().map(|expr| e.child(expr));
662                        let exprs = separated(", ", exprs);
663                        write!(f, "row({})", exprs)
664                    }
665                    func if func.is_infix_op() && exprs.len() > 1 => {
666                        let exprs = exprs.iter().map(|expr| e.child(expr));
667                        let func = format!(" {} ", func);
668                        let exprs = separated(&func, exprs);
669                        write!(f, "({})", exprs)
670                    }
671                    func => {
672                        let exprs = exprs.iter().map(|expr| e.child(expr));
673                        let exprs = separated(", ", exprs);
674                        write!(f, "{}({})", func, exprs)
675                    }
676                }
677            }
678            If { cond, then, els } => {
679                let cond = e.child::<LirScalarExpr>(&*cond);
680                let then = e.child::<LirScalarExpr>(&*then);
681                let els = e.child::<LirScalarExpr>(&*els);
682                write!(f, "case when {} then {} else {} end", cond, then, els)
683            }
684        }
685    }
686}
687
688impl std::fmt::Display for LirScalarExpr {
689    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
690        let mode = HumanizedExplain::default();
691        std::fmt::Display::fmt(&mode.expr(self, None), f)
692    }
693}
694
695impl From<&LirScalarExpr> for MirScalarExpr {
696    fn from(value: &LirScalarExpr) -> Self {
697        use LirScalarExpr::*;
698        match value {
699            Column(c, treat_as_equal) => MirScalarExpr::Column(c.clone(), treat_as_equal.clone()),
700            Literal(row, repr_column_type) => {
701                MirScalarExpr::Literal(row.clone().map(|row| row.0), repr_column_type.clone())
702            }
703            CallUnary { func, expr } => MirScalarExpr::CallUnary {
704                func: func.map_expr(),
705                expr: Box::new(MirScalarExpr::from(expr.as_ref())),
706            },
707            CallBinary { func, expr1, expr2 } => MirScalarExpr::CallBinary {
708                func: func.clone(),
709                expr1: Box::new(MirScalarExpr::from(expr1.as_ref())),
710                expr2: Box::new(MirScalarExpr::from(expr2.as_ref())),
711            },
712            CallVariadic { func, exprs } => MirScalarExpr::CallVariadic {
713                func: func.clone(),
714                exprs: exprs.iter().map(MirScalarExpr::from).collect(),
715            },
716            If { cond, then, els } => MirScalarExpr::If {
717                cond: Box::new(MirScalarExpr::from(cond.as_ref())),
718                then: Box::new(MirScalarExpr::from(then.as_ref())),
719                els: Box::new(MirScalarExpr::from(els.as_ref())),
720            },
721        }
722    }
723}
724
725impl TryFrom<&MirScalarExpr> for LirScalarExpr {
726    // MIR-to-LIR failures come from unmaterializable functions that haven't been dealt with yet.
727    type Error = Vec<UnmaterializableFunc>;
728
729    fn try_from(value: &MirScalarExpr) -> Result<Self, Self::Error> {
730        use MirScalarExpr::*;
731        match value {
732            Column(c, treat_as_equal) => Ok(LirScalarExpr::Column(*c, treat_as_equal.clone())),
733            Literal(row, repr_column_type) => Ok(LirScalarExpr::Literal(
734                row.clone().map(StableRow),
735                repr_column_type.clone(),
736            )),
737            CallUnary { func, expr } => Ok(LirScalarExpr::CallUnary {
738                func: func.try_map_expr()?,
739                expr: Box::new(LirScalarExpr::try_from(expr.as_ref())?),
740            }),
741            CallBinary { func, expr1, expr2 } => {
742                match (
743                    LirScalarExpr::try_from(expr1.as_ref()),
744                    LirScalarExpr::try_from(expr2.as_ref()),
745                ) {
746                    (Ok(expr1), Ok(expr2)) => Ok(LirScalarExpr::CallBinary {
747                        func: func.clone(),
748                        expr1: Box::new(expr1),
749                        expr2: Box::new(expr2),
750                    }),
751                    (Ok(_), Err(e)) | (Err(e), Ok(_)) => Err(e),
752                    (Err(mut e1), Err(mut e2)) => {
753                        e1.append(&mut e2);
754                        Err(e1)
755                    }
756                }
757            }
758            CallVariadic { func, exprs } => {
759                let (exprs, errors): (Vec<LirScalarExpr>, Vec<Vec<UnmaterializableFunc>>) = exprs
760                    .into_iter()
761                    .map(LirScalarExpr::try_from)
762                    .partition_result();
763
764                if errors.is_empty() {
765                    Ok(LirScalarExpr::CallVariadic {
766                        func: func.clone(),
767                        exprs,
768                    })
769                } else {
770                    Err(errors.concat())
771                }
772            }
773            If { cond, then, els } => {
774                let cond = LirScalarExpr::try_from(cond.as_ref());
775                let then = LirScalarExpr::try_from(then.as_ref());
776                let els = LirScalarExpr::try_from(els.as_ref());
777
778                match (cond, then, els) {
779                    (Ok(cond), Ok(then), Ok(els)) => Ok(LirScalarExpr::If {
780                        cond: Box::new(cond),
781                        then: Box::new(then),
782                        els: Box::new(els),
783                    }),
784                    (Err(e), Ok(_), Ok(_)) | (Ok(_), Err(e), Ok(_)) | (Ok(_), Ok(_), Err(e)) => {
785                        Err(e)
786                    }
787                    (Err(mut e1), Err(mut e2), Ok(_))
788                    | (Err(mut e1), Ok(_), Err(mut e2))
789                    | (Ok(_), Err(mut e1), Err(mut e2)) => {
790                        e1.append(&mut e2);
791                        Err(e1)
792                    }
793                    (Err(mut e1), Err(mut e2), Err(mut e3)) => {
794                        e1.append(&mut e2);
795                        e1.append(&mut e3);
796                        Err(e1)
797                    }
798                }
799            }
800            CallUnmaterializable(f) => Err(vec![f.clone()]),
801        }
802    }
803}
804
805/// Convert a MIR `MapFilterProject` to LIR.
806///
807/// Panics if any expression contains unmaterializable functions.
808pub fn mfp_mir_to_lir(mfp: MapFilterProject<MirScalarExpr>) -> MapFilterProject<LirScalarExpr> {
809    let expressions = lses_from_mses(&mfp.expressions);
810    let predicates = mfp
811        .predicates
812        .iter()
813        .map(|(pos, pred)| {
814            (
815                *pos,
816                LirScalarExpr::try_from(pred).expect("unmaterializable in MFP predicate"),
817            )
818        })
819        .collect();
820    MapFilterProject::<LirScalarExpr> {
821        expressions,
822        predicates,
823        projection: mfp.projection,
824        input_arity: mfp.input_arity,
825    }
826}
827
828/// Convert a MIR `SafeMfpPlan` to LIR.
829///
830/// Panics if any expression contains unmaterializable functions.
831pub fn safe_mfp_mir_to_lir(plan: SafeMfpPlan<MirScalarExpr>) -> SafeMfpPlan<LirScalarExpr> {
832    SafeMfpPlan::from_mfp(mfp_mir_to_lir(plan.into_mfp()))
833}
834
835/// Convert a MIR `MapFilterProject` into an LIR `MfpPlan`.
836///
837/// The temporal bounds and the inner SafeMfpPlan are all `mz_now()`-free
838/// after temporal extraction, so conversion always succeeds.
839/// Panics if any expression unexpectedly contains unmaterializable functions.
840pub fn mfp_mir_to_lir_plan(mfp: MapFilterProject<MirScalarExpr>) -> MfpPlan<LirScalarExpr> {
841    let plan = mfp.into_plan().expect("MFP planning failed");
842    mfp_plan_mir_to_lir(plan)
843}
844
845/// Convert a MIR `MfpPlan` to LIR.
846///
847/// The temporal bounds and the inner SafeMfpPlan are all `mz_now()`-free
848/// after temporal extraction, so conversion always succeeds.
849/// Panics if any expression unexpectedly contains unmaterializable functions.
850pub fn mfp_plan_mir_to_lir(plan: MfpPlan<MirScalarExpr>) -> MfpPlan<LirScalarExpr> {
851    let (safe, lower, upper) = plan.into_parts();
852    MfpPlan::from_parts(
853        safe_mfp_mir_to_lir(safe),
854        lses_from_mses(&lower),
855        lses_from_mses(&upper),
856    )
857}
858
859/// Convert a LIR `MfpPlan` to MIR (always succeeds).
860pub fn mfp_plan_lir_to_mir(plan: MfpPlan<LirScalarExpr>) -> MfpPlan<MirScalarExpr> {
861    let (safe, lower, upper) = plan.into_parts();
862
863    let mfp = safe.into_mfp();
864    let expressions = mfp.expressions.iter().map(MirScalarExpr::from).collect();
865    let predicates = mfp
866        .predicates
867        .iter()
868        .map(|(pos, pred)| (*pos, MirScalarExpr::from(pred)))
869        .collect();
870    let mir_mfp = MapFilterProject::<MirScalarExpr> {
871        expressions,
872        predicates,
873        projection: mfp.projection,
874        input_arity: mfp.input_arity,
875    };
876
877    let lower = lower.iter().map(MirScalarExpr::from).collect();
878    let upper = upper.iter().map(MirScalarExpr::from).collect();
879    MfpPlan::from_parts(SafeMfpPlan::from_mfp(mir_mfp), lower, upper)
880}
881
882/// Translates a `&Vec<MirScalarExpr>` (or similar) to a `Vec<LirScalarExpr>`.
883///
884/// LIR-level expressions never contain unmaterializable functions, so this
885/// conversion is total in practice. The function follows the convention that
886/// the non-`try_` variant panics on failure: a panic here indicates a lowering
887/// bug, not a recoverable condition.
888pub(crate) fn lses_from_mses<'a>(
889    exprs: impl IntoIterator<Item = &'a MirScalarExpr>,
890) -> Vec<LirScalarExpr> {
891    match exprs
892        .into_iter()
893        .map(LirScalarExpr::try_from)
894        .collect::<Result<Vec<LirScalarExpr>, _>>()
895    {
896        Ok(exprs) => exprs,
897        Err(funcs) => {
898            panic!(
899                "unmaterializable functions cannot be translated to LirScalarExpr: {}",
900                separated(", ", &funcs)
901            )
902        }
903    }
904}