mz_expr/scalar/func/impls/
mz_timestamp.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 chrono::{DateTime, NaiveDate, NaiveDateTime, Utc};
11use mz_ore::result::ResultExt;
12use mz_repr::adt::date::Date;
13use mz_repr::adt::numeric::Numeric;
14use mz_repr::adt::timestamp::CheckedTimestamp;
15use mz_repr::{Timestamp, strconv};
16
17use crate::EvalError;
18
19// Conversions to and from MzTimestamp to make it ergonomic to use. Although, theoretically, an
20// MzTimestamp might not always mean milliseconds-since-unix-epoch, in practice it currently always
21// does mean that. In order to increase usability of this type, we will provide casts and operators
22// that make that assumption.
23
24sqlfunc!(
25    #[sqlname = "mz_timestamp_to_text"]
26    #[preserves_uniqueness = true]
27    #[inverse = to_unary!(super::CastStringToMzTimestamp)]
28    fn cast_mz_timestamp_to_string(a: Timestamp) -> String {
29        let mut buf = String::new();
30        strconv::format_mz_timestamp(&mut buf, a);
31        buf
32    }
33);
34
35sqlfunc!(
36    #[sqlname = "text_to_mz_timestamp"]
37    #[preserves_uniqueness = false]
38    #[inverse = to_unary!(super::CastMzTimestampToString)]
39    fn cast_string_to_mz_timestamp(a: String) -> Result<Timestamp, EvalError> {
40        strconv::parse_mz_timestamp(&a).err_into()
41    }
42);
43
44sqlfunc!(
45    #[sqlname = "numeric_to_mz_timestamp"]
46    #[preserves_uniqueness = true]
47    #[is_monotone = true]
48    fn cast_numeric_to_mz_timestamp(a: Numeric) -> Result<Timestamp, EvalError> {
49        // The try_into will error if the conversion is lossy (out of range or fractional).
50        a.try_into()
51            .map_err(|_| EvalError::MzTimestampOutOfRange(a.to_string().into()))
52    }
53);
54
55sqlfunc!(
56    #[sqlname = "uint8_to_mz_timestamp"]
57    #[preserves_uniqueness = true]
58    #[is_monotone = true]
59    fn cast_uint64_to_mz_timestamp(a: u64) -> Timestamp {
60        a.into()
61    }
62);
63
64sqlfunc!(
65    #[sqlname = "uint4_to_mz_timestamp"]
66    #[preserves_uniqueness = true]
67    #[is_monotone = true]
68    fn cast_uint32_to_mz_timestamp(a: u32) -> Timestamp {
69        u64::from(a).into()
70    }
71);
72
73sqlfunc!(
74    #[sqlname = "bigint_to_mz_timestamp"]
75    #[preserves_uniqueness = true]
76    #[is_monotone = true]
77    fn cast_int64_to_mz_timestamp(a: i64) -> Result<Timestamp, EvalError> {
78        a.try_into()
79            .map_err(|_| EvalError::MzTimestampOutOfRange(a.to_string().into()))
80    }
81);
82
83sqlfunc!(
84    #[sqlname = "integer_to_mz_timestamp"]
85    #[preserves_uniqueness = true]
86    #[is_monotone = true]
87    fn cast_int32_to_mz_timestamp(a: i32) -> Result<Timestamp, EvalError> {
88        i64::from(a)
89            .try_into()
90            .map_err(|_| EvalError::MzTimestampOutOfRange(a.to_string().into()))
91    }
92);
93
94sqlfunc!(
95    #[sqlname = "timestamp_tz_to_mz_timestamp"]
96    #[is_monotone = true]
97    fn cast_timestamp_tz_to_mz_timestamp(
98        a: CheckedTimestamp<DateTime<Utc>>,
99    ) -> Result<Timestamp, EvalError> {
100        a.timestamp_millis()
101            .try_into()
102            .map_err(|_| EvalError::MzTimestampOutOfRange(a.to_string().into()))
103    }
104);
105
106sqlfunc!(
107    #[sqlname = "timestamp_to_mz_timestamp"]
108    #[is_monotone = true]
109    fn cast_timestamp_to_mz_timestamp(
110        a: CheckedTimestamp<NaiveDateTime>,
111    ) -> Result<Timestamp, EvalError> {
112        a.and_utc()
113            .timestamp_millis()
114            .try_into()
115            .map_err(|_| EvalError::MzTimestampOutOfRange(a.to_string().into()))
116    }
117);
118
119sqlfunc!(
120    #[sqlname = "date_to_mz_timestamp"]
121    #[preserves_uniqueness = true]
122    #[is_monotone = true]
123    fn cast_date_to_mz_timestamp(a: Date) -> Result<Timestamp, EvalError> {
124        let ts = CheckedTimestamp::try_from(NaiveDate::from(a).and_hms_opt(0, 0, 0).unwrap())?;
125        ts.and_utc()
126            .timestamp_millis()
127            .try_into()
128            .map_err(|_| EvalError::MzTimestampOutOfRange(a.to_string().into()))
129    }
130);
131
132sqlfunc!(
133    #[sqlname = "mz_timestamp_to_timestamp"]
134    #[preserves_uniqueness = true]
135    #[inverse = to_unary!(super::CastTimestampToMzTimestamp)]
136    fn cast_mz_timestamp_to_timestamp(
137        a: Timestamp,
138    ) -> Result<CheckedTimestamp<NaiveDateTime>, EvalError> {
139        let ms: i64 = a.try_into().map_err(|_| EvalError::TimestampOutOfRange)?;
140        let ct = DateTime::from_timestamp_millis(ms).and_then(|dt| {
141            let ct: Option<CheckedTimestamp<NaiveDateTime>> = dt.naive_utc().try_into().ok();
142            ct
143        });
144        ct.ok_or(EvalError::TimestampOutOfRange)
145    }
146);
147
148sqlfunc!(
149    #[sqlname = "mz_timestamp_to_timestamp_tz"]
150    #[preserves_uniqueness = true]
151    #[inverse = to_unary!(super::CastTimestampTzToMzTimestamp)]
152    fn cast_mz_timestamp_to_timestamp_tz(
153        a: Timestamp,
154    ) -> Result<CheckedTimestamp<DateTime<Utc>>, EvalError> {
155        let ms: i64 = a.try_into().map_err(|_| EvalError::TimestampOutOfRange)?;
156        let ct = DateTime::from_timestamp_millis(ms).and_then(|dt| {
157            let ct: Option<CheckedTimestamp<DateTime<Utc>>> = dt.try_into().ok();
158            ct
159        });
160        ct.ok_or(EvalError::TimestampOutOfRange)
161    }
162);
163
164sqlfunc!(
165    #[sqlname = "step_mz_timestamp"]
166    #[preserves_uniqueness = true]
167    #[is_monotone = true]
168    fn step_mz_timestamp(a: Timestamp) -> Result<Timestamp, EvalError> {
169        a.checked_add(1).ok_or(EvalError::MzTimestampStepOverflow)
170    }
171);