mz_transform/fusion/filter.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//! Fuses multiple `Filter` operators into one and canonicalizes predicates.
11//!
12//! If the `Filter` operator is empty, removes it.
13//!
14//! ```rust
15//! use mz_expr::{MirRelationExpr, MirScalarExpr};
16//! use mz_repr::{ColumnType, Datum, RelationType, ScalarType};
17//! use mz_repr::optimize::OptimizerFeatures;
18//! use mz_transform::{typecheck, Transform, TransformCtx};
19//! use mz_transform::dataflow::DataflowMetainfo;
20//!
21//! use mz_transform::fusion::filter::Filter;
22//!
23//! let input = MirRelationExpr::constant(vec![], RelationType::new(vec![
24//! ScalarType::Bool.nullable(false),
25//! ]));
26//!
27//! let predicate0 = MirScalarExpr::Column(0);
28//! let predicate1 = MirScalarExpr::Column(0);
29//! let predicate2 = MirScalarExpr::Column(0);
30//!
31//! let mut expr = input
32//! .clone()
33//! .filter(vec![predicate0.clone()])
34//! .filter(vec![predicate1.clone()])
35//! .filter(vec![predicate2.clone()]);
36//!
37//! let features = OptimizerFeatures::default();
38//! let typecheck_ctx = typecheck::empty_context();
39//! let mut df_meta = DataflowMetainfo::default();
40//! let mut transform_ctx = TransformCtx::local(&features, &typecheck_ctx, &mut df_meta, None);
41//!
42//! // Filter.transform() will deduplicate any predicates
43//! Filter.transform(&mut expr, &mut transform_ctx);
44//!
45//! let correct = input.filter(vec![predicate0]);
46//!
47//! assert_eq!(expr, correct);
48//! ```
49
50use mz_expr::MirRelationExpr;
51
52use crate::TransformCtx;
53
54/// Fuses multiple `Filter` operators into one and deduplicates predicates.
55#[derive(Debug)]
56pub struct Filter;
57
58impl crate::Transform for Filter {
59 fn name(&self) -> &'static str {
60 "FilterFusion"
61 }
62
63 #[mz_ore::instrument(
64 target = "optimizer",
65 level = "debug",
66 fields(path.segment = "filter_fusion")
67 )]
68 fn actually_perform_transform(
69 &self,
70 relation: &mut MirRelationExpr,
71 _: &mut TransformCtx,
72 ) -> Result<(), crate::TransformError> {
73 relation.visit_pre_mut(Self::action);
74 mz_repr::explain::trace_plan(&*relation);
75 Ok(())
76 }
77}
78
79impl Filter {
80 /// Fuses multiple `Filter` operators into one and canonicalizes predicates.
81 pub fn action(relation: &mut MirRelationExpr) {
82 if let MirRelationExpr::Filter { input, predicates } = relation {
83 // consolidate nested filters.
84 while let MirRelationExpr::Filter {
85 input: inner,
86 predicates: p2,
87 } = &mut **input
88 {
89 predicates.append(p2);
90 *input = Box::new(inner.take_dangerous());
91 }
92
93 mz_expr::canonicalize::canonicalize_predicates(predicates, &input.typ().column_types);
94
95 // remove the Filter stage if empty.
96 if predicates.is_empty() {
97 *relation = input.take_dangerous();
98 }
99 }
100 }
101}