mz_transform/reduce_elision.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//! Removes `Reduce` when the input has as unique keys the keys of the reduce.
11//!
12//! When a reduce has grouping keys that are contained within a
13//! set of columns that form unique keys for the input, the reduce
14//! can be simplified to a map operation.
15
16use itertools::Itertools;
17use mz_expr::MirRelationExpr;
18
19use crate::TransformCtx;
20use crate::analysis::{DerivedBuilder, DerivedView};
21use crate::analysis::{RelationType, UniqueKeys};
22
23/// Removes `Reduce` when the input has as unique keys the keys of the reduce.
24#[derive(Debug)]
25pub struct ReduceElision;
26
27impl crate::Transform for ReduceElision {
28 fn name(&self) -> &'static str {
29 "ReduceElision"
30 }
31
32 #[mz_ore::instrument(
33 target = "optimizer",
34 level = "debug",
35 fields(path.segment = "reduce_elision")
36 )]
37 fn actually_perform_transform(
38 &self,
39 relation: &mut MirRelationExpr,
40 ctx: &mut TransformCtx,
41 ) -> Result<(), crate::TransformError> {
42 // Assemble type information once for the whole expression.
43 let mut builder = DerivedBuilder::new(ctx.features);
44 builder.require(RelationType);
45 builder.require(UniqueKeys);
46 let derived = builder.visit(relation);
47 let derived_view = derived.as_view();
48
49 self.action(relation, derived_view);
50
51 mz_repr::explain::trace_plan(&*relation);
52 Ok(())
53 }
54}
55
56impl ReduceElision {
57 /// Removes `Reduce` when the input has as unique keys the keys of the reduce.
58 pub fn action(&self, relation: &mut MirRelationExpr, derived: DerivedView) {
59 let mut todo = vec![(relation, derived)];
60 while let Some((expr, view)) = todo.pop() {
61 let mut replaced = false;
62 if let MirRelationExpr::Reduce {
63 input,
64 group_key,
65 aggregates,
66 monotonic: _,
67 expected_group_size: _,
68 } = expr
69 {
70 let input_type = view
71 .last_child()
72 .value::<RelationType>()
73 .expect("RelationType required")
74 .as_ref()
75 .expect("Expression not well-typed");
76 let input_keys = view
77 .last_child()
78 .value::<UniqueKeys>()
79 .expect("UniqueKeys required");
80
81 if input_keys.iter().any(|keys| {
82 keys.iter()
83 .all(|k| group_key.contains(&mz_expr::MirScalarExpr::Column(*k)))
84 }) {
85 let map_scalars = aggregates
86 .iter()
87 .map(|a| a.on_unique(input_type))
88 .collect_vec();
89
90 let mut result = input.take_dangerous();
91
92 let input_arity = input_type.len();
93
94 // Append the group keys, then any `map_scalars`, then project
95 // to put them all in the right order.
96 let mut new_scalars = group_key.clone();
97 new_scalars.extend(map_scalars);
98 result = result.map(new_scalars).project(
99 (input_arity..(input_arity + (group_key.len() + aggregates.len())))
100 .collect(),
101 );
102
103 *expr = result;
104 replaced = true;
105
106 // // NB: The following is borked because of smart builders.
107 // if let MirRelationExpr::Project { input, .. } = expr {
108 // if let MirRelationExpr::Map { input, .. } = &mut **input {
109 // todo.push((&mut **input, view.last_child()))
110 // }
111 // }
112 }
113 }
114
115 // This gets around an awkward borrow of both `expr` and `input` above.
116 if !replaced {
117 todo.extend(expr.children_mut().rev().zip(view.children_rev()));
118 }
119 }
120 }
121}