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//! Analysis that determines the logical monotonicity of an expression.
11//!
12//! Expressions are logically monotonic if their inputs are monotonic, and they
13//! do not introduce any retractions.
1415use std::collections::BTreeSet;
1617use mz_expr::{Id, MirRelationExpr};
18use mz_repr::GlobalId;
1920use crate::analysis::common_lattice::BoolLattice;
21use crate::analysis::{Analysis, Derived, Lattice};
2223/// Determines the logical monotonicity of an expression.
24#[derive(Debug)]
25pub struct Monotonic {
26 global_monotonic_ids: BTreeSet<GlobalId>,
27}
2829impl Monotonic {
30/// A monotonicity analyser provided with the set of Global IDs that are known to be monotonic.
31pub fn new(global_monotonic_ids: BTreeSet<GlobalId>) -> Self {
32Self {
33 global_monotonic_ids,
34 }
35 }
3637#[inline]
38fn has_monotonic_children(
39 expr: &MirRelationExpr,
40 index: usize,
41 results: &[bool],
42 depends: &Derived,
43 ) -> bool {
44 depends
45 .children_of_rev(index, expr.children().count())
46 .fold(true, |acc, child| acc && results[child])
47 }
48}
4950impl Analysis for Monotonic {
51type Value = bool;
5253fn derive(
54&self,
55 expr: &MirRelationExpr,
56 index: usize,
57 results: &[Self::Value],
58 depends: &Derived,
59 ) -> Self::Value {
60use Id::*;
61match expr {
62 MirRelationExpr::Get { id: Global(id), .. } => self.global_monotonic_ids.contains(id),
63 MirRelationExpr::Get { id: Local(id), .. } => {
64let index = *depends
65 .bindings()
66 .get(id)
67 .expect("Dependency info not found");
68*results.get(index).unwrap_or(&false)
69 }
70// Monotonic iff their input is.
71MirRelationExpr::Project { .. }
72 | MirRelationExpr::Map { .. }
73 | MirRelationExpr::ArrangeBy { .. }
74 | MirRelationExpr::Threshold { .. }
75 | MirRelationExpr::Let { .. }
76 | MirRelationExpr::LetRec { .. } => results[index - 1],
77// Monotonic iff all inputs are.
78MirRelationExpr::Union { .. } | MirRelationExpr::Join { .. } => {
79Self::has_monotonic_children(expr, index, results, depends)
80 }
81// Any set limit or offset can result in retractions when input data arrive.
82 // If neither limit nor offset are set, the TopK stage will eventually be optimized out.
83MirRelationExpr::TopK { .. } => false,
84 MirRelationExpr::Negate { .. } => false,
85 MirRelationExpr::Filter { predicates, .. } => {
86let is_monotonic = results[index - 1];
87// Temporal predicates can introduce non-monotonicity, as they
88 // can result in the future removal of records.
89 // TODO: this could be improved to only restrict if upper bounds
90 // are present, as temporal lower bounds only delay introduction.
91is_monotonic && !predicates.iter().any(|p| p.contains_temporal())
92 }
93 MirRelationExpr::Reduce { aggregates, .. } => {
94// Reduce is monotonic iff its input is, and it is a "distinct"
95 // with no aggregate values; otherwise it may need to retract.
96results[index - 1] && aggregates.is_empty()
97 }
98 MirRelationExpr::FlatMap { func, .. } => {
99 results[index - 1] && func.preserves_monotonicity()
100 }
101 MirRelationExpr::Constant { rows: Ok(rows), .. } => {
102 rows.iter().all(|(_, diff)| diff.is_positive())
103 }
104// TODO: database-issues#8337 (Investigate if constant expressions with error rows can be marked monotonic)
105MirRelationExpr::Constant { rows: Err(_), .. } => false,
106 }
107 }
108109fn lattice() -> Option<Box<dyn Lattice<Self::Value>>> {
110Some(Box::new(BoolLattice))
111 }
112}