mz_expr/scalar/func/impls/
range.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
10use std::fmt;
11
12use mz_lowertest::MzReflect;
13use mz_repr::adt::range::Range;
14use mz_repr::{ColumnType, Datum, RowArena, ScalarType};
15use proptest_derive::Arbitrary;
16use serde::{Deserialize, Serialize};
17
18use crate::scalar::func::{LazyUnaryFunc, stringify_datum};
19use crate::{EvalError, MirScalarExpr};
20
21#[derive(
22    Arbitrary, Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect,
23)]
24pub struct CastRangeToString {
25    pub ty: ScalarType,
26}
27
28impl LazyUnaryFunc for CastRangeToString {
29    fn eval<'a>(
30        &'a self,
31        datums: &[Datum<'a>],
32        temp_storage: &'a RowArena,
33        a: &'a MirScalarExpr,
34    ) -> Result<Datum<'a>, EvalError> {
35        let a = a.eval(datums, temp_storage)?;
36        if a.is_null() {
37            return Ok(Datum::Null);
38        }
39        let mut buf = String::new();
40        stringify_datum(&mut buf, a, &self.ty)?;
41        Ok(Datum::String(temp_storage.push_string(buf)))
42    }
43
44    fn output_type(&self, input_type: ColumnType) -> ColumnType {
45        ScalarType::String.nullable(input_type.nullable)
46    }
47
48    fn propagates_nulls(&self) -> bool {
49        true
50    }
51
52    fn introduces_nulls(&self) -> bool {
53        false
54    }
55
56    fn preserves_uniqueness(&self) -> bool {
57        true
58    }
59
60    fn inverse(&self) -> Option<crate::UnaryFunc> {
61        // TODO? if typeconv was in expr, we could determine this
62        None
63    }
64
65    fn is_monotone(&self) -> bool {
66        false
67    }
68}
69
70impl fmt::Display for CastRangeToString {
71    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
72        f.write_str("rangetostr")
73    }
74}
75
76#[derive(
77    Arbitrary, Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect,
78)]
79pub struct RangeLower;
80
81impl LazyUnaryFunc for RangeLower {
82    fn eval<'a>(
83        &'a self,
84        datums: &[Datum<'a>],
85        temp_storage: &'a RowArena,
86        a: &'a MirScalarExpr,
87    ) -> Result<Datum<'a>, EvalError> {
88        let a = a.eval(datums, temp_storage)?;
89        if a.is_null() {
90            return Ok(Datum::Null);
91        }
92        let r = a.unwrap_range();
93        Ok(Datum::from(
94            r.inner.map(|inner| inner.lower.bound).flatten(),
95        ))
96    }
97
98    fn output_type(&self, input_type: ColumnType) -> ColumnType {
99        input_type
100            .scalar_type
101            .unwrap_range_element_type()
102            .clone()
103            .nullable(true)
104    }
105
106    fn propagates_nulls(&self) -> bool {
107        true
108    }
109
110    fn introduces_nulls(&self) -> bool {
111        true
112    }
113
114    fn preserves_uniqueness(&self) -> bool {
115        false
116    }
117
118    fn inverse(&self) -> Option<crate::UnaryFunc> {
119        None
120    }
121
122    fn is_monotone(&self) -> bool {
123        true // Ranges are sorted by lower first.
124    }
125}
126
127impl fmt::Display for RangeLower {
128    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
129        f.write_str("rangelower")
130    }
131}
132
133#[derive(
134    Arbitrary, Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect,
135)]
136pub struct RangeUpper;
137
138impl LazyUnaryFunc for RangeUpper {
139    fn eval<'a>(
140        &'a self,
141        datums: &[Datum<'a>],
142        temp_storage: &'a RowArena,
143        a: &'a MirScalarExpr,
144    ) -> Result<Datum<'a>, EvalError> {
145        let a = a.eval(datums, temp_storage)?;
146        if a.is_null() {
147            return Ok(Datum::Null);
148        }
149        let r = a.unwrap_range();
150        Ok(Datum::from(
151            r.inner.map(|inner| inner.upper.bound).flatten(),
152        ))
153    }
154
155    fn output_type(&self, input_type: ColumnType) -> ColumnType {
156        input_type
157            .scalar_type
158            .unwrap_range_element_type()
159            .clone()
160            .nullable(true)
161    }
162
163    fn propagates_nulls(&self) -> bool {
164        true
165    }
166
167    fn introduces_nulls(&self) -> bool {
168        true
169    }
170
171    fn preserves_uniqueness(&self) -> bool {
172        false
173    }
174
175    fn inverse(&self) -> Option<crate::UnaryFunc> {
176        None
177    }
178
179    fn is_monotone(&self) -> bool {
180        false
181    }
182}
183
184impl fmt::Display for RangeUpper {
185    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
186        f.write_str("rangeupper")
187    }
188}
189
190sqlfunc!(
191    #[sqlname = "range_empty"]
192    fn range_empty(a: Range<Datum<'a>>) -> bool {
193        a.inner.is_none()
194    }
195);
196
197sqlfunc!(
198    #[sqlname = "range_lower_inc"]
199    fn range_lower_inc(a: Range<Datum<'a>>) -> bool {
200        match a.inner {
201            None => false,
202            Some(inner) => inner.lower.inclusive,
203        }
204    }
205);
206
207sqlfunc!(
208    #[sqlname = "range_upper_inc"]
209    fn range_upper_inc(a: Range<Datum<'a>>) -> bool {
210        match a.inner {
211            None => false,
212            Some(inner) => inner.upper.inclusive,
213        }
214    }
215);
216
217sqlfunc!(
218    #[sqlname = "range_lower_inf"]
219    fn range_lower_inf(a: Range<Datum<'a>>) -> bool {
220        match a.inner {
221            None => false,
222            Some(inner) => inner.lower.bound.is_none(),
223        }
224    }
225);
226
227sqlfunc!(
228    #[sqlname = "range_upper_inf"]
229    fn range_upper_inf(a: Range<Datum<'a>>) -> bool {
230        match a.inner {
231            None => false,
232            Some(inner) => inner.upper.bound.is_none(),
233        }
234    }
235);