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