mz_transform/
equivalence_propagation.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//! Propagates expression equivalence from leaves to root, and back down again.
11//!
12//! Expression equivalences are `MirScalarExpr` replacements by simpler expressions.
13//! These equivalences derive from
14//!   Filters:  predicates must evaluate to `Datum::True`.
15//!   Maps:     new columns equal the expressions that define them.
16//!   Joins:    equated columns must be equal.
17//!   Others:   lots of other predicates we might learn (range constraints on aggregates; non-negativity)
18//!
19//! From leaf to root the equivalences are *enforced*, and communicate that the expression will not produce rows that do not satisfy the equivalence.
20//! From root to leaf the equivalences are *advised*, and communicate that the expression may discard any outputs that do not satisfy the equivalence.
21//!
22//! Importantly, in descent the operator *may not* assume any equivalence filtering will be applied to its results.
23//! It cannot therefore produce rows it would otherwise not, even rows that do not satisfy the equivalence.
24//! Operators *may* introduce filtering in descent, and they must do so to take further advantage of the equivalences.
25//!
26//! The subtlety is due to the expressions themselves causing the equivalences, and allowing more rows may invalidate equivalences.
27//! For example, we might learn that `Column(7)` equals `Literal(3)`, but must refrain from introducing that substitution in descent,
28//! because it is possible that the equivalence derives from restrictions in the expression we are visiting. Were we certain that the
29//! equivalence was independent of the expression (e.g. through a more nuanced expression traversal) we could imaging relaxing this.
30
31use std::collections::BTreeMap;
32
33use mz_expr::{Id, MirRelationExpr, MirScalarExpr};
34use mz_repr::Datum;
35
36use crate::analysis::equivalences::{EquivalenceClasses, Equivalences, ExpressionReducer};
37use crate::analysis::{Arity, DerivedView, RelationType};
38
39use crate::{TransformCtx, TransformError};
40
41/// Pulls up and pushes down predicate information represented as equivalences
42#[derive(Debug, Default)]
43pub struct EquivalencePropagation;
44
45impl crate::Transform for EquivalencePropagation {
46    fn name(&self) -> &'static str {
47        "EquivalencePropagation"
48    }
49
50    #[mz_ore::instrument(
51        target = "optimizer"
52        level = "trace",
53        fields(path.segment = "equivalence_propagation")
54    )]
55    fn actually_perform_transform(
56        &self,
57        relation: &mut MirRelationExpr,
58        ctx: &mut TransformCtx,
59    ) -> Result<(), TransformError> {
60        // Perform bottom-up equivalence class analysis.
61        use crate::analysis::DerivedBuilder;
62        let mut builder = DerivedBuilder::new(ctx.features);
63        builder.require(Equivalences);
64        let derived = builder.visit(relation);
65        let derived = derived.as_view();
66
67        let prior = relation.clone();
68
69        let mut get_equivalences = BTreeMap::default();
70        self.apply(
71            relation,
72            derived,
73            EquivalenceClasses::default(),
74            &mut get_equivalences,
75            ctx,
76        );
77
78        // Trace the plan as the result of `equivalence_propagation` before potentially applying
79        // `ColumnKnowledge`. (If `ColumnKnowledge` runs, it will trace its own result.)
80        mz_repr::explain::trace_plan(&*relation);
81
82        if prior == *relation {
83            let ck = crate::ColumnKnowledge::default();
84            ck.transform(relation, ctx)?;
85            if prior != *relation {
86                // This used to be tracing::error, but it became too common with
87                // dequadratic_eqprop_map.
88                tracing::warn!(
89                    ?ctx.global_id,
90                    "ColumnKnowledge performed work after EquivalencePropagation",
91                );
92            }
93        }
94
95        Ok(())
96    }
97}
98
99impl EquivalencePropagation {
100    /// Provides the opportunity to mutate `relation` in response to equivalences enforced by others.
101    ///
102    /// Provides the opportunity to mutate `relation` in response to equivalences enforced by their children,
103    /// as presented in `derived`, and equivalences enforced of their output (by their ancestors), as presented
104    /// in `outer_equivalences` and `get_equivalences`.
105    ///
106    /// The mutations should never invalidate an equivalence the operator has been reported as providing, as that
107    /// information may have already been acted upon by others.
108    ///
109    /// The `expr_index` argument must equal `expr`s position in post-order, so that it can be used as a reference
110    /// into `derived`. The argument can be used with the `SubtreeSize` analysis to determine the range of values
111    /// associated with `expr`.
112    ///
113    /// After the call, `get_equivalences` will be populated with certainly equivalences that will be certainly
114    /// enforced for all uses of each identifier. The information can be harvested and propagated to the definitions
115    /// of those identifiers.
116    pub fn apply(
117        &self,
118        expr: &mut MirRelationExpr,
119        derived: DerivedView,
120        mut outer_equivalences: EquivalenceClasses,
121        get_equivalences: &mut BTreeMap<Id, EquivalenceClasses>,
122        ctx: &mut TransformCtx,
123    ) {
124        // TODO: The top-down traversal can be coded as a worklist, with arguments tupled and enqueued.
125        // This has the potential to do a lot more cloning (of `outer_equivalences`), and some care is needed
126        // for `get_equivalences` which would be scoped to the whole method rather than tupled and enqueued.
127
128        let expr_type = derived
129            .value::<RelationType>()
130            .expect("RelationType required");
131        assert!(expr_type.is_some());
132        let expr_equivalences = derived
133            .value::<Equivalences>()
134            .expect("Equivalences required");
135
136        // `None` analysis values indicate collections that can be pruned.
137        let expr_equivalences = if let Some(e) = expr_equivalences {
138            e
139        } else {
140            expr.take_safely_with_col_types(expr_type.clone().unwrap());
141            return;
142        };
143
144        // Optimize `outer_equivalences` in the context of `expr_type`.
145        // If it ends up unsatisfiable, we can replace `expr` with an empty constant of the same relation type.
146        let reducer = expr_equivalences.reducer();
147        for class in outer_equivalences.classes.iter_mut() {
148            for expr in class.iter_mut() {
149                reducer.reduce_expr(expr);
150            }
151        }
152
153        outer_equivalences.minimize(expr_type.as_ref().map(|x| &x[..]));
154        if outer_equivalences.unsatisfiable() {
155            expr.take_safely_with_col_types(expr_type.clone().unwrap());
156            return;
157        }
158
159        match expr {
160            MirRelationExpr::Constant { rows, typ: _ } => {
161                if let Ok(rows) = rows {
162                    let mut datum_vec = mz_repr::DatumVec::new();
163                    // Delete any rows that violate the equivalences.
164                    // Do not delete rows that produce errors, as they are semantically important.
165                    rows.retain(|(row, _count)| {
166                        let temp_storage = mz_repr::RowArena::new();
167                        let datums = datum_vec.borrow_with(row);
168                        outer_equivalences.classes.iter().all(|class| {
169                            // Any subset of `Ok` results must equate, or we can drop the row.
170                            let mut oks = class
171                                .iter()
172                                .filter_map(|e| e.eval(&datums[..], &temp_storage).ok());
173                            if let Some(e1) = oks.next() {
174                                oks.all(|e2| e1 == e2)
175                            } else {
176                                true
177                            }
178                        })
179                    });
180                }
181            }
182            MirRelationExpr::Get { id, .. } => {
183                // Install and intersect with other equivalences from other `Get` sites.
184                // These will be read out by the corresponding `Let` binding's `value`.
185                if let Some(equivs) = get_equivalences.get_mut(id) {
186                    *equivs = equivs.union(&outer_equivalences);
187                } else {
188                    get_equivalences.insert(*id, outer_equivalences);
189                }
190            }
191            MirRelationExpr::Let { id, .. } => {
192                let id = *id;
193                // Traverse `body` first to assemble equivalences to push to `value`.
194                // Descend without a key for `id`, treating the absence as the identity for union.
195                // `Get` nodes with identifier `id` will populate the equivalence classes with the intersection of their guarantees.
196                let mut children_rev = expr.children_mut().rev().zip(derived.children_rev());
197
198                let body = children_rev.next().unwrap();
199                let value = children_rev.next().unwrap();
200
201                self.apply(
202                    body.0,
203                    body.1,
204                    outer_equivalences.clone(),
205                    get_equivalences,
206                    ctx,
207                );
208
209                // We expect to find `id` in `get_equivalences`, as otherwise the binding is
210                // not referenced and can be removed.
211                if let Some(equivalences) = get_equivalences.get(&Id::Local(id)) {
212                    self.apply(
213                        value.0,
214                        value.1,
215                        equivalences.clone(),
216                        get_equivalences,
217                        ctx,
218                    );
219                }
220            }
221            MirRelationExpr::LetRec { .. } => {
222                let mut child_iter = expr.children_mut().rev().zip(derived.children_rev());
223                // Continue in `body` with the outer equivalences.
224                let (body, view) = child_iter.next().unwrap();
225                self.apply(body, view, outer_equivalences, get_equivalences, ctx);
226                // Continue recursively, but without the outer equivalences supplied to `body`.
227                for (child, view) in child_iter {
228                    self.apply(
229                        child,
230                        view,
231                        EquivalenceClasses::default(),
232                        get_equivalences,
233                        ctx,
234                    );
235                }
236            }
237            MirRelationExpr::Project { input, outputs } => {
238                // Transform `outer_equivalences` to one relevant for `input`.
239                outer_equivalences.permute(outputs);
240                self.apply(
241                    input,
242                    derived.last_child(),
243                    outer_equivalences,
244                    get_equivalences,
245                    ctx,
246                );
247            }
248            MirRelationExpr::Map { input, scalars } => {
249                // Optimize `scalars` with respect to input equivalences.
250                let input_equivalences = derived
251                    .last_child()
252                    .value::<Equivalences>()
253                    .expect("Equivalences required");
254
255                if let Some(input_equivalences_orig) = input_equivalences {
256                    // We clone `input_equivalences` only if we want to modify it, which is when
257                    // `enable_dequadratic_eqprop_map` is off. Otherwise, we work with the original
258                    // `input_equivalences`.
259                    let mut input_equivalences_cloned = None;
260                    if !ctx.features.enable_dequadratic_eqprop_map {
261                        // We mutate them for variadic Maps if the feature flag is not set.
262                        input_equivalences_cloned = Some(input_equivalences_orig.clone());
263                    }
264                    // Get all output types, to reveal a prefix to each scaler expr.
265                    let input_types = derived
266                        .value::<RelationType>()
267                        .expect("RelationType required")
268                        .as_ref()
269                        .unwrap();
270                    let input_arity = input_types.len() - scalars.len();
271                    for (index, expr) in scalars.iter_mut().enumerate() {
272                        let reducer = if !ctx.features.enable_dequadratic_eqprop_map {
273                            input_equivalences_cloned
274                                .as_ref()
275                                .expect("always filled if feature flag is not set")
276                                .reducer()
277                        } else {
278                            input_equivalences_orig.reducer()
279                        };
280                        let changed = reducer.reduce_expr(expr);
281                        if changed || !ctx.features.enable_less_reduce_in_eqprop {
282                            expr.reduce(&input_types[..(input_arity + index)]);
283                        }
284                        if !ctx.features.enable_dequadratic_eqprop_map {
285                            // Unfortunately, we had to stop doing the following, because it
286                            // was making the `Map` handling quadratic.
287                            // TODO: Get back to this when we have e-graphs.
288                            // https://github.com/MaterializeInc/database-issues/issues/9157
289                            //
290                            // Introduce the fact relating the mapped expression and corresponding
291                            // column. This allows subsequent expressions to be optimized with this
292                            // information.
293                            input_equivalences_cloned
294                                .as_mut()
295                                .expect("always filled if feature flag is not set")
296                                .classes
297                                .push(vec![
298                                    expr.clone(),
299                                    MirScalarExpr::column(input_arity + index),
300                                ]);
301                            input_equivalences_cloned
302                                .as_mut()
303                                .expect("always filled if feature flag is not set")
304                                .minimize(Some(input_types));
305                        }
306                    }
307                    let input_arity = *derived
308                        .last_child()
309                        .value::<Arity>()
310                        .expect("Arity required");
311                    outer_equivalences.project(0..input_arity);
312                    self.apply(
313                        input,
314                        derived.last_child(),
315                        outer_equivalences,
316                        get_equivalences,
317                        ctx,
318                    );
319                }
320            }
321            MirRelationExpr::FlatMap { input, exprs, .. } => {
322                // Transform `exprs` by guarantees from `input` *and* from `outer`???
323                let input_equivalences = derived
324                    .last_child()
325                    .value::<Equivalences>()
326                    .expect("Equivalences required");
327
328                if let Some(input_equivalences) = input_equivalences {
329                    let input_types = derived
330                        .last_child()
331                        .value::<RelationType>()
332                        .expect("RelationType required");
333                    let reducer = input_equivalences.reducer();
334                    for expr in exprs.iter_mut() {
335                        let changed = reducer.reduce_expr(expr);
336                        if changed || !ctx.features.enable_less_reduce_in_eqprop {
337                            expr.reduce(input_types.as_ref().unwrap());
338                        }
339                    }
340                    let input_arity = *derived
341                        .last_child()
342                        .value::<Arity>()
343                        .expect("Arity required");
344                    outer_equivalences.project(0..input_arity);
345                    self.apply(
346                        input,
347                        derived.last_child(),
348                        outer_equivalences,
349                        get_equivalences,
350                        ctx,
351                    );
352                }
353            }
354            MirRelationExpr::Filter { input, predicates } => {
355                // Transform `predicates` by guarantees from `input` *and* from `outer`???
356                // If we reduce based on `input` guarantees, we won't be able to push those
357                // constraints down into input, which may be fine but is worth considering.
358                let input_equivalences = derived
359                    .last_child()
360                    .value::<Equivalences>()
361                    .expect("Equivalences required");
362                if let Some(input_equivalences) = input_equivalences {
363                    let input_types = derived
364                        .last_child()
365                        .value::<RelationType>()
366                        .expect("RelationType required");
367                    let reducer = input_equivalences.reducer();
368                    for expr in predicates.iter_mut() {
369                        let changed = reducer.reduce_expr(expr);
370                        if changed || !ctx.features.enable_less_reduce_in_eqprop {
371                            expr.reduce(input_types.as_ref().unwrap());
372                        }
373                    }
374                    // Incorporate `predicates` into `outer_equivalences`.
375                    let mut class = predicates.clone();
376                    class.push(MirScalarExpr::literal_ok(
377                        Datum::True,
378                        mz_repr::ScalarType::Bool,
379                    ));
380                    outer_equivalences.classes.push(class);
381                    outer_equivalences.minimize(input_types.as_ref().map(|x| &x[..]));
382                    self.apply(
383                        input,
384                        derived.last_child(),
385                        outer_equivalences,
386                        get_equivalences,
387                        ctx,
388                    );
389                }
390            }
391
392            MirRelationExpr::Join {
393                inputs,
394                equivalences,
395                ..
396            } => {
397                // Certain equivalences are ensured by each of the inputs.
398                // Other equivalences are imposed by parents of the expression.
399                // We must not weaken the properties provided by the expression to its parents,
400                // meaning we can optimize `equivalences` with respect to input guarantees,
401                // but not with respect to `outer_equivalences`.
402
403                // Each child can be presented with the integration of `join_equivalences`, `outer_equivalences`,
404                // and each input equivalence *other than* their own, projected onto the input's columns.
405
406                // Enumerate locations to find each child's analysis outputs.
407                let mut children: Vec<_> = derived.children_rev().collect::<Vec<_>>();
408                children.reverse();
409
410                // Assemble the appended input types, for use in expression minimization.
411                // Do not use `expr_types`, which may reflect nullability that does not hold for the inputs.
412                let mut input_types = Some(
413                    children
414                        .iter()
415                        .flat_map(|c| {
416                            c.value::<RelationType>()
417                                .expect("RelationType required")
418                                .as_ref()
419                                .unwrap()
420                                .iter()
421                                .cloned()
422                        })
423                        .collect::<Vec<_>>(),
424                );
425
426                // For each child, assemble its equivalences using join-relative column numbers.
427                // Don't do anything with the children yet, as we'll want to revisit each with
428                // this information at hand.
429                let mut columns = 0;
430                let mut input_equivalences = Vec::with_capacity(children.len());
431                for child in children.iter() {
432                    let child_arity = child.value::<Arity>().expect("Arity required");
433                    let equivalences = child
434                        .value::<Equivalences>()
435                        .expect("Equivalences required")
436                        .clone();
437
438                    if let Some(mut equivalences) = equivalences {
439                        let permutation = (columns..(columns + child_arity)).collect::<Vec<_>>();
440                        equivalences.permute(&permutation);
441                        equivalences.minimize(input_types.as_ref().map(|x| &x[..]));
442                        input_equivalences.push(equivalences);
443                    }
444                    columns += child_arity;
445                }
446
447                // Form the equivalences we will use to replace `equivalences`.
448                let mut join_equivalences = EquivalenceClasses::default();
449                join_equivalences
450                    .classes
451                    .extend(equivalences.iter().cloned());
452                // // Optionally, introduce `outer_equivalences` into `equivalences`.
453                // // This is not required, but it could be very helpful. To be seen.
454                // join_equivalences
455                //     .classes
456                //     .extend(outer_equivalences.classes.clone());
457
458                // Reduce join equivalences by the input equivalences.
459                for input_equivs in input_equivalences.iter() {
460                    let reducer = input_equivs.reducer();
461                    for class in join_equivalences.classes.iter_mut() {
462                        for expr in class.iter_mut() {
463                            // Semijoin elimination currently fails if you do more advanced simplification than
464                            // literal substitution.
465                            let old = expr.clone();
466                            let changed = reducer.reduce_expr(expr);
467                            let acceptable_sub = literal_domination(&old, expr);
468                            if changed || !ctx.features.enable_less_reduce_in_eqprop {
469                                expr.reduce(input_types.as_ref().unwrap());
470                            }
471                            if !acceptable_sub && !literal_domination(&old, expr) {
472                                expr.clone_from(&old);
473                            }
474                        }
475                    }
476                }
477                // Remove nullability information, as it has already been incorporated from input equivalences,
478                // and if it was reduced out relative to input equivalences we don't want to re-introduce it.
479                if let Some(input_types) = input_types.as_mut() {
480                    for col in input_types.iter_mut() {
481                        col.nullable = true;
482                    }
483                }
484                join_equivalences.minimize(input_types.as_ref().map(|x| &x[..]));
485
486                // Revisit each child, determining the information to present to it, and recurring.
487                let mut columns = 0;
488                for ((index, child), expr) in
489                    children.into_iter().enumerate().zip(inputs.iter_mut())
490                {
491                    let child_arity = child.value::<Arity>().expect("Arity required");
492
493                    let mut push_equivalences = join_equivalences.clone();
494                    push_equivalences
495                        .classes
496                        .extend(outer_equivalences.classes.clone());
497                    for (other, input_equivs) in input_equivalences.iter().enumerate() {
498                        if index != other {
499                            push_equivalences
500                                .classes
501                                .extend(input_equivs.classes.clone());
502                        }
503                    }
504                    push_equivalences.project(columns..(columns + child_arity));
505                    self.apply(expr, child, push_equivalences, get_equivalences, ctx);
506
507                    columns += child_arity;
508                }
509
510                equivalences.clone_from(&join_equivalences.classes);
511            }
512            MirRelationExpr::Reduce {
513                input,
514                group_key,
515                aggregates,
516                ..
517            } => {
518                // TODO: MIN, MAX, ANY, ALL aggregates pass through all certain properties of their columns.
519                // This may involve projection and permutation, to reposition the information appropriately.
520                // TODO: Non-null constraints likely push down into the support of the aggregate expressions.
521
522                // Apply any equivalences about the input to key and aggregate expressions.
523                let input_equivalences = derived
524                    .last_child()
525                    .value::<Equivalences>()
526                    .expect("Equivalences required");
527                if let Some(input_equivalences) = input_equivalences {
528                    let input_type = derived
529                        .last_child()
530                        .value::<RelationType>()
531                        .expect("RelationType required");
532                    let reducer = input_equivalences.reducer();
533                    for key in group_key.iter_mut() {
534                        // Semijoin elimination currently fails if you do more advanced simplification than
535                        // literal substitution.
536                        let old_key = key.clone();
537                        let changed = reducer.reduce_expr(key);
538                        let acceptable_sub = literal_domination(&old_key, key);
539                        if changed || !ctx.features.enable_less_reduce_in_eqprop {
540                            key.reduce(input_type.as_ref().unwrap());
541                        }
542                        if !acceptable_sub && !literal_domination(&old_key, key) {
543                            key.clone_from(&old_key);
544                        }
545                    }
546                    for aggr in aggregates.iter_mut() {
547                        let changed = reducer.reduce_expr(&mut aggr.expr);
548                        if changed || !ctx.features.enable_less_reduce_in_eqprop {
549                            aggr.expr.reduce(input_type.as_ref().unwrap());
550                        }
551                        // A count expression over a non-null expression can discard the expression.
552                        if aggr.func == mz_expr::AggregateFunc::Count && !aggr.distinct {
553                            let mut probe = aggr.expr.clone().call_is_null();
554                            reducer.reduce_expr(&mut probe);
555                            if probe.is_literal_false() {
556                                aggr.expr = MirScalarExpr::literal_true();
557                            }
558                        }
559                    }
560                }
561
562                // To transform `outer_equivalences` to one about `input`, we will "pretend" to pre-pend all of
563                // the input columns, introduce equivalences about the evaluation of `group_key` on them
564                // and the key columns themselves, and then project onto these "input" columns.
565                let input_arity = *derived
566                    .last_child()
567                    .value::<Arity>()
568                    .expect("Arity required");
569                let output_arity = *derived.value::<Arity>().expect("Arity required");
570
571                // Permute `outer_equivalences` to reference columns `input_arity` later.
572                let permutation = (input_arity..(input_arity + output_arity)).collect::<Vec<_>>();
573                outer_equivalences.permute(&permutation[..]);
574                for (index, group) in group_key.iter().enumerate() {
575                    outer_equivalences.classes.push(vec![
576                        MirScalarExpr::Column(input_arity + index),
577                        group.clone(),
578                    ]);
579                }
580                outer_equivalences.project(0..input_arity);
581                self.apply(
582                    input,
583                    derived.last_child(),
584                    outer_equivalences,
585                    get_equivalences,
586                    ctx,
587                );
588            }
589            MirRelationExpr::TopK {
590                input,
591                group_key,
592                limit,
593                ..
594            } => {
595                // We must be careful when updating `limit` to not install column references
596                // outside of `group_key`. We'll do this for now with `literal_domination`,
597                // which will ensure we only perform substitutions by a literal.
598                let input_equivalences = derived
599                    .last_child()
600                    .value::<Equivalences>()
601                    .expect("Equivalences required");
602                if let Some(input_equivalences) = input_equivalences {
603                    let input_types = derived
604                        .last_child()
605                        .value::<RelationType>()
606                        .expect("RelationType required");
607                    let reducer = input_equivalences.reducer();
608                    if let Some(expr) = limit {
609                        let old_expr = expr.clone();
610                        let changed = reducer.reduce_expr(expr);
611                        let acceptable_sub = literal_domination(&old_expr, expr);
612                        if changed || !ctx.features.enable_less_reduce_in_eqprop {
613                            expr.reduce(input_types.as_ref().unwrap());
614                        }
615                        if !acceptable_sub && !literal_domination(&old_expr, expr) {
616                            expr.clone_from(&old_expr);
617                        }
618                    }
619                }
620
621                // Discard equivalences among non-key columns, as it is not correct that `input` may drop rows
622                // that violate constraints among non-key columns without affecting the result.
623                outer_equivalences.project(0..group_key.len());
624                self.apply(
625                    input,
626                    derived.last_child(),
627                    outer_equivalences,
628                    get_equivalences,
629                    ctx,
630                );
631            }
632            MirRelationExpr::Negate { input } => {
633                self.apply(
634                    input,
635                    derived.last_child(),
636                    outer_equivalences,
637                    get_equivalences,
638                    ctx,
639                );
640            }
641            MirRelationExpr::Threshold { input } => {
642                self.apply(
643                    input,
644                    derived.last_child(),
645                    outer_equivalences,
646                    get_equivalences,
647                    ctx,
648                );
649            }
650            MirRelationExpr::Union { .. } => {
651                for (child, derived) in expr.children_mut().rev().zip(derived.children_rev()) {
652                    self.apply(
653                        child,
654                        derived,
655                        outer_equivalences.clone(),
656                        get_equivalences,
657                        ctx,
658                    );
659                }
660            }
661            MirRelationExpr::ArrangeBy { input, .. } => {
662                // TODO: Option to alter arrangement keys, though .. terrifying.
663                self.apply(
664                    input,
665                    derived.last_child(),
666                    outer_equivalences,
667                    get_equivalences,
668                    ctx,
669                );
670            }
671        }
672    }
673}
674
675/// Logic encapsulating our willingness to accept an expression simplification.
676///
677/// For reasons of robustness, we cannot yet perform all recommended simplifications.
678/// Certain transforms expect idiomatic expressions, often around precise use of column
679/// identifiers, rather than equivalent identifiers.
680///
681/// The substitutions we are confident with are those that introduce literals for columns,
682/// or which replace column nullability checks with literals.
683fn literal_domination(old: &MirScalarExpr, new: &MirScalarExpr) -> bool {
684    let mut todo = vec![(old, new)];
685    while let Some((old, new)) = todo.pop() {
686        match (old, new) {
687            (_, MirScalarExpr::Literal(_, _)) => {
688                // Substituting a literal is always acceptable; we don't need to consult
689                // the result of the old expression to determine this.
690            }
691            (
692                MirScalarExpr::CallUnary { func: f0, expr: e0 },
693                MirScalarExpr::CallUnary { func: f1, expr: e1 },
694            ) => {
695                if f0 != f1 {
696                    return false;
697                } else {
698                    todo.push((&**e0, &**e1));
699                }
700            }
701            (
702                MirScalarExpr::CallBinary {
703                    func: f0,
704                    expr1: e01,
705                    expr2: e02,
706                },
707                MirScalarExpr::CallBinary {
708                    func: f1,
709                    expr1: e11,
710                    expr2: e12,
711                },
712            ) => {
713                if f0 != f1 {
714                    return false;
715                } else {
716                    todo.push((&**e01, &**e11));
717                    todo.push((&**e02, &**e12));
718                }
719            }
720            (
721                MirScalarExpr::CallVariadic {
722                    func: f0,
723                    exprs: e0s,
724                },
725                MirScalarExpr::CallVariadic {
726                    func: f1,
727                    exprs: e1s,
728                },
729            ) => {
730                use itertools::Itertools;
731                if f0 != f1 {
732                    return false;
733                } else {
734                    todo.extend(e0s.iter().zip_eq(e1s));
735                }
736            }
737            (
738                MirScalarExpr::If {
739                    cond: c0,
740                    then: t0,
741                    els: e0,
742                },
743                MirScalarExpr::If {
744                    cond: c1,
745                    then: t1,
746                    els: e1,
747                },
748            ) => {
749                todo.push((&**c0, &**c1));
750                todo.push((&**t0, &**t1));
751                todo.push((&**e0, &**e1))
752            }
753            _ => {
754                if old != new {
755                    return false;
756                }
757            }
758        }
759    }
760    true
761}