risingwave_frontend/optimizer/property/
monotonicity.rs1use std::collections::BTreeMap;
16use std::ops::Index;
17
18use enum_as_inner::EnumAsInner;
19use risingwave_common::types::DataType;
20use risingwave_pb::expr::expr_node::Type as ExprType;
21
22use crate::expr::{Expr, ExprImpl, FunctionCall, TableFunction};
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumAsInner)]
27pub enum MonotonicityDerivation {
28 Inherent(Monotonicity),
30 FollowingInput(usize),
32 _FollowingInputInversely(usize),
35}
36
37impl MonotonicityDerivation {
38 pub fn inverse(self) -> Self {
39 use MonotonicityDerivation::*;
40 match self {
41 Inherent(monotonicity) => Inherent(monotonicity.inverse()),
42 FollowingInput(idx) => _FollowingInputInversely(idx),
43 _FollowingInputInversely(idx) => FollowingInput(idx),
44 }
45 }
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
71pub enum Monotonicity {
72 Constant,
73 NonDecreasing,
74 NonIncreasing,
75 Unknown,
76}
77
78impl Monotonicity {
79 pub fn is_constant(self) -> bool {
80 matches!(self, Monotonicity::Constant)
81 }
82
83 pub fn is_non_decreasing(self) -> bool {
84 matches!(self, Monotonicity::NonDecreasing | Monotonicity::Constant)
86 }
87
88 pub fn is_non_increasing(self) -> bool {
89 matches!(self, Monotonicity::NonIncreasing | Monotonicity::Constant)
91 }
92
93 pub fn is_unknown(self) -> bool {
94 matches!(self, Monotonicity::Unknown)
95 }
96
97 pub fn inverse(self) -> Self {
98 use Monotonicity::*;
99 match self {
100 Constant => Constant,
101 NonDecreasing => NonIncreasing,
102 NonIncreasing => NonDecreasing,
103 Unknown => Unknown,
104 }
105 }
106}
107
108pub mod monotonicity_variants {
109 pub use super::Monotonicity::*;
110 pub use super::MonotonicityDerivation::*;
111}
112
113pub fn analyze_monotonicity(expr: &ExprImpl) -> MonotonicityDerivation {
115 let analyzer = MonotonicityAnalyzer {};
116 analyzer.visit_expr(expr)
117}
118
119struct MonotonicityAnalyzer {}
120
121impl MonotonicityAnalyzer {
122 fn visit_expr(&self, expr: &ExprImpl) -> MonotonicityDerivation {
123 use monotonicity_variants::*;
124 match expr {
125 ExprImpl::InputRef(inner) => FollowingInput(inner.index()),
127 ExprImpl::Literal(_) => Inherent(Constant),
128 ExprImpl::Now(_) => Inherent(NonDecreasing),
129 ExprImpl::UserDefinedFunction(_) | ExprImpl::SecretRef(_) => Inherent(Unknown),
130
131 ExprImpl::FunctionCall(inner) => self.visit_function_call(inner),
133 ExprImpl::FunctionCallWithLambda(inner) => self.visit_function_call(inner.base()),
134 ExprImpl::TableFunction(inner) => self.visit_table_function(inner),
135
136 ExprImpl::Subquery(_)
138 | ExprImpl::AggCall(_)
139 | ExprImpl::CorrelatedInputRef(_)
140 | ExprImpl::WindowFunction(_)
141 | ExprImpl::Parameter(_) => panic!(
142 "Expression `{}` is not expected in the monotonicity analyzer",
143 expr.variant_name()
144 ),
145 }
146 }
147
148 fn visit_unary_op(&self, inputs: &[ExprImpl]) -> MonotonicityDerivation {
149 assert_eq!(inputs.len(), 1);
150 self.visit_expr(&inputs[0])
151 }
152
153 fn visit_binary_op(
154 &self,
155 inputs: &[ExprImpl],
156 ) -> (MonotonicityDerivation, MonotonicityDerivation) {
157 assert_eq!(inputs.len(), 2);
158 (self.visit_expr(&inputs[0]), self.visit_expr(&inputs[1]))
159 }
160
161 fn visit_ternary_op(
162 &self,
163 inputs: &[ExprImpl],
164 ) -> (
165 MonotonicityDerivation,
166 MonotonicityDerivation,
167 MonotonicityDerivation,
168 ) {
169 assert_eq!(inputs.len(), 3);
170 (
171 self.visit_expr(&inputs[0]),
172 self.visit_expr(&inputs[1]),
173 self.visit_expr(&inputs[2]),
174 )
175 }
176
177 fn visit_function_call(&self, func_call: &FunctionCall) -> MonotonicityDerivation {
178 use monotonicity_variants::*;
179
180 fn time_zone_is_without_dst(time_zone: Option<&str>) -> bool {
181 time_zone.is_some_and(|time_zone| time_zone.eq_ignore_ascii_case("UTC")) }
183
184 fn shifts_timestamp_by_months(func_call: &FunctionCall) -> bool {
185 let [lhs, rhs] = func_call.inputs() else {
186 return false;
187 };
188
189 let interval = match (lhs.return_type(), rhs.return_type()) {
190 (DataType::Timestamp, DataType::Interval) => rhs,
191 (DataType::Interval, DataType::Timestamp) => lhs,
192 _ => return false,
193 };
194
195 interval.as_literal().is_none_or(|lit| {
196 lit.get_data()
197 .as_ref()
198 .is_some_and(|v| v.as_interval().months() != 0)
199 })
200 }
201
202 match func_call.func_type() {
203 ExprType::Unspecified => unreachable!(),
204 ExprType::Add | ExprType::Subtract if shifts_timestamp_by_months(func_call) => {
205 Inherent(Unknown)
206 }
207 ExprType::Add => match self.visit_binary_op(func_call.inputs()) {
208 (Inherent(Constant), any) | (any, Inherent(Constant)) => any,
209 (Inherent(NonDecreasing), Inherent(NonDecreasing)) => Inherent(NonDecreasing),
210 (Inherent(NonIncreasing), Inherent(NonIncreasing)) => Inherent(NonIncreasing),
211 _ => Inherent(Unknown),
212 },
213 ExprType::Subtract => match self.visit_binary_op(func_call.inputs()) {
214 (any, Inherent(Constant)) => any,
215 (Inherent(Constant), any) => any.inverse(),
216 _ => Inherent(Unknown),
217 },
218 ExprType::Multiply | ExprType::Divide | ExprType::Modulus => {
219 match self.visit_binary_op(func_call.inputs()) {
220 (Inherent(Constant), Inherent(Constant)) => Inherent(Constant),
221 _ => Inherent(Unknown), }
223 }
224 ExprType::TumbleStart => {
225 if func_call.inputs().len() == 2 {
226 match self.visit_binary_op(func_call.inputs()) {
228 (any, Inherent(Constant)) => any,
229 _ => Inherent(Unknown),
230 }
231 } else {
232 assert_eq!(ExprType::TumbleStart, func_call.func_type());
234 match self.visit_ternary_op(func_call.inputs()) {
235 (any, Inherent(Constant), Inherent(Constant)) => any,
236 _ => Inherent(Unknown),
237 }
238 }
239 }
240 ExprType::AtTimeZone => match self.visit_binary_op(func_call.inputs()) {
241 (Inherent(Constant), Inherent(Constant)) => Inherent(Constant),
242 (any, Inherent(Constant)) => {
243 let time_zone = func_call.inputs()[1]
244 .as_literal()
245 .and_then(|literal| literal.get_data().as_ref())
246 .map(|tz| tz.as_utf8().as_ref());
247 if (func_call.inputs()[0].return_type() == DataType::Timestamp
252 && func_call.return_type() == DataType::Timestamptz)
253 || time_zone_is_without_dst(time_zone)
254 {
255 any
256 } else {
257 Inherent(Unknown)
258 }
259 }
260 _ => Inherent(Unknown),
261 },
262 ExprType::DateTrunc => match func_call.inputs().len() {
263 2 => match self.visit_binary_op(func_call.inputs()) {
264 (Inherent(Constant), any) => any,
265 _ => Inherent(Unknown),
266 },
267 3 => match self.visit_ternary_op(func_call.inputs()) {
268 (Inherent(Constant), Inherent(Constant), Inherent(Constant)) => {
269 Inherent(Constant)
270 }
271 (Inherent(Constant), any, Inherent(Constant)) => {
272 let time_zone = func_call.inputs()[2]
273 .as_literal()
274 .and_then(|literal| literal.get_data().as_ref())
275 .map(|tz| tz.as_utf8().as_ref());
276 if time_zone_is_without_dst(time_zone) {
277 any
278 } else {
279 Inherent(Unknown)
280 }
281 }
282 _ => Inherent(Unknown),
283 },
284 _ => unreachable!(),
285 },
286 ExprType::AddWithTimeZone | ExprType::SubtractWithTimeZone => {
287 let time_zone = match &func_call.inputs()[2] {
289 ExprImpl::Literal(lit) => {
290 lit.get_data().as_ref().map(|tz| tz.as_utf8().as_ref())
291 }
292 _ => return Inherent(Unknown),
293 };
294 let interval = match &func_call.inputs()[1] {
295 ExprImpl::Literal(lit) => lit
296 .get_data()
297 .as_ref()
298 .map(|interval| interval.as_interval()),
299 _ => return Inherent(Unknown),
300 };
301 let quantitative_only = interval.is_none_or(|v| {
302 v.months() == 0 && (v.days() == 0 || time_zone_is_without_dst(time_zone))
303 });
304 match (self.visit_expr(&func_call.inputs()[0]), quantitative_only) {
305 (Inherent(Constant), _) => Inherent(Constant),
306 (any, true) => any,
307 _ => Inherent(Unknown),
308 }
309 }
310 ExprType::SecToTimestamptz => self.visit_unary_op(func_call.inputs()),
311 ExprType::CharToTimestamptz => Inherent(Unknown),
312 ExprType::Cast => {
313 Inherent(Unknown)
315 }
316 ExprType::Case => {
317 Inherent(Unknown)
319 }
320 ExprType::Proctime => Inherent(NonDecreasing),
321 _ => Inherent(Unknown),
322 }
323 }
324
325 fn visit_table_function(&self, _table_func: &TableFunction) -> MonotonicityDerivation {
326 use monotonicity_variants::*;
328 Inherent(Unknown)
329 }
330}
331
332#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
334pub struct MonotonicityMap(BTreeMap<usize, Monotonicity>);
335
336impl MonotonicityMap {
337 pub fn new() -> Self {
338 MonotonicityMap(BTreeMap::new())
339 }
340
341 pub fn insert(&mut self, idx: usize, monotonicity: Monotonicity) {
342 if monotonicity != Monotonicity::Unknown {
343 self.0.insert(idx, monotonicity);
344 }
345 }
346
347 pub fn iter(&self) -> impl Iterator<Item = (usize, Monotonicity)> + '_ {
348 self.0
349 .iter()
350 .map(|(idx, monotonicity)| (*idx, *monotonicity))
351 }
352}
353
354impl Index<usize> for MonotonicityMap {
355 type Output = Monotonicity;
356
357 fn index(&self, idx: usize) -> &Self::Output {
358 self.0.get(&idx).unwrap_or(&Monotonicity::Unknown)
359 }
360}
361
362impl IntoIterator for MonotonicityMap {
363 type IntoIter = std::collections::btree_map::IntoIter<usize, Monotonicity>;
364 type Item = (usize, Monotonicity);
365
366 fn into_iter(self) -> Self::IntoIter {
367 self.0.into_iter()
368 }
369}
370
371impl FromIterator<(usize, Monotonicity)> for MonotonicityMap {
372 fn from_iter<T: IntoIterator<Item = (usize, Monotonicity)>>(iter: T) -> Self {
373 MonotonicityMap(iter.into_iter().collect())
374 }
375}