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.
910//! Fuses multiple `Union` operators into one.
11//!
12//! Nested negated unions are merged into the parent one by pushing
13//! the Negate to all their branches.
1415use std::iter;
1617use mz_expr::MirRelationExpr;
18use mz_expr::visit::Visit;
19use mz_repr::RelationType;
2021use crate::TransformCtx;
2223/// Fuses `Union` and `Negate` operators into one `Union` and multiple `Negate` operators.
24#[derive(Debug)]
25pub struct UnionNegateFusion;
2627impl crate::Transform for UnionNegateFusion {
28fn name(&self) -> &'static str {
29"UnionNegateFusion"
30}
3132#[mz_ore::instrument(
33 target = "optimizer",
34 level = "debug",
35 fields(path.segment = "union_negate")
36 )]
37fn actually_perform_transform(
38&self,
39 relation: &mut MirRelationExpr,
40_: &mut TransformCtx,
41 ) -> Result<(), crate::TransformError> {
42 relation.visit_mut_post(&mut Self::action)?;
43 mz_repr::explain::trace_plan(&*relation);
44Ok(())
45 }
46}
4748impl UnionNegateFusion {
49/// Fuses multiple `Union` operators into one.
50 /// Nested negated unions are merged into the parent one by pushing
51 /// the Negate to all their inputs.
52pub fn action(relation: &mut MirRelationExpr) {
53use MirRelationExpr::*;
54if let Union { base, inputs } = relation {
55let can_fuse = iter::once(&**base).chain(&*inputs).any(|input| -> bool {
56match input {
57 Union { .. } => true,
58 Negate { input } => matches!(**input, Union { .. }),
59_ => false,
60 }
61 });
62if can_fuse {
63let mut new_inputs: Vec<MirRelationExpr> = vec![];
64for input in iter::once(base.as_mut()).chain(inputs) {
65let input = input.take_dangerous();
66match input {
67 Union { base, inputs } => {
68 new_inputs.push(*base);
69 new_inputs.extend(inputs);
70 }
71 Negate { input } if matches!(*input, Union { .. }) => {
72if let Union { base, inputs } = *input {
73 new_inputs.push(base.negate());
74 new_inputs.extend(inputs.into_iter().map(|x| x.negate()));
75 } else {
76unreachable!()
77 }
78 }
79_ => new_inputs.push(input),
80 }
81 }
8283// Pushing down negations might enable further Negate fusion.
84for new_input in new_inputs.iter_mut() {
85crate::fusion::negate::Negate::action(new_input);
86 }
8788// A valid relation type is only needed for empty unions, but an existing union
89 // is guaranteed to be non-empty given that it always has at least a base branch.
90assert!(!new_inputs.is_empty());
91*relation = MirRelationExpr::union_many(new_inputs, RelationType::empty());
92 }
93 }
94 }
95}