Skip to main content

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 mz_expr_derive::sqlfunc;
11use mz_repr::adt::range::Range;
12use mz_repr::{Datum, ExcludeNull, SqlScalarType};
13use serde::{Deserialize, Serialize};
14
15use crate::EvalError;
16use crate::scalar::func::stringify_datum;
17
18#[derive(
19    Ord,
20    PartialOrd,
21    Clone,
22    Debug,
23    Eq,
24    PartialEq,
25    Serialize,
26    Deserialize,
27    Hash
28)]
29pub struct CastRangeToString {
30    pub ty: SqlScalarType,
31}
32
33// TODO? if typeconv was in expr, we could determine the inverse of this cast
34#[sqlfunc(CastRangeToString, sqlname = "rangetostr", preserves_uniqueness = true)]
35fn cast_range_to_string<'a>(&self, a: ExcludeNull<Datum<'a>>) -> Result<String, EvalError> {
36    let mut buf = String::new();
37    stringify_datum(&mut buf, *a, &self.ty)?;
38    Ok(buf)
39}
40
41// The monotone claim survives this function mapping empty and
42// unbounded-lower ranges to NULL, which the interpreter's endpoint box
43// cannot represent, only because those inputs form a downward-closed
44// prefix of the range ordering (`None` inner sorts below `Some`, and a
45// `None` lower bound sorts below every finite one): a range whose
46// endpoints both yield values contains no NULL-yielding interior. Any
47// change to range ordering or to this function's NULL cases must revisit
48// the claim; see `try_parse_monotonic_iso8601_timestamp` for the
49// SpecialUnary alternative.
50#[sqlfunc(sqlname = "rangelower", is_monotone = true)]
51fn range_lower<T>(a: Range<T>) -> Option<T> {
52    a.inner.map(|inner| inner.lower.bound).flatten()
53}
54
55#[sqlfunc(sqlname = "rangeupper")]
56fn range_upper<T>(a: Range<T>) -> Option<T> {
57    a.inner.map(|inner| inner.upper.bound).flatten()
58}
59
60#[sqlfunc(sqlname = "range_empty")]
61fn range_empty<T>(a: Range<T>) -> bool {
62    a.inner.is_none()
63}
64
65#[sqlfunc(sqlname = "range_lower_inc")]
66fn range_lower_inc<T>(a: Range<T>) -> bool {
67    match a.inner {
68        None => false,
69        Some(inner) => inner.lower.inclusive,
70    }
71}
72
73#[sqlfunc(sqlname = "range_upper_inc")]
74fn range_upper_inc<T>(a: Range<T>) -> bool {
75    match a.inner {
76        None => false,
77        Some(inner) => inner.upper.inclusive,
78    }
79}
80
81#[sqlfunc(sqlname = "range_lower_inf")]
82fn range_lower_inf<T>(a: Range<T>) -> bool {
83    match a.inner {
84        None => false,
85        Some(inner) => inner.lower.bound.is_none(),
86    }
87}
88
89#[sqlfunc(sqlname = "range_upper_inf")]
90fn range_upper_inf<T>(a: Range<T>) -> bool {
91    match a.inner {
92        None => false,
93        Some(inner) => inner.upper.bound.is_none(),
94    }
95}