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