mz_expr/scalar/func/impls/
date.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 chrono::{DateTime, Datelike, NaiveDate, NaiveDateTime, Utc};
13use mz_lowertest::MzReflect;
14use mz_repr::adt::date::Date;
15use mz_repr::adt::datetime::DateTimeUnits;
16use mz_repr::adt::numeric::Numeric;
17use mz_repr::adt::timestamp::{CheckedTimestamp, DateLike, TimestampPrecision};
18use mz_repr::{SqlColumnType, SqlScalarType, strconv};
19use serde::{Deserialize, Serialize};
20
21use crate::EvalError;
22use crate::func::most_significant_unit;
23use crate::scalar::func::EagerUnaryFunc;
24
25sqlfunc!(
26    #[sqlname = "date_to_text"]
27    #[preserves_uniqueness = true]
28    #[inverse = to_unary!(super::CastStringToDate)]
29    fn cast_date_to_string(a: Date) -> String {
30        let mut buf = String::new();
31        strconv::format_date(&mut buf, a);
32        buf
33    }
34);
35
36#[derive(Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect)]
37pub struct CastDateToTimestamp(pub Option<TimestampPrecision>);
38
39impl<'a> EagerUnaryFunc<'a> for CastDateToTimestamp {
40    type Input = Date;
41    type Output = Result<CheckedTimestamp<NaiveDateTime>, EvalError>;
42
43    fn call(&self, a: Date) -> Result<CheckedTimestamp<NaiveDateTime>, EvalError> {
44        let out =
45            CheckedTimestamp::from_timestamplike(NaiveDate::from(a).and_hms_opt(0, 0, 0).unwrap())?;
46        let updated = out.round_to_precision(self.0)?;
47        Ok(updated)
48    }
49
50    fn output_type(&self, input: SqlColumnType) -> SqlColumnType {
51        SqlScalarType::Timestamp { precision: self.0 }.nullable(input.nullable)
52    }
53
54    fn preserves_uniqueness(&self) -> bool {
55        true
56    }
57
58    fn inverse(&self) -> Option<crate::UnaryFunc> {
59        to_unary!(super::CastTimestampToDate)
60    }
61
62    fn is_monotone(&self) -> bool {
63        true
64    }
65}
66
67impl fmt::Display for CastDateToTimestamp {
68    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
69        f.write_str("date_to_timestamp")
70    }
71}
72
73#[derive(Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect)]
74pub struct CastDateToTimestampTz(pub Option<TimestampPrecision>);
75
76impl<'a> EagerUnaryFunc<'a> for CastDateToTimestampTz {
77    type Input = Date;
78    type Output = Result<CheckedTimestamp<DateTime<Utc>>, EvalError>;
79
80    fn call(&self, a: Date) -> Result<CheckedTimestamp<DateTime<Utc>>, EvalError> {
81        let out =
82            CheckedTimestamp::from_timestamplike(DateTime::<Utc>::from_naive_utc_and_offset(
83                NaiveDate::from(a).and_hms_opt(0, 0, 0).unwrap(),
84                Utc,
85            ))?;
86        let updated = out.round_to_precision(self.0)?;
87        Ok(updated)
88    }
89
90    fn output_type(&self, input: SqlColumnType) -> SqlColumnType {
91        SqlScalarType::TimestampTz { precision: self.0 }.nullable(input.nullable)
92    }
93
94    fn preserves_uniqueness(&self) -> bool {
95        true
96    }
97
98    fn inverse(&self) -> Option<crate::UnaryFunc> {
99        to_unary!(super::CastTimestampTzToDate)
100    }
101
102    fn is_monotone(&self) -> bool {
103        true
104    }
105}
106
107impl fmt::Display for CastDateToTimestampTz {
108    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
109        f.write_str("date_to_timestamp_with_timezone")
110    }
111}
112
113pub fn extract_date_inner(units: DateTimeUnits, date: NaiveDate) -> Result<Numeric, EvalError> {
114    match units {
115        DateTimeUnits::Epoch => Ok(Numeric::from(date.extract_epoch())),
116        DateTimeUnits::Millennium => Ok(Numeric::from(date.millennium())),
117        DateTimeUnits::Century => Ok(Numeric::from(date.century())),
118        DateTimeUnits::Decade => Ok(Numeric::from(date.decade())),
119        DateTimeUnits::Year => Ok(Numeric::from(date.year())),
120        DateTimeUnits::Quarter => Ok(Numeric::from(date.quarter())),
121        DateTimeUnits::Week => Ok(Numeric::from(date.iso_week_number())),
122        DateTimeUnits::Month => Ok(Numeric::from(date.month())),
123        DateTimeUnits::Day => Ok(Numeric::from(date.day())),
124        DateTimeUnits::DayOfWeek => Ok(Numeric::from(date.day_of_week())),
125        DateTimeUnits::DayOfYear => Ok(Numeric::from(date.ordinal())),
126        DateTimeUnits::IsoDayOfWeek => Ok(Numeric::from(date.iso_day_of_week())),
127        DateTimeUnits::Hour
128        | DateTimeUnits::Minute
129        | DateTimeUnits::Second
130        | DateTimeUnits::Milliseconds
131        | DateTimeUnits::Microseconds => Err(EvalError::UnsupportedUnits(
132            format!("{}", units).into(),
133            "date".into(),
134        )),
135        DateTimeUnits::Timezone
136        | DateTimeUnits::TimezoneHour
137        | DateTimeUnits::TimezoneMinute
138        | DateTimeUnits::IsoDayOfYear => Err(EvalError::Unsupported {
139            feature: format!("'{}' timestamp units", units).into(),
140            discussion_no: None,
141        }),
142    }
143}
144
145#[derive(Ord, PartialOrd, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, MzReflect)]
146pub struct ExtractDate(pub DateTimeUnits);
147
148impl<'a> EagerUnaryFunc<'a> for ExtractDate {
149    type Input = Date;
150    type Output = Result<Numeric, EvalError>;
151
152    fn call(&self, a: Date) -> Result<Numeric, EvalError> {
153        extract_date_inner(self.0, a.into())
154    }
155
156    fn output_type(&self, input: SqlColumnType) -> SqlColumnType {
157        SqlScalarType::Numeric { max_scale: None }.nullable(input.nullable)
158    }
159
160    fn is_monotone(&self) -> bool {
161        most_significant_unit(self.0)
162    }
163}
164
165impl fmt::Display for ExtractDate {
166    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
167        write!(f, "extract_{}_d", self.0)
168    }
169}