From fcdc3a112bb64ac151d44cc48f0bb143d92d456d Mon Sep 17 00:00:00 2001 From: Runji Wang Date: Mon, 9 Oct 2023 01:24:48 +0800 Subject: [PATCH] perf(expr): avoid parsing unit every time on `extract` and `date_part` (#12398) Signed-off-by: Runji Wang --- src/expr/impl/src/scalar/extract.rs | 641 ++++++++++++++++++---------- src/expr/macro/src/gen.rs | 1 + 2 files changed, 414 insertions(+), 228 deletions(-) diff --git a/src/expr/impl/src/scalar/extract.rs b/src/expr/impl/src/scalar/extract.rs index 9adc750808acb..d42489d1bf9ef 100644 --- a/src/expr/impl/src/scalar/extract.rs +++ b/src/expr/impl/src/scalar/extract.rs @@ -12,221 +12,395 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::str::FromStr; + use chrono::{Datelike, NaiveTime, Timelike}; use risingwave_common::types::{Date, Decimal, Interval, Time, Timestamp, Timestamptz, F64}; use risingwave_expr::{function, ExprError, Result}; +use self::Unit::*; use crate::scalar::timestamptz::time_zone_err; -fn extract_date(date: impl Datelike, unit: &str) -> Option { - Some(if unit.eq_ignore_ascii_case("millennium") { - ((date.year() - 1) / 1000 + 1).into() - } else if unit.eq_ignore_ascii_case("century") { - ((date.year() - 1) / 100 + 1).into() - } else if unit.eq_ignore_ascii_case("decade") { - (date.year() / 10).into() - } else if unit.eq_ignore_ascii_case("year") { - date.year().into() - } else if unit.eq_ignore_ascii_case("isoyear") { - date.iso_week().year().into() - } else if unit.eq_ignore_ascii_case("quarter") { - ((date.month() - 1) / 3 + 1).into() - } else if unit.eq_ignore_ascii_case("month") { - date.month().into() - } else if unit.eq_ignore_ascii_case("week") { - date.iso_week().week().into() - } else if unit.eq_ignore_ascii_case("day") { - date.day().into() - } else if unit.eq_ignore_ascii_case("doy") { - date.ordinal().into() - } else if unit.eq_ignore_ascii_case("dow") { - date.weekday().num_days_from_sunday().into() - } else if unit.eq_ignore_ascii_case("isodow") { - date.weekday().number_from_monday().into() - } else { - return None; - }) +/// Extract field from `Datelike`. +fn extract_from_datelike(date: impl Datelike, unit: Unit) -> Decimal { + match unit { + Millennium => ((date.year() - 1) / 1000 + 1).into(), + Century => ((date.year() - 1) / 100 + 1).into(), + Decade => (date.year() / 10).into(), + Year => date.year().into(), + IsoYear => date.iso_week().year().into(), + Quarter => ((date.month() - 1) / 3 + 1).into(), + Month => date.month().into(), + Week => date.iso_week().week().into(), + Day => date.day().into(), + Doy => date.ordinal().into(), + Dow => date.weekday().num_days_from_sunday().into(), + IsoDow => date.weekday().number_from_monday().into(), + u => unreachable!("invalid unit {:?} for date", u), + } } -fn extract_time(time: impl Timelike, unit: &str) -> Option { +/// Extract field from `Timelike`. +fn extract_from_timelike(time: impl Timelike, unit: Unit) -> Decimal { let usecs = || time.second() as u64 * 1_000_000 + (time.nanosecond() / 1000) as u64; - Some(if unit.eq_ignore_ascii_case("hour") { - time.hour().into() - } else if unit.eq_ignore_ascii_case("minute") { - time.minute().into() - } else if unit.eq_ignore_ascii_case("second") { - Decimal::from_i128_with_scale(usecs() as i128, 6) - } else if unit.eq_ignore_ascii_case("millisecond") { - Decimal::from_i128_with_scale(usecs() as i128, 3) - } else if unit.eq_ignore_ascii_case("microsecond") { - usecs().into() - } else if unit.eq_ignore_ascii_case("epoch") { - let usecs = - time.num_seconds_from_midnight() as u64 * 1_000_000 + (time.nanosecond() / 1000) as u64; - Decimal::from_i128_with_scale(usecs as i128, 6) - } else { - return None; - }) + match unit { + Hour => time.hour().into(), + Minute => time.minute().into(), + Second => Decimal::from_i128_with_scale(usecs() as i128, 6), + Millisecond => Decimal::from_i128_with_scale(usecs() as i128, 3), + Microsecond => usecs().into(), + Epoch => { + let usecs = time.num_seconds_from_midnight() as u64 * 1_000_000 + + (time.nanosecond() / 1000) as u64; + Decimal::from_i128_with_scale(usecs as i128, 6) + } + u => unreachable!("invalid unit {:?} for time", u), + } } -#[function("extract(varchar, date) -> decimal")] -pub fn extract_from_date(unit: &str, date: Date) -> Result { - if unit.eq_ignore_ascii_case("epoch") { - let epoch = date.0.and_time(NaiveTime::default()).timestamp(); - return Ok(epoch.into()); - } else if unit.eq_ignore_ascii_case("julian") { - const UNIX_EPOCH_DAY: i32 = 719_163; - let julian = date.0.num_days_from_ce() - UNIX_EPOCH_DAY + 2_440_588; - return Ok(julian.into()); - }; - extract_date(date.0, unit).ok_or_else(|| invalid_unit("date unit", unit)) +#[function( + "extract(varchar, date) -> decimal", + prebuild = "Unit::from_str($0)?.ensure_date()?" +)] +fn extract_from_date(date: Date, unit: &Unit) -> Decimal { + match unit { + Epoch => { + let epoch = date.0.and_time(NaiveTime::default()).timestamp(); + epoch.into() + } + Julian => { + const UNIX_EPOCH_DAY: i32 = 719_163; + let julian = date.0.num_days_from_ce() - UNIX_EPOCH_DAY + 2_440_588; + julian.into() + } + _ => extract_from_datelike(date.0, *unit), + } } -#[function("extract(varchar, time) -> decimal")] -pub fn extract_from_time(unit: &str, time: Time) -> Result { - extract_time(time.0, unit).ok_or_else(|| invalid_unit("time unit", unit)) +#[function( + "extract(varchar, time) -> decimal", + prebuild = "Unit::from_str($0)?.ensure_time()?" +)] +fn extract_from_time(time: Time, unit: &Unit) -> Decimal { + extract_from_timelike(time.0, *unit) } -#[function("extract(varchar, timestamp) -> decimal")] -pub fn extract_from_timestamp(unit: &str, timestamp: Timestamp) -> Result { - if unit.eq_ignore_ascii_case("epoch") { - let epoch = Decimal::from_i128_with_scale(timestamp.0.timestamp_micros() as i128, 6); - return Ok(epoch); - } else if unit.eq_ignore_ascii_case("julian") { - let epoch = Decimal::from_i128_with_scale(timestamp.0.timestamp_micros() as i128, 6); - return Ok(epoch / (24 * 60 * 60).into() + 2_440_588.into()); - }; - extract_date(timestamp.0, unit) - .or_else(|| extract_time(timestamp.0, unit)) - .ok_or_else(|| invalid_unit("timestamp unit", unit)) +#[function( + "extract(varchar, timestamp) -> decimal", + prebuild = "Unit::from_str($0)?.ensure_timestamp()?" +)] +fn extract_from_timestamp(timestamp: Timestamp, unit: &Unit) -> Decimal { + match unit { + Epoch => { + let epoch = timestamp.0.timestamp_micros(); + Decimal::from_i128_with_scale(epoch as i128, 6) + } + Julian => { + let epoch = Decimal::from_i128_with_scale(timestamp.0.timestamp_micros() as i128, 6); + epoch / (24 * 60 * 60).into() + 2_440_588.into() + } + _ if unit.is_date_unit() => extract_from_datelike(timestamp.0.date(), *unit), + _ if unit.is_time_unit() => extract_from_timelike(timestamp.0.time(), *unit), + u => unreachable!("invalid unit {:?} for timestamp", u), + } } -#[function("extract(varchar, timestamptz) -> decimal")] -pub fn extract_from_timestamptz(unit: &str, tz: Timestamptz) -> Result { - if unit.eq_ignore_ascii_case("epoch") { - Ok(Decimal::from_i128_with_scale(tz.timestamp_micros() as _, 6)) - } else { +#[function( + "extract(varchar, timestamptz) -> decimal", + prebuild = "Unit::from_str($0)?.ensure_timestamptz()?" +)] +fn extract_from_timestamptz(tz: Timestamptz, unit: &Unit) -> Decimal { + match unit { + Epoch => Decimal::from_i128_with_scale(tz.timestamp_micros() as _, 6), // TODO(#5826): all other units depend on implicit session TimeZone - Err(invalid_unit("timestamp with time zone units", unit)) + u => unreachable!("invalid unit {u:?} for timestamp with time zone"), } } -#[function("extract(varchar, timestamptz, varchar) -> decimal")] -pub fn extract_from_timestamptz_at_timezone( - unit: &str, +#[function( + "extract(varchar, timestamptz, varchar) -> decimal", + prebuild = "Unit::from_str($0)?.ensure_timestamptz_at_timezone()?" +)] +fn extract_from_timestamptz_at_timezone( input: Timestamptz, timezone: &str, + unit: &Unit, ) -> Result { use chrono::Offset as _; let time_zone = Timestamptz::lookup_time_zone(timezone).map_err(time_zone_err)?; let instant_local = input.to_datetime_in_zone(time_zone); - if unit.eq_ignore_ascii_case("epoch") { - Ok(Decimal::from_i128_with_scale( - input.timestamp_micros() as _, - 6, - )) - } else if unit.eq_ignore_ascii_case("timezone") { - let east_secs = instant_local.offset().fix().local_minus_utc(); - Ok(east_secs.into()) - } else if unit.eq_ignore_ascii_case("timezone_hour") { - let east_secs = instant_local.offset().fix().local_minus_utc(); - Ok((east_secs / 3600).into()) - } else if unit.eq_ignore_ascii_case("timezone_minute") { - let east_secs = instant_local.offset().fix().local_minus_utc(); - Ok((east_secs % 3600 / 60).into()) - } else { - let timestamp = instant_local.naive_local(); - extract_from_timestamp(unit, timestamp.into()) - } + Ok(match unit { + Epoch => Decimal::from_i128_with_scale(instant_local.timestamp_micros() as _, 6), + Timezone => { + let east_secs = instant_local.offset().fix().local_minus_utc(); + east_secs.into() + } + Timezone_Hour => { + let east_secs = instant_local.offset().fix().local_minus_utc(); + (east_secs / 3600).into() + } + Timezone_Minute => { + let east_secs = instant_local.offset().fix().local_minus_utc(); + (east_secs % 3600 / 60).into() + } + _ => extract_from_timestamp(instant_local.naive_local().into(), unit), + }) } -#[function("extract(varchar, interval) -> decimal")] -pub fn extract_from_interval(unit: &str, interval: Interval) -> Result { - Ok(if unit.eq_ignore_ascii_case("millennium") { - (interval.years_field() / 1000).into() - } else if unit.eq_ignore_ascii_case("century") { - (interval.years_field() / 100).into() - } else if unit.eq_ignore_ascii_case("decade") { - (interval.years_field() / 10).into() - } else if unit.eq_ignore_ascii_case("year") { - interval.years_field().into() - } else if unit.eq_ignore_ascii_case("quarter") { - (interval.months_field() / 3 + 1).into() - } else if unit.eq_ignore_ascii_case("month") { - interval.months_field().into() - } else if unit.eq_ignore_ascii_case("day") { - interval.days_field().into() - } else if unit.eq_ignore_ascii_case("hour") { - interval.hours_field().into() - } else if unit.eq_ignore_ascii_case("minute") { - interval.minutes_field().into() - } else if unit.eq_ignore_ascii_case("second") { - Decimal::from_i128_with_scale(interval.seconds_in_micros() as i128, 6) - } else if unit.eq_ignore_ascii_case("millisecond") { - Decimal::from_i128_with_scale(interval.seconds_in_micros() as i128, 3) - } else if unit.eq_ignore_ascii_case("microsecond") { - interval.seconds_in_micros().into() - } else if unit.eq_ignore_ascii_case("epoch") { - Decimal::from_i128_with_scale(interval.epoch_in_micros(), 6) - } else { - return Err(invalid_unit("interval unit", unit)); - }) +#[function( + "extract(varchar, interval) -> decimal", + prebuild = "Unit::from_str($0)?.ensure_interval()?" +)] +fn extract_from_interval(interval: Interval, unit: &Unit) -> Decimal { + match unit { + Millennium => (interval.years_field() / 1000).into(), + Century => (interval.years_field() / 100).into(), + Decade => (interval.years_field() / 10).into(), + Year => interval.years_field().into(), + Quarter => (interval.months_field() / 3 + 1).into(), + Month => interval.months_field().into(), + Day => interval.days_field().into(), + Hour => interval.hours_field().into(), + Minute => interval.minutes_field().into(), + Second => Decimal::from_i128_with_scale(interval.seconds_in_micros() as i128, 6), + Millisecond => Decimal::from_i128_with_scale(interval.seconds_in_micros() as i128, 3), + Microsecond => interval.seconds_in_micros().into(), + Epoch => Decimal::from_i128_with_scale(interval.epoch_in_micros(), 6), + u => unreachable!("invalid unit {:?} for interval", u), + } } -#[function("date_part(varchar, date) -> float8")] -pub fn date_part_from_date(unit: &str, date: Date) -> Result { +#[function( + "date_part(varchar, date) -> float8", + prebuild = "Unit::from_str($0)?.ensure_date()?" +)] +fn date_part_from_date(date: Date, unit: &Unit) -> Result { // date_part of date manually cast to timestamp // https://github.com/postgres/postgres/blob/REL_15_2/src/backend/catalog/system_functions.sql#L123 - extract_from_timestamp(unit, date.into())? + extract_from_timestamp(date.into(), unit) .try_into() .map_err(|_| ExprError::NumericOutOfRange) } -#[function("date_part(varchar, time) -> float8")] -pub fn date_part_from_time(unit: &str, time: Time) -> Result { - extract_from_time(unit, time)? +#[function( + "date_part(varchar, time) -> float8", + prebuild = "Unit::from_str($0)?.ensure_time()?" +)] +fn date_part_from_time(time: Time, unit: &Unit) -> Result { + extract_from_time(time, unit) .try_into() .map_err(|_| ExprError::NumericOutOfRange) } -#[function("date_part(varchar, timestamptz) -> float8")] -pub fn date_part_from_timestamptz(unit: &str, input: Timestamptz) -> Result { - extract_from_timestamptz(unit, input)? +#[function( + "date_part(varchar, timestamp) -> float8", + prebuild = "Unit::from_str($0)?.ensure_timestamp()?" +)] +fn date_part_from_timestamp(timestamp: Timestamp, unit: &Unit) -> Result { + extract_from_timestamp(timestamp, unit) .try_into() .map_err(|_| ExprError::NumericOutOfRange) } -#[function("date_part(varchar, timestamptz, varchar) -> float8")] -pub fn date_part_from_timestamptz_at_timezone( - unit: &str, +#[function( + "date_part(varchar, timestamptz) -> float8", + prebuild = "Unit::from_str($0)?.ensure_timestamptz()?" +)] +fn date_part_from_timestamptz(input: Timestamptz, unit: &Unit) -> Result { + extract_from_timestamptz(input, unit) + .try_into() + .map_err(|_| ExprError::NumericOutOfRange) +} + +#[function( + "date_part(varchar, timestamptz, varchar) -> float8", + prebuild = "Unit::from_str($0)?.ensure_timestamptz_at_timezone()?" +)] +fn date_part_from_timestamptz_at_timezone( input: Timestamptz, timezone: &str, + unit: &Unit, ) -> Result { - extract_from_timestamptz_at_timezone(unit, input, timezone)? + extract_from_timestamptz_at_timezone(input, timezone, unit)? .try_into() .map_err(|_| ExprError::NumericOutOfRange) } -#[function("date_part(varchar, timestamp) -> float8")] -pub fn date_part_from_timestamp(unit: &str, timestamp: Timestamp) -> Result { - extract_from_timestamp(unit, timestamp)? +#[function( + "date_part(varchar, interval) -> float8", + prebuild = "Unit::from_str($0)?.ensure_interval()?" +)] +fn date_part_from_interval(interval: Interval, unit: &Unit) -> Result { + extract_from_interval(interval, unit) .try_into() .map_err(|_| ExprError::NumericOutOfRange) } -#[function("date_part(varchar, interval) -> float8")] -pub fn date_part_from_interval(unit: &str, interval: Interval) -> Result { - extract_from_interval(unit, interval)? - .try_into() - .map_err(|_| ExprError::NumericOutOfRange) +/// Define an enum and its `FromStr` impl. +macro_rules! define_unit { + ($(#[ $attr:meta ])* enum $name:ident { $($variant:ident,)* }) => { + $(#[$attr])* + #[derive(Debug, PartialEq, Eq, Clone, Copy)] + enum $name { + $($variant,)* + } + + impl FromStr for $name { + type Err = ExprError; + + fn from_str(s: &str) -> Result { + $( + if s.eq_ignore_ascii_case(stringify!($variant)) { + return Ok(Self::$variant); + } + )* + Err(invalid_unit(s)) + } + } + }; } -fn invalid_unit(name: &'static str, unit: &str) -> ExprError { +define_unit! { + /// Datetime units. + #[allow(non_camel_case_types)] + enum Unit { + Millennium, + Century, + Decade, + Year, + IsoYear, + Quarter, + Month, + Week, + Day, + Doy, + Dow, + IsoDow, + Hour, + Minute, + Second, + Millisecond, + Microsecond, + Epoch, + Julian, + Timezone, + Timezone_Hour, + Timezone_Minute, + } +} + +impl Unit { + /// Whether the unit is a valid date unit. + #[rustfmt::skip] + const fn is_date_unit(self) -> bool { + matches!( + self, + Millennium | Century | Decade | Year | IsoYear | Quarter | Month | Week + | Day | Doy | Dow | IsoDow | Epoch | Julian + ) + } + + /// Whether the unit is a valid time unit. + const fn is_time_unit(self) -> bool { + matches!( + self, + Hour | Minute | Second | Millisecond | Microsecond | Epoch + ) + } + + /// Whether the unit is a valid timestamp unit. + const fn is_timestamp_unit(self) -> bool { + self.is_date_unit() || self.is_time_unit() + } + + /// Whether the unit is a valid timestamptz unit. + const fn is_timestamptz_unit(self) -> bool { + matches!(self, Epoch) + } + + /// Whether the unit is a valid timestamptz at timezone unit. + const fn is_timestamptz_at_timezone_unit(self) -> bool { + self.is_timestamp_unit() || matches!(self, Timezone | Timezone_Hour | Timezone_Minute) + } + + /// Whether the unit is a valid interval unit. + #[rustfmt::skip] + const fn is_interval_unit(self) -> bool { + matches!( + self, + Millennium | Century | Decade | Year | Quarter | Month | Day | Hour | Minute + | Second | Millisecond | Microsecond | Epoch + ) + } + + /// Ensure the unit is a valid date unit. + fn ensure_date(self) -> Result { + if self.is_date_unit() { + Ok(self) + } else { + Err(unsupported_unit(self, "date")) + } + } + + /// Ensure the unit is a valid time unit. + fn ensure_time(self) -> Result { + if self.is_time_unit() { + Ok(self) + } else { + Err(unsupported_unit(self, "time")) + } + } + + /// Ensure the unit is a valid timestamp unit. + fn ensure_timestamp(self) -> Result { + if self.is_timestamp_unit() { + Ok(self) + } else { + Err(unsupported_unit(self, "timestamp")) + } + } + + /// Ensure the unit is a valid timestamptz unit. + fn ensure_timestamptz(self) -> Result { + if self.is_timestamptz_unit() { + Ok(self) + } else { + Err(unsupported_unit(self, "timestamp with time zone")) + } + } + + /// Ensure the unit is a valid timestamptz unit. + fn ensure_timestamptz_at_timezone(self) -> Result { + if self.is_timestamptz_at_timezone_unit() { + Ok(self) + } else { + Err(unsupported_unit(self, "timestamp with time zone")) + } + } + + /// Ensure the unit is a valid interval unit. + fn ensure_interval(self) -> Result { + if self.is_interval_unit() { + Ok(self) + } else { + Err(unsupported_unit(self, "interval")) + } + } +} + +fn invalid_unit(unit: &str) -> ExprError { + ExprError::InvalidParam { + name: "unit", + reason: format!("unit \"{unit}\" not recognized").into(), + } +} + +fn unsupported_unit(unit: Unit, type_: &str) -> ExprError { ExprError::InvalidParam { - name, - reason: format!("\"{unit}\" not recognized or supported").into(), + name: "unit", + reason: format!("unit \"{unit:?}\" not supported for type {type_}").into(), } } @@ -237,99 +411,110 @@ mod tests { use super::*; #[test] - fn test_date() { + fn test_extract_from_date() { let date = Date::new(NaiveDate::parse_from_str("2021-11-22", "%Y-%m-%d").unwrap()); - assert_eq!(extract_from_date("DAY", date).unwrap(), 22.into()); - assert_eq!(extract_from_date("MONTH", date).unwrap(), 11.into()); - assert_eq!(extract_from_date("YEAR", date).unwrap(), 2021.into()); - assert_eq!(extract_from_date("DOW", date).unwrap(), 1.into()); - assert_eq!(extract_from_date("DOY", date).unwrap(), 326.into()); - assert_eq!(extract_from_date("MILLENNIUM", date).unwrap(), 3.into()); - assert_eq!(extract_from_date("CENTURY", date).unwrap(), 21.into()); - assert_eq!(extract_from_date("DECADE", date).unwrap(), 202.into()); - assert_eq!(extract_from_date("ISOYEAR", date).unwrap(), 2021.into()); - assert_eq!(extract_from_date("QUARTER", date).unwrap(), 4.into()); - assert_eq!(extract_from_date("WEEK", date).unwrap(), 47.into()); - assert_eq!(extract_from_date("ISODOW", date).unwrap(), 1.into()); - assert_eq!(extract_from_date("EPOCH", date).unwrap(), 1637539200.into()); - assert_eq!(extract_from_date("JULIAN", date).unwrap(), 2_459_541.into()); + let extract = |i| extract_from_date(date, &i).to_string(); + assert_eq!(extract(Day), "22"); + assert_eq!(extract(Month), "11"); + assert_eq!(extract(Year), "2021"); + assert_eq!(extract(Dow), "1"); + assert_eq!(extract(Doy), "326"); + assert_eq!(extract(Millennium), "3"); + assert_eq!(extract(Century), "21"); + assert_eq!(extract(Decade), "202"); + assert_eq!(extract(IsoYear), "2021"); + assert_eq!(extract(Quarter), "4"); + assert_eq!(extract(Week), "47"); + assert_eq!(extract(IsoDow), "1"); + assert_eq!(extract(Epoch), "1637539200"); + assert_eq!(extract(Julian), "2459541"); } #[test] - fn test_timestamp() { + fn test_extract_from_time() { + let time: Time = "23:22:57.123450".parse().unwrap(); + let extract = |unit| extract_from_time(time, &unit).to_string(); + assert_eq!(extract(Hour), "23"); + assert_eq!(extract(Minute), "22"); + assert_eq!(extract(Second), "57.123450"); + assert_eq!(extract(Millisecond), "57123.450"); + assert_eq!(extract(Microsecond), "57123450"); + assert_eq!(extract(Epoch), "84177.123450"); + } + + #[test] + fn test_extract_from_timestamp() { let ts = Timestamp::new( NaiveDateTime::parse_from_str("2021-11-22 12:4:2.575400", "%Y-%m-%d %H:%M:%S%.f") .unwrap(), ); - let extract = |f, i| extract_from_timestamp(f, i).unwrap().to_string(); - assert_eq!(extract("MILLENNIUM", ts), "3"); - assert_eq!(extract("CENTURY", ts), "21"); - assert_eq!(extract("DECADE", ts), "202"); - assert_eq!(extract("ISOYEAR", ts), "2021"); - assert_eq!(extract("YEAR", ts), "2021"); - assert_eq!(extract("QUARTER", ts), "4"); - assert_eq!(extract("MONTH", ts), "11"); - assert_eq!(extract("WEEK", ts), "47"); - assert_eq!(extract("DAY", ts), "22"); - assert_eq!(extract("DOW", ts), "1"); - assert_eq!(extract("ISODOW", ts), "1"); - assert_eq!(extract("DOY", ts), "326"); - assert_eq!(extract("HOUR", ts), "12"); - assert_eq!(extract("MINUTE", ts), "4"); - assert_eq!(extract("SECOND", ts), "2.575400"); - assert_eq!(extract("MILLISECOND", ts), "2575.400"); - assert_eq!(extract("MICROSECOND", ts), "2575400"); - assert_eq!(extract("EPOCH", ts), "1637582642.575400"); - assert_eq!(extract("JULIAN", ts), "2459541.5028075856481481481481"); + let extract = |unit| extract_from_timestamp(ts, &unit).to_string(); + assert_eq!(extract(Millennium), "3"); + assert_eq!(extract(Century), "21"); + assert_eq!(extract(Decade), "202"); + assert_eq!(extract(IsoYear), "2021"); + assert_eq!(extract(Year), "2021"); + assert_eq!(extract(Quarter), "4"); + assert_eq!(extract(Month), "11"); + assert_eq!(extract(Week), "47"); + assert_eq!(extract(Day), "22"); + assert_eq!(extract(Dow), "1"); + assert_eq!(extract(IsoDow), "1"); + assert_eq!(extract(Doy), "326"); + assert_eq!(extract(Hour), "12"); + assert_eq!(extract(Minute), "4"); + assert_eq!(extract(Second), "2.575400"); + assert_eq!(extract(Millisecond), "2575.400"); + assert_eq!(extract(Microsecond), "2575400"); + assert_eq!(extract(Epoch), "1637582642.575400"); + assert_eq!(extract(Julian), "2459541.5028075856481481481481"); } #[test] - fn test_extract_from_time() { - let time: Time = "23:22:57.123450".parse().unwrap(); - let extract = |f, i| extract_from_time(f, i).unwrap().to_string(); - assert_eq!(extract("Hour", time), "23"); - assert_eq!(extract("Minute", time), "22"); - assert_eq!(extract("Second", time), "57.123450"); - assert_eq!(extract("Millisecond", time), "57123.450"); - assert_eq!(extract("Microsecond", time), "57123450"); - assert_eq!(extract("Epoch", time), "84177.123450"); + fn test_extract_from_timestamptz() { + let ts: Timestamptz = "2023-06-01 00:00:00Z".parse().unwrap(); + let extract = |unit| { + extract_from_timestamptz_at_timezone(ts, "pst8pdt", &unit) + .unwrap() + .to_string() + }; + assert_eq!(extract(Timezone), "-25200"); + assert_eq!(extract(Timezone_Hour), "-7"); + assert_eq!(extract(Timezone_Minute), "0"); } #[test] fn test_extract_from_interval() { let interval: Interval = "2345 years 1 mon 250 days 23:22:57.123450".parse().unwrap(); - let extract = |f, i| extract_from_interval(f, i).unwrap().to_string(); - assert_eq!(extract("Millennium", interval), "2"); - assert_eq!(extract("Century", interval), "23"); - assert_eq!(extract("Decade", interval), "234"); - assert_eq!(extract("Year", interval), "2345"); - assert_eq!(extract("Month", interval), "1"); - assert_eq!(extract("Day", interval), "250"); - assert_eq!(extract("Hour", interval), "23"); - assert_eq!(extract("Minute", interval), "22"); - assert_eq!(extract("Second", interval), "57.123450"); - assert_eq!(extract("Millisecond", interval), "57123.450"); - assert_eq!(extract("Microsecond", interval), "57123450"); - assert_eq!(extract("Epoch", interval), "74026848177.123450"); - assert!(extract_from_interval("Nanosecond", interval).is_err()); - assert!(extract_from_interval("Week", interval).is_err()); + let extract = |unit| extract_from_interval(interval, &unit).to_string(); + assert_eq!(extract(Millennium), "2"); + assert_eq!(extract(Century), "23"); + assert_eq!(extract(Decade), "234"); + assert_eq!(extract(Year), "2345"); + assert_eq!(extract(Month), "1"); + assert_eq!(extract(Day), "250"); + assert_eq!(extract(Hour), "23"); + assert_eq!(extract(Minute), "22"); + assert_eq!(extract(Second), "57.123450"); + assert_eq!(extract(Millisecond), "57123.450"); + assert_eq!(extract(Microsecond), "57123450"); + assert_eq!(extract(Epoch), "74026848177.123450"); let interval: Interval = "-2345 years -1 mon -250 days -23:22:57.123450" .parse() .unwrap(); - assert_eq!(extract("Millennium", interval), "-2"); - assert_eq!(extract("Century", interval), "-23"); - assert_eq!(extract("Decade", interval), "-234"); - assert_eq!(extract("Year", interval), "-2345"); - assert_eq!(extract("Month", interval), "-1"); - assert_eq!(extract("Day", interval), "-250"); - assert_eq!(extract("Hour", interval), "-23"); - assert_eq!(extract("Minute", interval), "-22"); - assert_eq!(extract("Second", interval), "-57.123450"); - assert_eq!(extract("Millisecond", interval), "-57123.450"); - assert_eq!(extract("Microsecond", interval), "-57123450"); - assert_eq!(extract("Epoch", interval), "-74026848177.123450"); - assert!(extract_from_interval("Nanosecond", interval).is_err()); - assert!(extract_from_interval("Week", interval).is_err()); + let extract = |unit| extract_from_interval(interval, &unit).to_string(); + assert_eq!(extract(Millennium), "-2"); + assert_eq!(extract(Century), "-23"); + assert_eq!(extract(Decade), "-234"); + assert_eq!(extract(Year), "-2345"); + assert_eq!(extract(Month), "-1"); + assert_eq!(extract(Day), "-250"); + assert_eq!(extract(Hour), "-23"); + assert_eq!(extract(Minute), "-22"); + assert_eq!(extract(Second), "-57.123450"); + assert_eq!(extract(Millisecond), "-57123.450"); + assert_eq!(extract(Microsecond), "-57123450"); + assert_eq!(extract(Epoch), "-74026848177.123450"); } } diff --git a/src/expr/macro/src/gen.rs b/src/expr/macro/src/gen.rs index 0404b8f8b8768..50c276e6685c8 100644 --- a/src/expr/macro/src/gen.rs +++ b/src/expr/macro/src/gen.rs @@ -338,6 +338,7 @@ impl FunctionAttr { } else if (types::is_primitive(&self.ret) || self.ret == "boolean") && user_fn.is_pure() && !variadic + && self.prebuild.is_none() { // SIMD optimization for primitive types match self.args.len() {