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