Skip to main content

mz_expr/scalar/
reduce.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//! Rewrite-rule-driven simplification of `MirScalarExpr`.
11//!
12//! `reduce` repeatedly applies a fixed set of local rewrite rules to an
13//! expression until reaching a fixed point. The per-variant rules live in
14//! sibling modules (`unary`, `binary`, `variadic`, `if_then`); this file
15//! owns the fixed-point loop and the pre/post-pass dispatch.
16//!
17//! Behavioral parity with the prior imperative implementation is a goal:
18//! rule order and the pre-/post-pass split are preserved.
19
20use mz_repr::{ReprColumnType, RowArena};
21
22use crate::MirScalarExpr;
23use crate::scalar::func::UnaryFunc;
24use crate::visit::Visit;
25
26mod binary;
27mod if_then;
28mod unary;
29mod variadic;
30
31/// Reduce `expr` to a simpler equivalent form by repeatedly applying local
32/// rewrite rules until reaching a fixed point.
33pub fn reduce(expr: &mut MirScalarExpr, column_types: &[ReprColumnType]) {
34    let temp_storage = &RowArena::new();
35
36    // Simplifications run in a loop until `expr` no longer changes.
37    let mut old = MirScalarExpr::column(0);
38    while old != *expr {
39        old = expr.clone();
40        expr.visit_mut_pre_post(
41            &mut |e| {
42                reduce_pre(e, column_types);
43                None
44            },
45            &mut |e| reduce_post(e, column_types, temp_storage),
46        );
47    }
48}
49
50/// Pre-order rewrites, applied before children are visited.
51///
52/// `IsNull` and `Not` need to fire pre-order: if they push themselves inward
53/// (e.g. `Not(Not(x)) → x`), the result is the new node at this position,
54/// which the visitor will then descend into for normal post-order handling.
55fn reduce_pre(e: &mut MirScalarExpr, column_types: &[ReprColumnType]) {
56    match e {
57        MirScalarExpr::CallUnary { func, expr } => match func {
58            UnaryFunc::IsNull(_) => {
59                if !expr.typ(column_types).nullable {
60                    *e = MirScalarExpr::literal_false();
61                } else if let Some(rewritten) = expr.decompose_is_null() {
62                    // Try to at least decompose IsNull into a disjunction of
63                    // simpler IsNull subexpressions.
64                    *e = rewritten;
65                }
66            }
67            UnaryFunc::Not(_) => match &mut **expr {
68                // Push down not expressions
69                // Two negates cancel each other out.
70                MirScalarExpr::CallUnary {
71                    expr: inner_expr,
72                    func: UnaryFunc::Not(_),
73                } => {
74                    *e = inner_expr.take();
75                }
76                // Transforms `NOT(a <op> b)` to `a negate(<op>) b` if a
77                // negation exists.
78                MirScalarExpr::CallBinary {
79                    expr1,
80                    expr2,
81                    func: bf,
82                } => {
83                    if let Some(negated) = bf.negate() {
84                        *e = MirScalarExpr::CallBinary {
85                            expr1: Box::new(expr1.take()),
86                            expr2: Box::new(expr2.take()),
87                            func: negated,
88                        };
89                    }
90                }
91                MirScalarExpr::CallVariadic { .. } => e.demorgans(),
92                _ => {}
93            },
94            _ => {}
95        },
96        _ => {}
97    }
98}
99
100/// Post-order rewrites, applied after children have been reduced.
101fn reduce_post(e: &mut MirScalarExpr, column_types: &[ReprColumnType], temp_storage: &RowArena) {
102    match e {
103        // Evaluate and pull up constants
104        MirScalarExpr::Column(_, _)
105        | MirScalarExpr::Literal(_, _)
106        | MirScalarExpr::CallUnmaterializable(_) => {}
107        MirScalarExpr::CallUnary { .. } => unary::reduce_call_unary(e, column_types, temp_storage),
108        MirScalarExpr::CallBinary { .. } => {
109            binary::reduce_call_binary(e, column_types, temp_storage)
110        }
111        MirScalarExpr::CallVariadic { .. } => {
112            variadic::reduce_call_variadic(e, column_types, temp_storage)
113        }
114        MirScalarExpr::If { .. } => if_then::reduce_if(e, column_types),
115    }
116}