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 std::fmt;
11
12use mz_expr_derive::sqlfunc;
13use mz_lowertest::MzReflect;
14use mz_repr::adt::range::Range;
15use mz_repr::{Datum, RowArena, SqlColumnType, SqlScalarType};
16use serde::{Deserialize, Serialize};
17
18use crate::scalar::func::{LazyUnaryFunc, stringify_datum};
19use crate::{EvalError, MirScalarExpr};
20
21#[derive(
22    Ord,
23    PartialOrd,
24    Clone,
25    Debug,
26    Eq,
27    PartialEq,
28    Serialize,
29    Deserialize,
30    Hash,
31    MzReflect
32)]
33pub struct CastRangeToString {
34    pub ty: SqlScalarType,
35}
36
37impl LazyUnaryFunc for CastRangeToString {
38    fn eval<'a>(
39        &'a self,
40        datums: &[Datum<'a>],
41        temp_storage: &'a RowArena,
42        a: &'a MirScalarExpr,
43    ) -> Result<Datum<'a>, EvalError> {
44        let a = a.eval(datums, temp_storage)?;
45        if a.is_null() {
46            return Ok(Datum::Null);
47        }
48        let mut buf = String::new();
49        stringify_datum(&mut buf, a, &self.ty)?;
50        Ok(Datum::String(temp_storage.push_string(buf)))
51    }
52
53    fn output_type(&self, input_type: SqlColumnType) -> SqlColumnType {
54        SqlScalarType::String.nullable(input_type.nullable)
55    }
56
57    fn propagates_nulls(&self) -> bool {
58        true
59    }
60
61    fn introduces_nulls(&self) -> bool {
62        false
63    }
64
65    fn preserves_uniqueness(&self) -> bool {
66        true
67    }
68
69    fn inverse(&self) -> Option<crate::UnaryFunc> {
70        // TODO? if typeconv was in expr, we could determine this
71        None
72    }
73
74    fn is_monotone(&self) -> bool {
75        false
76    }
77}
78
79impl fmt::Display for CastRangeToString {
80    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
81        f.write_str("rangetostr")
82    }
83}
84
85#[sqlfunc(
86    sqlname = "rangelower",
87    is_monotone = true,
88    introduces_nulls = true,
89    output_type_expr = input_type.scalar_type.unwrap_range_element_type().clone().nullable(true)
90)]
91fn range_lower<'a>(a: Range<Datum<'a>>) -> Option<Datum<'a>> {
92    a.inner.map(|inner| inner.lower.bound).flatten()
93}
94
95#[sqlfunc(
96    sqlname = "rangeupper",
97    introduces_nulls = true,
98    output_type_expr = input_type.scalar_type.unwrap_range_element_type().clone().nullable(true)
99)]
100fn range_upper<'a>(a: Range<Datum<'a>>) -> Option<Datum<'a>> {
101    a.inner.map(|inner| inner.upper.bound).flatten()
102}
103
104#[sqlfunc(sqlname = "range_empty")]
105fn range_empty<'a>(a: Range<Datum<'a>>) -> bool {
106    a.inner.is_none()
107}
108
109#[sqlfunc(sqlname = "range_lower_inc")]
110fn range_lower_inc<'a>(a: Range<Datum<'a>>) -> bool {
111    match a.inner {
112        None => false,
113        Some(inner) => inner.lower.inclusive,
114    }
115}
116
117#[sqlfunc(sqlname = "range_upper_inc")]
118fn range_upper_inc<'a>(a: Range<Datum<'a>>) -> bool {
119    match a.inner {
120        None => false,
121        Some(inner) => inner.upper.inclusive,
122    }
123}
124
125#[sqlfunc(sqlname = "range_lower_inf")]
126fn range_lower_inf<'a>(a: Range<Datum<'a>>) -> bool {
127    match a.inner {
128        None => false,
129        Some(inner) => inner.lower.bound.is_none(),
130    }
131}
132
133#[sqlfunc(sqlname = "range_upper_inf")]
134fn range_upper_inf<'a>(a: Range<Datum<'a>>) -> bool {
135    match a.inner {
136        None => false,
137        Some(inner) => inner.upper.bound.is_none(),
138    }
139}