mz_expr/scalar/func/impls/
interval.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::NaiveTime;
11use mz_expr_derive::sqlfunc;
12use mz_ore::cast::CastLossy;
13use mz_repr::adt::interval::{Interval, USECS_PER_DAY};
14use mz_repr::strconv;
15use num::traits::CheckedNeg;
16
17use crate::EvalError;
18
19#[sqlfunc(
20    sqlname = "interval_to_text",
21    preserves_uniqueness = true,
22    inverse = to_unary!(super::CastStringToInterval)
23)]
24fn cast_interval_to_string(a: Interval) -> String {
25    let mut buf = String::new();
26    strconv::format_interval(&mut buf, a);
27    buf
28}
29
30#[sqlfunc(
31    sqlname = "interval_to_time",
32    preserves_uniqueness = false,
33    inverse = to_unary!(super::CastTimeToInterval)
34)]
35fn cast_interval_to_time(i: Interval) -> NaiveTime {
36    // Modeled after the PostgreSQL implementation:
37    // https://github.com/postgres/postgres/blob/6a1ea02c491d16474a6214603dce40b5b122d4d1/src/backend/utils/adt/date.c#L2003-L2027
38    let mut result = i.micros % *USECS_PER_DAY;
39    if result < 0 {
40        result += *USECS_PER_DAY;
41    }
42
43    let i = Interval::new(0, 0, result);
44
45    let hours: u32 = i
46        .hours()
47        .try_into()
48        .expect("interval is positive and hours() returns a value in the range [-24, 24]");
49    let minutes: u32 = i
50        .minutes()
51        .try_into()
52        .expect("interval is positive and minutes() returns a value in the range [-60, 60]");
53    let seconds: u32 = i64::cast_lossy(i.seconds::<f64>())
54        .try_into()
55        .expect("interval is positive and seconds() returns a value in the range [-60.0, 60.0]");
56    let nanoseconds: u32 =
57            i.nanoseconds().try_into().expect(
58                "interval is positive and nanoseconds() returns a value in the range [-1_000_000_000, 1_000_000_000]",
59            );
60
61    NaiveTime::from_hms_nano_opt(hours, minutes, seconds, nanoseconds).unwrap()
62}
63
64#[sqlfunc(
65    sqlname = "-",
66    preserves_uniqueness = true,
67    inverse = to_unary!(super::NegInterval)
68)]
69fn neg_interval(i: Interval) -> Result<Interval, EvalError> {
70    i.checked_neg()
71        .ok_or_else(|| EvalError::IntervalOutOfRange(i.to_string().into()))
72}
73
74#[sqlfunc(sqlname = "justify_days")]
75fn justify_days(i: Interval) -> Result<Interval, EvalError> {
76    i.justify_days()
77        .map_err(|_| EvalError::IntervalOutOfRange(i.to_string().into()))
78}
79
80#[sqlfunc(sqlname = "justify_hours")]
81fn justify_hours(i: Interval) -> Result<Interval, EvalError> {
82    i.justify_hours()
83        .map_err(|_| EvalError::IntervalOutOfRange(i.to_string().into()))
84}
85
86#[sqlfunc(sqlname = "justify_interval")]
87fn justify_interval(i: Interval) -> Result<Interval, EvalError> {
88    i.justify_interval()
89        .map_err(|_| EvalError::IntervalOutOfRange(i.to_string().into()))
90}