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