Skip to main content

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