1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
use crate::eval::expr::{BindError, BindEvalExpr, EvalExpr};

use partiql_types::TYPE_DATETIME;
use partiql_value::Value::Missing;
use partiql_value::{DateTime, Value};

use rust_decimal::Decimal;
use std::fmt::Debug;

use crate::eval::eval_expr_wrapper::UnaryValueExpr;
use std::time::Duration;

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub(crate) enum EvalExtractFn {
    /// Represents a year `EXTRACT` function, e.g. `extract(YEAR FROM t)`.
    Year,
    /// Represents a month `EXTRACT` function, e.g. `extract(MONTH FROM t)`.
    Month,
    /// Represents a day `EXTRACT` function, e.g. `extract(DAY FROM t)`.
    Day,
    /// Represents an hour `EXTRACT` function, e.g. `extract(HOUR FROM t)`.
    Hour,
    /// Represents a minute `EXTRACT` function, e.g. `extract(MINUTE FROM t)`.
    Minute,
    /// Represents a second `EXTRACT` function, e.g. `extract(SECOND FROM t)`.
    Second,
    /// Represents a timezone hour `EXTRACT` function, e.g. `extract(TIMEZONE_HOUR FROM t)`.
    TzHour,
    /// Represents a timezone minute `EXTRACT` function, e.g. `extract(TIMEZONE_MINUTE FROM t)`.
    TzMinute,
}

impl BindEvalExpr for EvalExtractFn {
    fn bind<const STRICT: bool>(
        &self,
        args: Vec<Box<dyn EvalExpr>>,
    ) -> Result<Box<dyn EvalExpr>, BindError> {
        #[inline]
        fn total_seconds(second: u8, nanosecond: u32) -> Value {
            const NANOSECOND_SCALE: u32 = 9;
            let total = Duration::new(second as u64, nanosecond).as_nanos() as i128;
            Decimal::from_i128_with_scale(total, NANOSECOND_SCALE).into()
        }

        let create = |f: fn(&DateTime) -> Value| {
            UnaryValueExpr::create_typed::<{ STRICT }, _>([TYPE_DATETIME], args, move |value| {
                match value {
                    Value::DateTime(dt) => f(dt.as_ref()),
                    _ => Missing,
                }
            })
        };

        match self {
            EvalExtractFn::Year => create(|dt: &DateTime| match dt {
                DateTime::Date(d) => Value::from(d.year()),
                DateTime::Timestamp(ts) => Value::from(ts.year()),
                DateTime::TimestampWithTz(ts) => Value::from(ts.year()),
                _ => Missing,
            }),
            EvalExtractFn::Month => create(|dt: &DateTime| match dt {
                DateTime::Date(d) => Value::from(d.month() as u8),
                DateTime::Timestamp(ts) => Value::from(ts.month() as u8),
                DateTime::TimestampWithTz(ts) => Value::from(ts.month() as u8),
                _ => Missing,
            }),
            EvalExtractFn::Day => create(|dt: &DateTime| match dt {
                DateTime::Date(d) => Value::from(d.day()),
                DateTime::Timestamp(ts) => Value::from(ts.day()),
                DateTime::TimestampWithTz(ts) => Value::from(ts.day()),
                _ => Missing,
            }),
            EvalExtractFn::Hour => create(|dt: &DateTime| match dt {
                DateTime::Time(t) => Value::from(t.hour()),
                DateTime::TimeWithTz(t, _) => Value::from(t.hour()),
                DateTime::Timestamp(ts) => Value::from(ts.hour()),
                DateTime::TimestampWithTz(ts) => Value::from(ts.hour()),
                _ => Missing,
            }),
            EvalExtractFn::Minute => create(|dt: &DateTime| match dt {
                DateTime::Time(t) => Value::from(t.minute()),
                DateTime::TimeWithTz(t, _) => Value::from(t.minute()),
                DateTime::Timestamp(ts) => Value::from(ts.minute()),
                DateTime::TimestampWithTz(ts) => Value::from(ts.minute()),
                _ => Missing,
            }),
            EvalExtractFn::Second => create(|dt: &DateTime| match dt {
                DateTime::Time(t) => total_seconds(t.second(), t.nanosecond()),
                DateTime::TimeWithTz(t, _) => total_seconds(t.second(), t.nanosecond()),
                DateTime::Timestamp(ts) => total_seconds(ts.second(), ts.nanosecond()),
                DateTime::TimestampWithTz(ts) => total_seconds(ts.second(), ts.nanosecond()),
                _ => Missing,
            }),
            EvalExtractFn::TzHour => create(|dt: &DateTime| match dt {
                DateTime::TimeWithTz(_, tz) => Value::from(tz.whole_hours()),
                DateTime::TimestampWithTz(ts) => Value::from(ts.offset().whole_hours()),

                _ => Missing,
            }),
            EvalExtractFn::TzMinute => create(|dt: &DateTime| match dt {
                DateTime::TimeWithTz(_, tz) => Value::from(tz.minutes_past_hour()),
                DateTime::TimestampWithTz(ts) => Value::from(ts.offset().minutes_past_hour()),
                _ => Missing,
            }),
        }
    }
}