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}