diff --git a/doc/user/content/release-notes.md b/doc/user/content/release-notes.md index a670d2884051b..bec4dfb499c2e 100644 --- a/doc/user/content/release-notes.md +++ b/doc/user/content/release-notes.md @@ -120,6 +120,20 @@ changes that have not yet been documented. - Add the `array_cat` function. +- **Breaking change.** Return an error when [`extract`](/sql/functions/extract/) + is called with a [`date`] value but a time-related field (e.g., `SECOND`). + + Previous versions of Materialize would incorrectly return `0` in these cases. + The new behavior matches PostgreSQL. + + [`date_part`](/sql/functions/date-part/) still returns a `0` in these cases, + which matches the PostgreSQL behavior. + +- **Breaking change.** Change the return type of [`extract`](/sql/functions/extract/) + from [`float`](/sql/types/float/) to [`numeric`](/sql/types/numeric/). + + This new behavior matches PostgreSQL v14. + {{< comment >}} Only add new release notes above this line. diff --git a/doc/user/content/sql/functions/date-part.md b/doc/user/content/sql/functions/date-part.md new file mode 100644 index 0000000000000..aabc2d29c762e --- /dev/null +++ b/doc/user/content/sql/functions/date-part.md @@ -0,0 +1,72 @@ +--- +title: "date_part Function" +description: "Returns a specified time component from a time-based value" +menu: + main: + parent: 'sql-functions' +--- + +`date_part` returns some time component from a time-based value, such as the year from a Timestamp. +It is mostly functionally equivalent to the function [`EXTRACT`](../extract), except to maintain +PostgreSQL compatibility, `date_part` returns values of type [`float`](../../types/float). This can +result in a loss of precision in certain uses. Using [`EXTRACT`](../extract) is recommended instead. + +## Signatures + +{{< diagram "func-date-part.svg" >}} + +Parameter | Type | Description +----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|------------ +_val_ | [`time`](../../types/time), [`timestamp`](../../types/timestamp), [`timestamp with time zone`](../../types/timestamptz), [`interval`](../../types/interval), [`date`](../../types/date) | The value from which you want to extract a component. vals of type [`date`](../../types/date) are first cast to type [`timestamp`](../../types/timestamp). + +### Arguments + +`date_part` supports multiple synonyms for most time periods. + +Time period | Synonyms +------------|--------- +epoch | `EPOCH` +millennium | `MIL`, `MILLENNIUM`, `MILLENNIA` +century | `C`, `CENT`, `CENTURY`, `CENTURIES` +decade | `DEC`, `DECS`, `DECADE`, `DECADES` +year | `Y`, `YEAR`, `YEARS`, `YR`, `YRS` +quarter | `QTR`, `QUARTER` +month | `MON`, `MONS`, `MONTH`, `MONTHS` +week | `W`, `WEEK`, `WEEKS` +day | `D`, `DAY`, `DAYS` +hour |`H`, `HR`, `HRS`, `HOUR`, `HOURS` +minute | `M`, `MIN`, `MINS`, `MINUTE`, `MINUTES` +second | `S`, `SEC`, `SECS`, `SECOND`, `SECONDS` +microsecond | `US`, `USEC`, `USECS`, `USECONDS`, `MICROSECOND`, `MICROSECONDS` +millisecond | `MS`, `MSEC`, `MSECS`, `MSECONDS`, `MILLISECOND`, `MILLISECONDS` +day of week |`DOW` +ISO day of week | `ISODOW` +day of year | `DOY` + +### Return value + +`date_part` returns a [`float`](../../types/float) value. + +## Examples + +### Extract second from timestamptz + +```sql +SELECT date_part('S', TIMESTAMP '2006-01-02 15:04:05.06'); +``` +```nofmt + date_part +----------- + 5.06 +``` + +### Extract century from date + +```sql +SELECT date_part('CENTURIES', DATE '2006-01-02'); +``` +```nofmt + date_part +----------- + 21 +``` diff --git a/doc/user/content/sql/functions/extract.md b/doc/user/content/sql/functions/extract.md index 1699eae17d84e..6a079b7710525 100644 --- a/doc/user/content/sql/functions/extract.md +++ b/doc/user/content/sql/functions/extract.md @@ -12,9 +12,9 @@ menu: {{< diagram "func-extract.svg" >}} -Parameter | Type | Description -----------|-----------------------------------------------------------------------------------------------------------------------------------------------------|------------ -_val_ | [`date`](../../types/date), [`time`](../../types/time), [`timestamp`](../../types/timestamp), [`timestamp with time zone`](../../types/timestamptz) | The value from which you want to extract a component. +Parameter | Type | Description +----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------ +_val_ | [`date`](../../types/date), [`time`](../../types/time), [`timestamp`](../../types/timestamp), [`timestamp with time zone`](../../types/timestamptz), [`interval`](../../types/interval) | The value from which you want to extract a component. ### Arguments @@ -42,30 +42,28 @@ decade | `DEC`, `DECS`, `DECADE`, `DECADES` ### Return value -`EXTRACT` returns a [`float`](../../types/float) value. +`EXTRACT` returns a [`numeric`](../../types/numeric) value. ## Examples ### Extract second from timestamptz ```sql -SELECT EXTRACT(S FROM TIMESTAMP '2006-01-02 15:04:05.06') -AS sec_extr; +SELECT EXTRACT(S FROM TIMESTAMP '2006-01-02 15:04:05.06'); ``` ```nofmt - sec_extr ----------- - 5.06 + extract +--------- + 5.06 ``` ### Extract century from date ```sql -SELECT EXTRACT(CENTURIES FROM DATE '2006-01-02') -AS sec_extr; +SELECT EXTRACT(CENTURIES FROM DATE '2006-01-02'); ``` ```nofmt - sec_extr ----------- + extract +--------- 21 ``` diff --git a/doc/user/data/sql_funcs.yml b/doc/user/data/sql_funcs.yml index e1ae98aaeaa72..b4297987b7e4e 100644 --- a/doc/user/data/sql_funcs.yml +++ b/doc/user/data/sql_funcs.yml @@ -403,10 +403,14 @@ description: Largest `time_component` <= `val` url: date-trunc - - signature: EXTRACT(extract_expr) -> float + - signature: EXTRACT(extract_expr) -> numeric description: Specified time component from value url: extract + - signature: 'date_part(time_component: str, val: timestamp) -> float' + description: Specified time component from value + url: date-part + - signature: mz_logical_timestamp() -> numeric description: 'The logical time at which a query executes. Used for temporal filters and internal debugging.' url: now_and_mz_logical_timestamp diff --git a/doc/user/layouts/partials/sql-grammar/func-date-part.svg b/doc/user/layouts/partials/sql-grammar/func-date-part.svg new file mode 100644 index 0000000000000..6f6366d46f681 --- /dev/null +++ b/doc/user/layouts/partials/sql-grammar/func-date-part.svg @@ -0,0 +1,195 @@ + + + + + + date_part + + + ( + + + ' + + + epoch + + + millennium + + + century + + + decade + + + year + + + quarter + + + month + + + week + + + dat + + + hour + + + minute + + + second + + + microsecond + + + millisecond + + + dow + + + isodow + + + doy + + + ' + + + , + + + val + + + ) + + + + diff --git a/doc/user/sql-grammar/sql-grammar.bnf b/doc/user/sql-grammar/sql-grammar.bnf index a05de602f5d60..01dea1c331936 100644 --- a/doc/user/sql-grammar/sql-grammar.bnf +++ b/doc/user/sql-grammar/sql-grammar.bnf @@ -346,6 +346,8 @@ func_date_trunc ::= 'date_trunc' '(' "'" ( 'microseconds' | 'milliseconds' | 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year' | 'decade' | 'century' | 'millenium' ) "'" ',' ts_val ')' func_extract ::= 'EXTRACT' '(' ( 'EPOCH' | 'MILLENNIUM' | 'CENTURY' | 'DECADE' | 'YEAR' | 'QUARTER' | 'MONTH' | 'WEEK' | 'DAY' | 'HOUR' | 'MINUTE' | 'SECOND' | 'MICROSECOND' | 'MILLISECOND' | 'DOW' | 'ISODOW' | 'DOY' ) 'FROM' val ')' +func_date_part ::= + 'date_part' '(' "'" ( 'epoch' | 'millennium' | 'century' | 'decade' | 'year' | 'quarter' | 'month' | 'week' | 'dat' | 'hour' | 'minute' | 'second' | 'microsecond' | 'millisecond' | 'dow' | 'isodow' | 'doy' ) "'" ',' val ')' func_length ::= 'length' '(' str (',' encoding_name)? ')' func_substring ::= diff --git a/src/expr/src/scalar/func.rs b/src/expr/src/scalar/func.rs index fc2c93246aea6..7cfe2a5399311 100644 --- a/src/expr/src/scalar/func.rs +++ b/src/expr/src/scalar/func.rs @@ -36,7 +36,7 @@ use repr::adt::array::ArrayDimension; use repr::adt::datetime::{DateTimeUnits, Timezone}; use repr::adt::interval::Interval; use repr::adt::jsonb::JsonbRef; -use repr::adt::numeric::{self, Numeric}; +use repr::adt::numeric::{self, DecimalLike, Numeric}; use repr::adt::regex::Regex; use repr::{strconv, ColumnName, ColumnType, Datum, DatumType, Row, RowArena, ScalarType}; @@ -1363,49 +1363,100 @@ fn ascii<'a>(a: Datum<'a>) -> Datum<'a> { } } -/// A timestamp with only a time component. +/// Common set of methods for time component. pub trait TimeLike: chrono::Timelike { - fn extract_hour(&self) -> f64 { - f64::from(self.hour()) + fn extract_epoch(&self) -> T + where + T: DecimalLike, + { + T::from(self.hour() * 60 * 60 + self.minute() * 60) + self.extract_second::() + } + + fn extract_second(&self) -> T + where + T: DecimalLike, + { + let s = T::from(self.second()); + let ns = T::from(self.nanosecond()) / T::from(1e9); + s + ns } - fn extract_minute(&self) -> f64 { - f64::from(self.minute()) + fn extract_millisecond(&self) -> T + where + T: DecimalLike, + { + let s = T::from(self.second() * 1_000); + let ns = T::from(self.nanosecond()) / T::from(1e6); + s + ns } - fn extract_second(&self) -> f64 { - let s = f64::from(self.second()); - let ns = f64::from(self.nanosecond()) / 1e9; + fn extract_microsecond(&self) -> T + where + T: DecimalLike, + { + let s = T::from(self.second() * 1_000_000); + let ns = T::from(self.nanosecond()) / T::from(1e3); s + ns } +} - fn extract_millisecond(&self) -> f64 { - let s = f64::from(self.second() * 1_000); - let ns = f64::from(self.nanosecond()) / 1e6; - s + ns +impl TimeLike for T where T: chrono::Timelike {} + +/// Common set of methods for date component. +pub trait DateLike: chrono::Datelike { + fn extract_epoch(&self) -> i64 { + let naive_date = + NaiveDate::from_ymd(self.year(), self.month(), self.day()).and_hms(0, 0, 0); + naive_date.timestamp() } - fn extract_microsecond(&self) -> f64 { - let s = f64::from(self.second() * 1_000_000); - let ns = f64::from(self.nanosecond()) / 1e3; - s + ns + fn millennium(&self) -> i32 { + (self.year() + if self.year() > 0 { 999 } else { -1_000 }) / 1_000 + } + + fn century(&self) -> i32 { + (self.year() + if self.year() > 0 { 99 } else { -100 }) / 100 + } + + fn decade(&self) -> i32 { + self.year().div_euclid(10) + } + + fn quarter(&self) -> f64 { + (f64::from(self.month()) / 3.0).ceil() + } + + /// Extract the iso week of the year + /// + /// Note that because isoweeks are defined in terms of January 4th, Jan 1 is only in week + /// 1 about half of the time + fn week(&self) -> u32 { + self.iso_week().week() + } + + fn day_of_week(&self) -> u32 { + self.weekday().num_days_from_sunday() + } + + fn iso_day_of_week(&self) -> u32 { + self.weekday().number_from_monday() } } -impl TimeLike for T where T: chrono::Timelike {} +impl DateLike for T where T: chrono::Datelike {} /// A timestamp with both a date and a time component, but not necessarily a /// timezone component. pub trait TimestampLike: Clone + PartialOrd - + chrono::Datelike + std::ops::Add + std::ops::Sub + std::ops::Sub + for<'a> Into> + for<'a> TryFrom, Error = ()> + TimeLike + + DateLike { fn new(date: NaiveDate, time: NaiveTime) -> Self; @@ -1429,56 +1480,11 @@ pub trait TimestampLike: fn timestamp_subsec_micros(&self) -> u32; - fn extract_epoch(&self) -> f64 { - self.timestamp() as f64 + (self.timestamp_subsec_micros() as f64) / 1e6 - } - - fn extract_year(&self) -> f64 { - f64::from(self.year()) - } - - fn extract_quarter(&self) -> f64 { - (f64::from(self.month()) / 3.0).ceil() - } - - fn extract_month(&self) -> f64 { - f64::from(self.month()) - } - - fn extract_day(&self) -> f64 { - f64::from(self.day()) - } - - fn extract_millennium(&self) -> f64 { - f64::from((self.year() + if self.year() > 0 { 999 } else { -1_000 }) / 1_000) - } - - fn extract_century(&self) -> f64 { - f64::from((self.year() + if self.year() > 0 { 99 } else { -100 }) / 100) - } - - fn extract_decade(&self) -> f64 { - f64::from(self.year().div_euclid(10)) - } - - /// Extract the iso week of the year - /// - /// Note that because isoweeks are defined in terms of January 4th, Jan 1 is only in week - /// 1 about half of the time - fn extract_week(&self) -> f64 { - f64::from(self.iso_week().week()) - } - - fn extract_dayofyear(&self) -> f64 { - f64::from(self.ordinal()) - } - - fn extract_dayofweek(&self) -> f64 { - f64::from(self.weekday().num_days_from_sunday()) - } - - fn extract_isodayofweek(&self) -> f64 { - f64::from(self.weekday().number_from_monday()) + fn extract_epoch(&self) -> T + where + T: DecimalLike, + { + T::lossy_from(self.timestamp()) + T::from(self.timestamp_subsec_micros()) / T::from(1e6) } fn truncate_microseconds(&self) -> Self { @@ -1713,32 +1719,39 @@ impl TimestampLike for chrono::DateTime { } } -fn date_part_interval<'a>(a: Datum<'a>, interval: Interval) -> Result, EvalError> { +fn date_part_interval<'a, D>(a: Datum<'a>, b: Datum<'a>) -> Result, EvalError> +where + D: DecimalLike + Into>, +{ let units = a.unwrap_str(); match units.parse() { - Ok(units) => date_part_interval_inner(units, interval), + Ok(units) => date_part_interval_inner::(units, b), Err(_) => Err(EvalError::UnknownUnits(units.to_owned())), } } -fn date_part_interval_inner( +fn date_part_interval_inner( units: DateTimeUnits, - interval: Interval, -) -> Result, EvalError> { + interval: Datum, +) -> Result, EvalError> +where + D: DecimalLike + Into>, +{ + let interval = interval.unwrap_interval(); match units { - DateTimeUnits::Epoch => Ok(interval.as_seconds().into()), - DateTimeUnits::Year => Ok(interval.years().into()), - DateTimeUnits::Day => Ok(interval.days().into()), - DateTimeUnits::Hour => Ok(interval.hours().into()), - DateTimeUnits::Minute => Ok(interval.minutes().into()), - DateTimeUnits::Second => Ok(interval.seconds().into()), - DateTimeUnits::Millennium => Ok(interval.millennia().into()), - DateTimeUnits::Century => Ok(interval.centuries().into()), - DateTimeUnits::Decade => Ok(interval.decades().into()), - DateTimeUnits::Quarter => Ok(interval.quarters().into()), - DateTimeUnits::Month => Ok(interval.months().into()), - DateTimeUnits::Milliseconds => Ok(interval.milliseconds().into()), - DateTimeUnits::Microseconds => Ok(interval.microseconds().into()), + DateTimeUnits::Epoch => Ok(interval.as_seconds::().into()), + DateTimeUnits::Millennium => Ok(D::from(interval.millennia()).into()), + DateTimeUnits::Century => Ok(D::from(interval.centuries()).into()), + DateTimeUnits::Decade => Ok(D::from(interval.decades()).into()), + DateTimeUnits::Year => Ok(D::from(interval.years()).into()), + DateTimeUnits::Quarter => Ok(D::from(interval.quarters()).into()), + DateTimeUnits::Month => Ok(D::from(interval.months()).into()), + DateTimeUnits::Day => Ok(D::lossy_from(interval.days()).into()), + DateTimeUnits::Hour => Ok(D::lossy_from(interval.hours()).into()), + DateTimeUnits::Minute => Ok(D::lossy_from(interval.minutes()).into()), + DateTimeUnits::Second => Ok(interval.seconds::().into()), + DateTimeUnits::Milliseconds => Ok(interval.milliseconds::().into()), + DateTimeUnits::Microseconds => Ok(interval.microseconds::().into()), DateTimeUnits::Week | DateTimeUnits::Timezone | DateTimeUnits::TimezoneHour @@ -1753,40 +1766,44 @@ fn date_part_interval_inner( } } -fn date_part_time<'a, T>(a: Datum<'a>, time: T) -> Result, EvalError> +fn date_part_time<'a, D>(a: Datum<'a>, b: Datum<'a>) -> Result, EvalError> where - T: TimeLike, + D: DecimalLike + Into>, { let units = a.unwrap_str(); match units.parse() { - Ok(units) => date_part_time_inner(units, time), + Ok(units) => date_part_time_inner::(units, b), Err(_) => Err(EvalError::UnknownUnits(units.to_owned())), } } -fn date_part_time_inner<'a, T>(units: DateTimeUnits, time: T) -> Result, EvalError> +fn date_part_time_inner<'a, D>( + units: DateTimeUnits, + time: Datum<'a>, +) -> Result, EvalError> where - T: TimeLike, + D: DecimalLike + Into>, { + let time = time.unwrap_time(); match units { - DateTimeUnits::Hour => Ok(time.extract_hour().into()), - DateTimeUnits::Minute => Ok(time.extract_minute().into()), - DateTimeUnits::Second => Ok(time.extract_second().into()), - DateTimeUnits::Milliseconds => Ok(time.extract_millisecond().into()), - DateTimeUnits::Microseconds => Ok(time.extract_microsecond().into()), - DateTimeUnits::Epoch - | DateTimeUnits::Year - | DateTimeUnits::Week - | DateTimeUnits::Day - | DateTimeUnits::Millennium + DateTimeUnits::Epoch => Ok(time.extract_epoch::().into()), + DateTimeUnits::Hour => Ok(D::from(time.hour()).into()), + DateTimeUnits::Minute => Ok(D::from(time.minute()).into()), + DateTimeUnits::Second => Ok(time.extract_second::().into()), + DateTimeUnits::Milliseconds => Ok(time.extract_millisecond::().into()), + DateTimeUnits::Microseconds => Ok(time.extract_microsecond::().into()), + DateTimeUnits::Millennium | DateTimeUnits::Century | DateTimeUnits::Decade + | DateTimeUnits::Year | DateTimeUnits::Quarter | DateTimeUnits::Month - | DateTimeUnits::DayOfWeek + | DateTimeUnits::Week + | DateTimeUnits::Day | DateTimeUnits::DayOfYear - | DateTimeUnits::IsoDayOfWeek - | DateTimeUnits::IsoDayOfYear => Err(EvalError::UnsupportedUnits( + | DateTimeUnits::DayOfWeek + | DateTimeUnits::IsoDayOfYear + | DateTimeUnits::IsoDayOfWeek => Err(EvalError::UnsupportedUnits( format!("{}", units), "time".to_string(), )), @@ -1799,39 +1816,82 @@ where } } -fn date_part_timestamp<'a, T>(a: Datum<'a>, ts: T) -> Result, EvalError> +fn date_part_timestamp<'a, T, D>(a: Datum<'a>, ts: T) -> Result, EvalError> where T: TimestampLike, + D: DecimalLike + Into>, { let units = a.unwrap_str(); match units.parse() { - Ok(units) => date_part_timestamp_inner(units, ts), + Ok(units) => date_part_timestamp_inner::<_, D>(units, ts), Err(_) => Err(EvalError::UnknownUnits(units.to_owned())), } } -fn date_part_timestamp_inner<'a, T>(units: DateTimeUnits, ts: T) -> Result, EvalError> +fn date_part_timestamp_inner<'a, T, D>(units: DateTimeUnits, ts: T) -> Result, EvalError> where T: TimestampLike, + D: DecimalLike + Into>, { match units { - DateTimeUnits::Epoch => Ok(ts.extract_epoch().into()), - DateTimeUnits::Year => Ok(ts.extract_year().into()), - DateTimeUnits::Quarter => Ok(ts.extract_quarter().into()), - DateTimeUnits::Week => Ok(ts.extract_week().into()), - DateTimeUnits::Day => Ok(ts.extract_day().into()), - DateTimeUnits::DayOfWeek => Ok(ts.extract_dayofweek().into()), - DateTimeUnits::DayOfYear => Ok(ts.extract_dayofyear().into()), - DateTimeUnits::IsoDayOfWeek => Ok(ts.extract_isodayofweek().into()), - DateTimeUnits::Hour => Ok(ts.extract_hour().into()), - DateTimeUnits::Minute => Ok(ts.extract_minute().into()), - DateTimeUnits::Second => Ok(ts.extract_second().into()), - DateTimeUnits::Month => Ok(ts.extract_month().into()), - DateTimeUnits::Milliseconds => Ok(ts.extract_millisecond().into()), - DateTimeUnits::Microseconds => Ok(ts.extract_microsecond().into()), - DateTimeUnits::Millennium => Ok(ts.extract_millennium().into()), - DateTimeUnits::Century => Ok(ts.extract_century().into()), - DateTimeUnits::Decade => Ok(ts.extract_decade().into()), + DateTimeUnits::Epoch => Ok(TimestampLike::extract_epoch::(&ts).into()), + DateTimeUnits::Millennium => Ok(D::from(ts.millennium()).into()), + DateTimeUnits::Century => Ok(D::from(ts.century()).into()), + DateTimeUnits::Decade => Ok(D::from(ts.decade()).into()), + DateTimeUnits::Year => Ok(D::from(ts.year()).into()), + DateTimeUnits::Quarter => Ok(D::from(ts.quarter()).into()), + DateTimeUnits::Week => Ok(D::from(ts.week()).into()), + DateTimeUnits::Month => Ok(D::from(ts.month()).into()), + DateTimeUnits::Day => Ok(D::from(ts.day()).into()), + DateTimeUnits::DayOfWeek => Ok(D::from(ts.day_of_week()).into()), + DateTimeUnits::DayOfYear => Ok(D::from(ts.ordinal()).into()), + DateTimeUnits::IsoDayOfWeek => Ok(D::from(ts.iso_day_of_week()).into()), + DateTimeUnits::Hour => Ok(D::from(ts.hour()).into()), + DateTimeUnits::Minute => Ok(D::from(ts.minute()).into()), + DateTimeUnits::Second => Ok(ts.extract_second::().into()), + DateTimeUnits::Milliseconds => Ok(ts.extract_millisecond::().into()), + DateTimeUnits::Microseconds => Ok(ts.extract_microsecond::().into()), + DateTimeUnits::Timezone + | DateTimeUnits::TimezoneHour + | DateTimeUnits::TimezoneMinute + | DateTimeUnits::IsoDayOfYear => Err(EvalError::Unsupported { + feature: format!("'{}' timestamp units", units), + issue_no: None, + }), + } +} + +fn extract_date<'a>(a: Datum<'a>, b: Datum<'a>) -> Result, EvalError> { + let units = a.unwrap_str(); + match units.parse() { + Ok(units) => extract_date_inner(units, b), + Err(_) => Err(EvalError::UnknownUnits(units.to_owned())), + } +} + +fn extract_date_inner(units: DateTimeUnits, date: Datum) -> Result { + let date = date.unwrap_date(); + match units { + DateTimeUnits::Epoch => Ok(Numeric::from(date.extract_epoch()).into()), + DateTimeUnits::Millennium => Ok(Numeric::from(date.millennium()).into()), + DateTimeUnits::Century => Ok(Numeric::from(date.century()).into()), + DateTimeUnits::Decade => Ok(Numeric::from(date.decade()).into()), + DateTimeUnits::Year => Ok(Numeric::from(date.year()).into()), + DateTimeUnits::Quarter => Ok(Numeric::from(date.quarter()).into()), + DateTimeUnits::Week => Ok(Numeric::from(date.week()).into()), + DateTimeUnits::Month => Ok(Numeric::from(date.month()).into()), + DateTimeUnits::Day => Ok(Numeric::from(date.day()).into()), + DateTimeUnits::DayOfWeek => Ok(Numeric::from(date.day_of_week()).into()), + DateTimeUnits::DayOfYear => Ok(Numeric::from(date.ordinal()).into()), + DateTimeUnits::IsoDayOfWeek => Ok(Numeric::from(date.iso_day_of_week()).into()), + DateTimeUnits::Hour + | DateTimeUnits::Minute + | DateTimeUnits::Second + | DateTimeUnits::Milliseconds + | DateTimeUnits::Microseconds => Err(EvalError::UnsupportedUnits( + format!("{}", units), + "date".to_string(), + )), DateTimeUnits::Timezone | DateTimeUnits::TimezoneHour | DateTimeUnits::TimezoneMinute @@ -2143,6 +2203,11 @@ pub enum BinaryFunc { ToCharTimestampTz, DateBinTimestamp, DateBinTimestampTz, + ExtractInterval, + ExtractTime, + ExtractTimestamp, + ExtractTimestampTz, + ExtractDate, DatePartInterval, DatePartTime, DatePartTimestamp, @@ -2345,17 +2410,32 @@ impl BinaryFunc { DateTime::::from_utc(NaiveDateTime::from_timestamp(0, 0), Utc) )) } + BinaryFunc::ExtractInterval => { + eager!(date_part_interval::) + } + BinaryFunc::ExtractTime => { + eager!(date_part_time::) + } + BinaryFunc::ExtractTimestamp => { + eager!(|a, b: Datum| date_part_timestamp::<_, Numeric>(a, b.unwrap_timestamp())) + } + BinaryFunc::ExtractTimestampTz => { + eager!(|a, b: Datum| date_part_timestamp::<_, Numeric>(a, b.unwrap_timestamptz())) + } + BinaryFunc::ExtractDate => { + eager!(extract_date) + } BinaryFunc::DatePartInterval => { - eager!(|a, b: Datum| date_part_interval(a, b.unwrap_interval())) + eager!(date_part_interval::) } BinaryFunc::DatePartTime => { - eager!(|a, b: Datum| date_part_time(a, b.unwrap_time())) + eager!(date_part_time::) } BinaryFunc::DatePartTimestamp => { - eager!(|a, b: Datum| date_part_timestamp(a, b.unwrap_timestamp())) + eager!(|a, b: Datum| date_part_timestamp::<_, f64>(a, b.unwrap_timestamp())) } BinaryFunc::DatePartTimestampTz => { - eager!(|a, b: Datum| date_part_timestamp(a, b.unwrap_timestamptz())) + eager!(|a, b: Datum| date_part_timestamp::<_, f64>(a, b.unwrap_timestamptz())) } BinaryFunc::DateTruncTimestamp => { eager!(|a, b: Datum| date_trunc(a, b.unwrap_timestamp())) @@ -2521,6 +2601,10 @@ impl BinaryFunc { ScalarType::Timestamp.nullable(in_nullable) } + ExtractInterval | ExtractTime | ExtractTimestamp | ExtractTimestampTz | ExtractDate => { + ScalarType::Numeric { scale: None }.nullable(true) + } + DatePartInterval | DatePartTime | DatePartTimestamp | DatePartTimestampTz => { ScalarType::Float64.nullable(true) } @@ -2806,6 +2890,11 @@ impl BinaryFunc { | ToCharTimestampTz | DateBinTimestamp | DateBinTimestampTz + | ExtractInterval + | ExtractTime + | ExtractTimestamp + | ExtractTimestampTz + | ExtractDate | DatePartInterval | DatePartTime | DatePartTimestamp @@ -2946,6 +3035,11 @@ impl fmt::Display for BinaryFunc { BinaryFunc::ToCharTimestampTz => f.write_str("tochartstz"), BinaryFunc::DateBinTimestamp => f.write_str("bin_unix_epoch_timestamp"), BinaryFunc::DateBinTimestampTz => f.write_str("bin_unix_epoch_timestamptz"), + BinaryFunc::ExtractInterval => f.write_str("extractiv"), + BinaryFunc::ExtractTime => f.write_str("extractt"), + BinaryFunc::ExtractTimestamp => f.write_str("extractts"), + BinaryFunc::ExtractTimestampTz => f.write_str("extracttstz"), + BinaryFunc::ExtractDate => f.write_str("extractd"), BinaryFunc::DatePartInterval => f.write_str("date_partiv"), BinaryFunc::DatePartTime => f.write_str("date_partt"), BinaryFunc::DatePartTimestamp => f.write_str("date_partts"), @@ -3257,6 +3351,11 @@ pub enum UnaryFunc { CharLength, IsRegexpMatch(Regex), RegexpMatch(Regex), + ExtractInterval(DateTimeUnits), + ExtractTime(DateTimeUnits), + ExtractTimestamp(DateTimeUnits), + ExtractTimestampTz(DateTimeUnits), + ExtractDate(DateTimeUnits), DatePartInterval(DateTimeUnits), DatePartTime(DateTimeUnits), DatePartTimestamp(DateTimeUnits), @@ -3633,10 +3732,23 @@ impl UnaryFunc { CharLength => char_length(a), IsRegexpMatch(regex) => Ok(is_regexp_match_static(a, ®ex)), RegexpMatch(regex) => regexp_match_static(a, temp_storage, ®ex), - DatePartInterval(units) => date_part_interval_inner(*units, a.unwrap_interval()), - DatePartTime(units) => date_part_time_inner(*units, a.unwrap_time()), - DatePartTimestamp(units) => date_part_timestamp_inner(*units, a.unwrap_timestamp()), - DatePartTimestampTz(units) => date_part_timestamp_inner(*units, a.unwrap_timestamptz()), + ExtractInterval(units) => date_part_interval_inner::(*units, a), + ExtractTime(units) => date_part_time_inner::(*units, a), + ExtractTimestamp(units) => { + date_part_timestamp_inner::<_, Numeric>(*units, a.unwrap_timestamp()) + } + ExtractTimestampTz(units) => { + date_part_timestamp_inner::<_, Numeric>(*units, a.unwrap_timestamptz()) + } + ExtractDate(units) => extract_date_inner(*units, a), + DatePartInterval(units) => date_part_interval_inner::(*units, a), + DatePartTime(units) => date_part_time_inner::(*units, a), + DatePartTimestamp(units) => { + date_part_timestamp_inner::<_, f64>(*units, a.unwrap_timestamp()) + } + DatePartTimestampTz(units) => { + date_part_timestamp_inner::<_, f64>(*units, a.unwrap_timestamptz()) + } DateTruncTimestamp(units) => date_trunc_inner(*units, a.unwrap_timestamp()), DateTruncTimestampTz(units) => date_trunc_inner(*units, a.unwrap_timestamptz()), TimezoneTimestamp(tz) => timezone_timestamp(*tz, a.unwrap_timestamp()), @@ -3854,6 +3966,12 @@ impl UnaryFunc { return_ty.default_embedded_value().nullable(false) } + ExtractInterval(_) + | ExtractTime(_) + | ExtractTimestamp(_) + | ExtractTimestampTz(_) + | ExtractDate(_) => ScalarType::Numeric { scale: None }.nullable(nullable), + DatePartInterval(_) | DatePartTime(_) | DatePartTimestamp(_) @@ -4076,6 +4194,11 @@ impl UnaryFunc { TimezoneTimestamp(_) => false, CastList1ToList2 { .. } | CastInPlace { .. } => false, JsonbTypeof | JsonbStripNulls | JsonbPretty | ListLength => false, + ExtractInterval(_) + | ExtractTime(_) + | ExtractTimestamp(_) + | ExtractTimestampTz(_) + | ExtractDate(_) => false, DatePartInterval(_) | DatePartTime(_) | DatePartTimestamp(_) @@ -4321,6 +4444,11 @@ impl UnaryFunc { ByteLengthString => f.write_str("octet_length"), IsRegexpMatch(regex) => write!(f, "{} ~", regex.as_str().quoted()), RegexpMatch(regex) => write!(f, "regexp_match[{}]", regex.as_str()), + ExtractInterval(units) => write!(f, "extract_{}_iv", units), + ExtractTime(units) => write!(f, "extract_{}_t", units), + ExtractTimestamp(units) => write!(f, "extract_{}_ts", units), + ExtractTimestampTz(units) => write!(f, "extract_{}_tstz", units), + ExtractDate(units) => write!(f, "extract_{}_d", units), DatePartInterval(units) => write!(f, "date_part_{}_iv", units), DatePartTime(units) => write!(f, "date_part_{}_t", units), DatePartTimestamp(units) => write!(f, "date_part_{}_ts", units), diff --git a/src/expr/src/scalar/func/impls/interval.rs b/src/expr/src/scalar/func/impls/interval.rs index b5f587772749b..819a96a99a4b8 100644 --- a/src/expr/src/scalar/func/impls/interval.rs +++ b/src/expr/src/scalar/func/impls/interval.rs @@ -39,7 +39,7 @@ sqlfunc!( NaiveTime::from_hms_nano( i.hours() as u32, i.minutes() as u32, - i.seconds() as u32, + i.seconds::() as u32, i.nanoseconds() as u32, ) } diff --git a/src/expr/src/scalar/mod.rs b/src/expr/src/scalar/mod.rs index 5531c4e179576..b18e2e99c2883 100644 --- a/src/expr/src/scalar/mod.rs +++ b/src/expr/src/scalar/mod.rs @@ -372,6 +372,66 @@ impl MirScalarExpr { ), }; } + } else if *func == BinaryFunc::ExtractInterval && expr1.is_literal() { + let units = expr1.as_literal_str().unwrap(); + *e = match units.parse::() { + Ok(units) => MirScalarExpr::CallUnary { + func: UnaryFunc::ExtractInterval(units), + expr: Box::new(expr2.take()), + }, + Err(_) => MirScalarExpr::literal( + Err(EvalError::UnknownUnits(units.to_owned())), + e.typ(&relation_type).scalar_type, + ), + } + } else if *func == BinaryFunc::ExtractTime && expr1.is_literal() { + let units = expr1.as_literal_str().unwrap(); + *e = match units.parse::() { + Ok(units) => MirScalarExpr::CallUnary { + func: UnaryFunc::ExtractTime(units), + expr: Box::new(expr2.take()), + }, + Err(_) => MirScalarExpr::literal( + Err(EvalError::UnknownUnits(units.to_owned())), + e.typ(&relation_type).scalar_type, + ), + } + } else if *func == BinaryFunc::ExtractTimestamp && expr1.is_literal() { + let units = expr1.as_literal_str().unwrap(); + *e = match units.parse::() { + Ok(units) => MirScalarExpr::CallUnary { + func: UnaryFunc::ExtractTimestamp(units), + expr: Box::new(expr2.take()), + }, + Err(_) => MirScalarExpr::literal( + Err(EvalError::UnknownUnits(units.to_owned())), + e.typ(&relation_type).scalar_type, + ), + } + } else if *func == BinaryFunc::ExtractTimestampTz && expr1.is_literal() { + let units = expr1.as_literal_str().unwrap(); + *e = match units.parse::() { + Ok(units) => MirScalarExpr::CallUnary { + func: UnaryFunc::ExtractTimestampTz(units), + expr: Box::new(expr2.take()), + }, + Err(_) => MirScalarExpr::literal( + Err(EvalError::UnknownUnits(units.to_owned())), + e.typ(&relation_type).scalar_type, + ), + } + } else if *func == BinaryFunc::ExtractDate && expr1.is_literal() { + let units = expr1.as_literal_str().unwrap(); + *e = match units.parse::() { + Ok(units) => MirScalarExpr::CallUnary { + func: UnaryFunc::ExtractDate(units), + expr: Box::new(expr2.take()), + }, + Err(_) => MirScalarExpr::literal( + Err(EvalError::UnknownUnits(units.to_owned())), + e.typ(&relation_type).scalar_type, + ), + } } else if *func == BinaryFunc::DatePartInterval && expr1.is_literal() { let units = expr1.as_literal_str().unwrap(); *e = match units.parse::() { diff --git a/src/repr/src/adt/datetime.rs b/src/repr/src/adt/datetime.rs index f3bf43c9bde19..7721d53a674bd 100644 --- a/src/repr/src/adt/datetime.rs +++ b/src/repr/src/adt/datetime.rs @@ -73,7 +73,7 @@ impl fmt::Display for DateTimeUnits { Self::IsoDayOfWeek => f.write_str("isodow"), Self::IsoDayOfYear => f.write_str("isodoy"), Self::Minute => f.write_str("minute"), - Self::Second => f.write_str(""), + Self::Second => f.write_str("seconds"), Self::Milliseconds => f.write_str("milliseconds"), Self::Microseconds => f.write_str("microseconds"), Self::Timezone => f.write_str("timezone"), diff --git a/src/repr/src/adt/interval.rs b/src/repr/src/adt/interval.rs index 768b7b2353b65..d5dda63202c15 100644 --- a/src/repr/src/adt/interval.rs +++ b/src/repr/src/adt/interval.rs @@ -16,6 +16,7 @@ use anyhow::bail; use serde::{Deserialize, Serialize}; use crate::adt::datetime::DateTimeField; +use crate::adt::numeric::DecimalLike; /// An interval of time meant to express SQL intervals. /// @@ -145,102 +146,111 @@ impl Interval { /// Computes the millennium part of the interval. /// /// The millennium part is the number of whole millennia in the interval. For example, - /// this function returns `3.0` for the interval `3400 years`. - pub fn millennia(&self) -> f64 { - (self.months / 12_000) as f64 + /// this function returns `3` for the interval `3400 years`. + pub fn millennia(&self) -> i32 { + self.months / 12_000 } /// Computes the century part of the interval. /// /// The century part is the number of whole centuries in the interval. For example, - /// this function returns `3.0` for the interval `340 years`. - pub fn centuries(&self) -> f64 { - (self.months / 1_200) as f64 + /// this function returns `3` for the interval `340 years`. + pub fn centuries(&self) -> i32 { + self.months / 1_200 } /// Computes the decade part of the interval. /// /// The decade part is the number of whole decades in the interval. For example, - /// this function returns `3.0` for the interval `34 years`. - pub fn decades(&self) -> f64 { - (self.months / 120) as f64 + /// this function returns `3` for the interval `34 years`. + pub fn decades(&self) -> i32 { + self.months / 120 } /// Computes the year part of the interval. /// /// The year part is the number of whole years in the interval. For example, - /// this function returns `3.0` for the interval `3 years 4 months`. - pub fn years(&self) -> f64 { - (self.months / 12) as f64 + /// this function returns `3` for the interval `3 years 4 months`. + pub fn years(&self) -> i32 { + self.months / 12 } /// Computes the quarter part of the interval. /// /// The quarter part is obtained from taking the number of whole months modulo 12, /// and assigning quarter #1 for months 0-2, #2 for 3-5, #3 for 6-8 and #4 for 9-11. - /// For example, this function returns `4.0` for the interval `11 months`. - pub fn quarters(&self) -> f64 { - ((self.months % 12) / 3 + 1) as f64 + /// For example, this function returns `4` for the interval `11 months`. + pub fn quarters(&self) -> i32 { + (self.months % 12) / 3 + 1 } /// Computes the month part of the interval. /// /// The month part is the number of whole months in the interval, modulo 12. - /// For example, this function returns `4.0` for the interval `3 years 4 + /// For example, this function returns `4` for the interval `3 years 4 /// months`. - pub fn months(&self) -> f64 { - (self.months % 12) as f64 + pub fn months(&self) -> i32 { + self.months % 12 } /// Computes the day part of the interval. /// /// The day part is the number of whole days in the interval. For example, - /// this function returns `5.0` for the interval `5 days 4 hours 3 minutes + /// this function returns `5` for the interval `5 days 4 hours 3 minutes /// 2.1 seconds`. - pub fn days(&self) -> f64 { - (self.dur_as_secs() / (60 * 60 * 24)) as f64 + pub fn days(&self) -> i64 { + self.dur_as_secs() / (60 * 60 * 24) } /// Computes the hour part of the interval. /// /// The hour part is the number of whole hours in the interval, modulo 24. - /// For example, this function returns `4.0` for the interval `5 days 4 + /// For example, this function returns `4` for the interval `5 days 4 /// hours 3 minutes 2.1 seconds`. - pub fn hours(&self) -> f64 { - ((self.dur_as_secs() / (60 * 60)) % 24) as f64 + pub fn hours(&self) -> i64 { + (self.dur_as_secs() / (60 * 60)) % 24 } /// Computes the minute part of the interval. /// /// The minute part is the number of whole minutes in the interval, modulo - /// 60. For example, this function returns `3.0` for the interval `5 days 4 + /// 60. For example, this function returns `3` for the interval `5 days 4 /// hours 3 minutes 2.1 seconds`. - pub fn minutes(&self) -> f64 { - ((self.dur_as_secs() / 60) % 60) as f64 + pub fn minutes(&self) -> i64 { + (self.dur_as_secs() / 60) % 60 } /// Computes the second part of the interval. /// /// The second part is the number of fractional seconds in the interval, /// modulo 60.0. - pub fn seconds(&self) -> f64 { - (self.duration % 60_000_000_000) as f64 / 1e9 + pub fn seconds(&self) -> T + where + T: DecimalLike, + { + T::lossy_from((self.duration % 60_000_000_000) as i64) / T::from(1e9) } /// Computes the second part of the interval displayed in milliseconds. /// /// The second part is the number of fractional seconds in the interval, /// modulo 60.0. - pub fn milliseconds(&self) -> f64 { - (self.duration % 60_000_000_000) as f64 / 1e6 + pub fn milliseconds(&self) -> T + where + T: DecimalLike, + { + T::lossy_from((self.duration % 60_000_000_000) as i64) / T::from(1e6) } /// Computes the second part of the interval displayed in microseconds. /// /// The second part is the number of fractional seconds in the interval, /// modulo 60.0. - pub fn microseconds(&self) -> f64 { - (self.duration % 60_000_000_000) as f64 / 1e3 + pub fn microseconds(&self) -> T + where + T: DecimalLike, + { + T::lossy_from((self.duration % 60_000_000_000) as i64) / T::from(1e3) } /// Computes the nanosecond part of the interval. @@ -249,11 +259,14 @@ impl Interval { } /// Computes the total number of seconds in the interval. - pub fn as_seconds(&self) -> f64 { - self.years() * 60.0 * 60.0 * 24.0 * 365.25 - + self.months() * 60.0 * 60.0 * 24.0 * 30.0 - + (self.dur_as_secs() as f64) - + f64::from(self.nanoseconds()) / 1e9 + pub fn as_seconds(&self) -> T + where + T: DecimalLike, + { + T::from(self.years() * 60 * 60 * 24) * T::from(365.25) + + T::from(self.months() * 60 * 60 * 24 * 30) + + T::lossy_from(self.dur_as_secs()) + + T::from(self.nanoseconds()) / T::from(1e9) } /// Truncate the "head" of the interval, removing all time units greater than `f`. diff --git a/src/repr/src/adt/numeric.rs b/src/repr/src/adt/numeric.rs index a0e8a39526928..e0b1f545eaa2a 100644 --- a/src/repr/src/adt/numeric.rs +++ b/src/repr/src/adt/numeric.rs @@ -616,3 +616,36 @@ pub fn extract_typ_mod(typ_mod: &[u64]) -> Result, anyhow::Error> { _ => unreachable!(), }) } + +/// A type that can represent Real Numbers. Useful for interoperability between Numeric and +/// floating point. +pub trait DecimalLike: + From + + From + + From + + From + + From + + From + + From + + From + + std::ops::Add + + std::ops::Sub + + std::ops::Mul + + std::ops::Div +{ + /// Used to do value-to-value conversions while consuming the input value. Depending on the + /// implementation it may be potentially lossy. + fn lossy_from(i: i64) -> Self; +} + +impl DecimalLike for f64 { + fn lossy_from(i: i64) -> Self { + i as f64 + } +} + +impl DecimalLike for Numeric { + fn lossy_from(i: i64) -> Self { + Numeric::from(i) + } +} diff --git a/src/sql-parser/src/parser.rs b/src/sql-parser/src/parser.rs index 972b1f922feca..18eb0e45d4ab4 100644 --- a/src/sql-parser/src/parser.rs +++ b/src/sql-parser/src/parser.rs @@ -728,7 +728,7 @@ impl<'a> Parser<'a> { let expr = self.parse_expr()?; self.expect_token(&Token::RParen)?; Ok(Expr::Function(Function { - name: UnresolvedObjectName::unqualified("date_part"), + name: UnresolvedObjectName::unqualified("extract"), args: FunctionArgs::args(vec![Expr::Value(Value::String(field)), expr]), filter: None, over: None, diff --git a/src/sql-parser/tests/sqlparser_common.rs b/src/sql-parser/tests/sqlparser_common.rs index ce6de9d87ce79..c7e83dc9d8324 100644 --- a/src/sql-parser/tests/sqlparser_common.rs +++ b/src/sql-parser/tests/sqlparser_common.rs @@ -208,7 +208,7 @@ fn test_basic_visitor() -> Result<(), Box> { AND CASE a27 WHEN a28 THEN a29 ELSE a30 END AND a31 BETWEEN a32 AND a33 AND a34 COLLATE a35 = a36 - AND EXTRACT(YEAR FROM a37) + AND DATE_PART('YEAR', a37) AND (SELECT a38) AND EXISTS (SELECT a39) FROM a40(a41) AS a42 diff --git a/src/sql-parser/tests/testdata/scalar b/src/sql-parser/tests/testdata/scalar index a6d6a67bf5ebe..f4ec52379a1ab 100644 --- a/src/sql-parser/tests/testdata/scalar +++ b/src/sql-parser/tests/testdata/scalar @@ -169,107 +169,214 @@ id::numeric parse-scalar EXTRACT(YEAR FROM d) ---- -Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("year")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) +Function(Function { name: UnresolvedObjectName([Ident("extract")]), args: Args { args: [Value(String("year")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) parse-scalar EXTRACT(MILLENIUM FROM d) ---- -Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("millenium")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) +Function(Function { name: UnresolvedObjectName([Ident("extract")]), args: Args { args: [Value(String("millenium")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) parse-scalar EXTRACT(CENTURY FROM d) ---- -Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("century")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) +Function(Function { name: UnresolvedObjectName([Ident("extract")]), args: Args { args: [Value(String("century")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) parse-scalar EXTRACT(YEAR FROM d) ---- -Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("year")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) +Function(Function { name: UnresolvedObjectName([Ident("extract")]), args: Args { args: [Value(String("year")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) parse-scalar EXTRACT(ISOYEAR FROM d) ---- -Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("isoyear")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) +Function(Function { name: UnresolvedObjectName([Ident("extract")]), args: Args { args: [Value(String("isoyear")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) parse-scalar EXTRACT(QUARTER FROM d) ---- -Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("quarter")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) +Function(Function { name: UnresolvedObjectName([Ident("extract")]), args: Args { args: [Value(String("quarter")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) parse-scalar EXTRACT(MONTH FROM d) ---- -Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("month")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) +Function(Function { name: UnresolvedObjectName([Ident("extract")]), args: Args { args: [Value(String("month")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) parse-scalar EXTRACT(DAY FROM d) ---- -Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("day")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) +Function(Function { name: UnresolvedObjectName([Ident("extract")]), args: Args { args: [Value(String("day")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) parse-scalar EXTRACT(HOUR FROM d) ---- -Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("hour")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) +Function(Function { name: UnresolvedObjectName([Ident("extract")]), args: Args { args: [Value(String("hour")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) parse-scalar EXTRACT(MINUTE FROM d) ---- -Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("minute")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) +Function(Function { name: UnresolvedObjectName([Ident("extract")]), args: Args { args: [Value(String("minute")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) parse-scalar EXTRACT(SECOND FROM d) ---- -Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("second")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) +Function(Function { name: UnresolvedObjectName([Ident("extract")]), args: Args { args: [Value(String("second")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) parse-scalar EXTRACT(MILLISECONDS FROM d) ---- -Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("milliseconds")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) +Function(Function { name: UnresolvedObjectName([Ident("extract")]), args: Args { args: [Value(String("milliseconds")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) parse-scalar EXTRACT(MICROSECONDS FROM d) ---- -Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("microseconds")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) +Function(Function { name: UnresolvedObjectName([Ident("extract")]), args: Args { args: [Value(String("microseconds")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) parse-scalar EXTRACT(TIMEZONE FROM d) ---- -Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("timezone")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) +Function(Function { name: UnresolvedObjectName([Ident("extract")]), args: Args { args: [Value(String("timezone")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) parse-scalar EXTRACT(TIMEZONE_HOUR FROM d) ---- -Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("timezone_hour")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) +Function(Function { name: UnresolvedObjectName([Ident("extract")]), args: Args { args: [Value(String("timezone_hour")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) parse-scalar EXTRACT(TIMEZONE_MINUTE FROM d) ---- -Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("timezone_minute")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) +Function(Function { name: UnresolvedObjectName([Ident("extract")]), args: Args { args: [Value(String("timezone_minute")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) parse-scalar EXTRACT(WEEK FROM d) ---- -Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("week")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) +Function(Function { name: UnresolvedObjectName([Ident("extract")]), args: Args { args: [Value(String("week")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) parse-scalar EXTRACT(DOY FROM d) ---- -Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("doy")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) +Function(Function { name: UnresolvedObjectName([Ident("extract")]), args: Args { args: [Value(String("doy")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) parse-scalar EXTRACT(DOW FROM d) ---- -Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("dow")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) +Function(Function { name: UnresolvedObjectName([Ident("extract")]), args: Args { args: [Value(String("dow")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) parse-scalar EXTRACT(ISODOW FROM d) ---- -Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("isodow")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) +Function(Function { name: UnresolvedObjectName([Ident("extract")]), args: Args { args: [Value(String("isodow")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) parse-scalar EXTRACT(EPOCH FROM d) ---- -Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("epoch")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) +Function(Function { name: UnresolvedObjectName([Ident("extract")]), args: Args { args: [Value(String("epoch")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) + +# date_part + +parse-scalar +DATE_PART('YEAR', d) +---- +Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("YEAR")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) + +parse-scalar +DATE_PART('MILLENIUM', d) +---- +Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("MILLENIUM")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) + +parse-scalar +DATE_PART('CENTURY', d) +---- +Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("CENTURY")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) + +parse-scalar +DATE_PART('YEAR', d) +---- +Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("YEAR")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) + +parse-scalar +DATE_PART('ISOYEAR', d) +---- +Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("ISOYEAR")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) + +parse-scalar +DATE_PART('QUARTER', d) +---- +Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("QUARTER")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) + +parse-scalar +DATE_PART('MONTH', d) +---- +Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("MONTH")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) + +parse-scalar +DATE_PART('DAY', d) +---- +Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("DAY")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) + +parse-scalar +DATE_PART('HOUR', d) +---- +Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("HOUR")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) + +parse-scalar +DATE_PART('MINUTE', d) +---- +Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("MINUTE")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) + +parse-scalar +DATE_PART('SECOND', d) +---- +Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("SECOND")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) + +parse-scalar +DATE_PART('MILLISECONDS', d) +---- +Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("MILLISECONDS")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) + +parse-scalar +DATE_PART('MICROSECONDS', d) +---- +Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("MICROSECONDS")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) + +parse-scalar +DATE_PART('TIMEZONE', d) +---- +Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("TIMEZONE")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) + +parse-scalar +DATE_PART('TIMEZONE_HOUR', d) +---- +Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("TIMEZONE_HOUR")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) + +parse-scalar +DATE_PART('TIMEZONE_MINUTE', d) +---- +Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("TIMEZONE_MINUTE")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) + +parse-scalar +DATE_PART('WEEK', d) +---- +Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("WEEK")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) + +parse-scalar +DATE_PART('DOY', d) +---- +Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("DOY")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) + +parse-scalar +DATE_PART('DOW', d) +---- +Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("DOW")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) + +parse-scalar +DATE_PART('ISODOW', d) +---- +Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("ISODOW")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) + +parse-scalar +DATE_PART('EPOCH', d) +---- +Function(Function { name: UnresolvedObjectName([Ident("date_part")]), args: Args { args: [Value(String("EPOCH")), Identifier([Ident("d")])], order_by: [] }, filter: None, over: None, distinct: false }) parse-scalar COALESCE(foo, bar) diff --git a/src/sql/src/func.rs b/src/sql/src/func.rs index 105ef2de297fd..a1109c0e9dee5 100644 --- a/src/sql/src/func.rs +++ b/src/sql/src/func.rs @@ -1749,6 +1749,13 @@ lazy_static! { params!(Interval, Timestamp, Timestamp) => VariadicFunc::DateBinTimestamp, 6177; params!(Interval, TimestampTz, TimestampTz) => VariadicFunc::DateBinTimestampTz, 6178; }, + "extract" => Scalar { + params!(String, Interval) => BinaryFunc::ExtractInterval, 6204; + params!(String, Time) => BinaryFunc::ExtractTime, 6200; + params!(String, Timestamp) => BinaryFunc::ExtractTimestamp, 6202; + params!(String, TimestampTz) => BinaryFunc::ExtractTimestampTz, 6203; + params!(String, Date) => BinaryFunc::ExtractDate, 6199; + }, "date_part" => Scalar { params!(String, Interval) => BinaryFunc::DatePartInterval, 1172; params!(String, Time) => BinaryFunc::DatePartTime, 1385; diff --git a/src/sql/src/plan/statement/scl.rs b/src/sql/src/plan/statement/scl.rs index 1fe8f50b7e807..bff8a86cf775b 100644 --- a/src/sql/src/plan/statement/scl.rs +++ b/src/sql/src/plan/statement/scl.rs @@ -147,7 +147,7 @@ pub fn plan_fetch( // bumped. If we do bump it, ensure that the new upper limit is within the // bounds of a tokio time future, otherwise it'll panic. const SECS_PER_DAY: f64 = 60f64 * 60f64 * 24f64; - let timeout_secs = timeout.as_seconds(); + let timeout_secs = timeout.as_seconds::(); if !timeout_secs.is_finite() || timeout_secs < 0f64 || timeout_secs > SECS_PER_DAY { bail!("timeout out of range: {:#}", timeout); } diff --git a/test/sqllogictest/chbench.slt b/test/sqllogictest/chbench.slt index 1c50cb6af98e8..b206b87ae6f7c 100644 --- a/test/sqllogictest/chbench.slt +++ b/test/sqllogictest/chbench.slt @@ -626,7 +626,7 @@ ORDER BY su_nationkey, cust_nation, l_year | | delta %0 %5.(#21) %4.(#2, #1, #3) %3.(#2, #1, #0) %2.(#0, #1) %1.(#0) %0.(#0) | Filter (datetots(#31) <= 2012-01-02 00:00:00), (datetots(#31) >= 2007-01-02 00:00:00), (((#66 = "GERMANY") && (#70 = "CAMBODIA")) || ((#66 = "CAMBODIA") && (#70 = "GERMANY"))) | Project (#3, #33, #39, #52) -| Reduce group=(#0, substr(chartostr(#3), 1, 1), date_part_year_tstz(datetotstz(#2))) +| Reduce group=(#0, substr(chartostr(#3), 1, 1), extract_year_d(#2)) | | agg sum(#1) Finish order_by=(#0 asc, #1 asc, #2 asc) limit=none offset=0 project=(#0..#3) @@ -711,7 +711,7 @@ ORDER BY l_year | | delta %8 %6.(#2) %5.(#21) %4.(#2, #1, #3) %3.(#2, #1, #0) %2.(#0, #1) %0.(#0) %1.(#0) %7.(#0) | Filter (#0 < 1000), (#79 = "EUROPE"), (datetots(#44) <= 2012-01-02 00:00:00), (datetots(#44) >= 2007-01-02 00:00:00), "^.*b$" ~(padchar(#4)) | Project (#38, #44, #75) -| Reduce group=(date_part_year_tstz(datetotstz(#1))) +| Reduce group=(extract_year_d(#1)) | | agg sum(if (#2 = "GERMANY") then {#0} else {0}) | | agg sum(#0) | Map (#1 / if (#2 = 0) then {1} else {#2}) @@ -775,7 +775,7 @@ ORDER BY n_name, l_year DESC | | delta %5 %2.(#3) %1.(#17) %0.(#0) %3.(#5, #4) %4.(#0, #1, #2) | Filter "^.*BB$" ~(padchar(#4)) | Project (#38, #44, #49) -| Reduce group=(#2, date_part_year_tstz(datetotstz(#1))) +| Reduce group=(#2, extract_year_d(#1)) | | agg sum(#0) Finish order_by=(#0 asc, #1 desc) limit=none offset=0 project=(#0..#2) diff --git a/test/sqllogictest/cockroach/extract.slt b/test/sqllogictest/cockroach/extract.slt index 43c1a0dfbaad1..4cc14d6e77888 100644 --- a/test/sqllogictest/cockroach/extract.slt +++ b/test/sqllogictest/cockroach/extract.slt @@ -157,7 +157,7 @@ SELECT extract(minute FROM TIMESTAMP '2010-01-10 12:13:14.1') query R SELECT extract(second FROM TIMESTAMP '2010-01-10 12:13:14.1') ---- -14.100 +14.1 skipif postgresql # millisecond is not supported query R @@ -197,7 +197,7 @@ SELECT extract(minute FROM INTERVAL '123:10' MINUTE TO SECOND) query R SELECT extract(second FROM INTERVAL '10:20.30' MINUTE TO SECOND) ---- -20.300 +20.3 skipif postgresql # millisecond is not supported query R @@ -208,4 +208,4 @@ SELECT extract(millisecond FROM INTERVAL '20.3040' SECOND) query R SELECT extract(epoch FROM INTERVAL '10:20.30' MINUTE TO SECOND) ---- -620.300 +620.3 diff --git a/test/sqllogictest/dates-times.slt b/test/sqllogictest/dates-times.slt index e7fde4fd70762..311d43fe30fa8 100644 --- a/test/sqllogictest/dates-times.slt +++ b/test/sqllogictest/dates-times.slt @@ -752,102 +752,196 @@ SELECT (INTERVAL '1-3' YEAR TO MONTH)::text 1 year 3 months query RRRRR -SELECT EXTRACT(hour from TIME '11:12:42.666'), - EXTRACT(minute from TIME '11:12:42.666'), - EXTRACT(second from TIME '11:12:42.666'), - EXTRACT(milliseconds from TIME '11:12:42.666'), - EXTRACT(microseconds from TIME '11:12:42.666') +SELECT EXTRACT(HOUR from TIME '11:12:42.666'), + EXTRACT(MINUTE from TIME '11:12:42.666'), + EXTRACT(SECOND from TIME '11:12:42.666'), + EXTRACT(MILLISECONDS from TIME '11:12:42.666'), + EXTRACT(MICROSECONDS from TIME '11:12:42.666') ---- 11 12 42.666 42666 42666000 +query RRRRR +SELECT date_part('HOUR', TIME '11:12:42.666'), + date_part('MINUTE', TIME '11:12:42.666'), + date_part('SECOND', TIME '11:12:42.666'), + date_part('MILLISECONDS', TIME '11:12:42.666'), + date_part('MICROSECONDS', TIME '11:12:42.666') +---- +11 12 42.666 42666 42666000 + +query error unit 'millennium' not supported for type time +SELECT EXTRACT(MILLENNIUM from TIME '11:12:42.666') + +query error unit 'century' not supported for type time +SELECT EXTRACT(CENTURY from TIME '11:12:42.666') + +query error unit 'decade' not supported for type time +SELECT EXTRACT(DECADE from TIME '11:12:42.666') + query error unit 'year' not supported for type time -SELECT EXTRACT(year from TIME '11:12:42.666') +SELECT EXTRACT(YEAR from TIME '11:12:42.666') + +query error unit 'quarter' not supported for type time +SELECT EXTRACT(QUARTER from TIME '11:12:42.666') query error unit 'month' not supported for type time -SELECT EXTRACT(month from TIME '11:12:42.666') +SELECT EXTRACT(MONTH from TIME '11:12:42.666') + +query error unit 'week' not supported for type time +SELECT EXTRACT(WEEK from TIME '11:12:42.666') query error unit 'day' not supported for type time -SELECT EXTRACT(day from TIME '11:12:42.666') +SELECT EXTRACT(DAY from TIME '11:12:42.666') + +query error unit 'dow' not supported for type time +SELECT EXTRACT(DOW from TIME '11:12:42.666') + +query error unit 'doy' not supported for type time +SELECT EXTRACT(DOY from TIME '11:12:42.666') + +query error unit 'isodow' not supported for type time +SELECT EXTRACT(ISODOW from TIME '11:12:42.666') + +query error unit 'isodoy' not supported for type time +SELECT EXTRACT(ISODOY from TIME '11:12:42.666') + +query error unit 'month' not supported for type time +SELECT date_part('MONTH', TIME '11:12:42.666') query RR -SELECT EXTRACT(dow FROM TIMESTAMP '1999-12-26 00:00:00'), EXTRACT(dow FROM TIMESTAMP '2000-01-01 00:00:00') +SELECT EXTRACT(DOW FROM TIMESTAMP '1999-12-26 00:00:00'), EXTRACT(DOW FROM TIMESTAMP '2000-01-01 00:00:00') ---- 0 6 query RR -SELECT EXTRACT(isodow FROM TIMESTAMP '1999-12-26 00:00:00'), EXTRACT(isodow FROM TIMESTAMP '2000-01-01 00:00:00') +SELECT EXTRACT(ISODOW FROM TIMESTAMP '1999-12-26 00:00:00'), EXTRACT(ISODOW FROM TIMESTAMP '2000-01-01 00:00:00') ---- 7 6 -query RRR -SELECT EXTRACT(second FROM TIMESTAMP '2019-11-26 15:56:46.241150'), - EXTRACT(ms FROM TIMESTAMP '2019-11-26 15:56:46.241150'), - EXTRACT(us FROM TIMESTAMP '2019-11-26 15:56:46.241150') ----- -46.24115 46241.15 46241150 +query RRRRRRRRRRRRRRRRR +SELECT EXTRACT(EPOCH FROM TIMESTAMP '2019-11-26 15:56:46.241150'), + EXTRACT(MILLENNIUM FROM TIMESTAMP '2019-11-26 15:56:46.241150'), + EXTRACT(CENTURY FROM TIMESTAMP '2019-11-26 15:56:46.241150'), + EXTRACT(DECADE FROM TIMESTAMP '2019-11-26 15:56:46.241150'), + EXTRACT(YEAR FROM TIMESTAMP '2019-11-26 15:56:46.241150'), + EXTRACT(QUARTER FROM TIMESTAMP '2019-11-26 15:56:46.241150'), + EXTRACT(WEEK FROM TIMESTAMP '2019-11-26 15:56:46.241150'), + EXTRACT(MONTH FROM TIMESTAMP '2019-11-26 15:56:46.241150'), + EXTRACT(HOUR FROM TIMESTAMP '2019-11-26 15:56:46.241150'), + EXTRACT(DAY FROM TIMESTAMP '2019-11-26 15:56:46.241150'), + EXTRACT(DOW FROM TIMESTAMP '2019-11-26 15:56:46.241150'), + EXTRACT(DOY FROM TIMESTAMP '2019-11-26 15:56:46.241150'), + EXTRACT(ISODOW FROM TIMESTAMP '2019-11-26 15:56:46.241150'), + EXTRACT(MINUTE FROM TIMESTAMP '2019-11-26 15:56:46.241150'), + EXTRACT(SECOND FROM TIMESTAMP '2019-11-26 15:56:46.241150'), + EXTRACT(MS FROM TIMESTAMP '2019-11-26 15:56:46.241150'), + EXTRACT(US FROM TIMESTAMP '2019-11-26 15:56:46.241150') +---- +1574783806.24115 3 21 201 2019 4 48 11 15 26 2 330 2 56 46.24115 46241.15 46241150 + +query RRRRRRRRRRRRRRRRR +SELECT date_part('EPOCH', TIMESTAMP '2019-11-26 15:56:46.241150'), + date_part('MILLENNIUM', TIMESTAMP '2019-11-26 15:56:46.241150'), + date_part('CENTURY', TIMESTAMP '2019-11-26 15:56:46.241150'), + date_part('DECADE', TIMESTAMP '2019-11-26 15:56:46.241150'), + date_part('YEAR', TIMESTAMP '2019-11-26 15:56:46.241150'), + date_part('QUARTER', TIMESTAMP '2019-11-26 15:56:46.241150'), + date_part('WEEK', TIMESTAMP '2019-11-26 15:56:46.241150'), + date_part('MONTH', TIMESTAMP '2019-11-26 15:56:46.241150'), + date_part('HOUR', TIMESTAMP '2019-11-26 15:56:46.241150'), + date_part('DAY', TIMESTAMP '2019-11-26 15:56:46.241150'), + date_part('DOW', TIMESTAMP '2019-11-26 15:56:46.241150'), + date_part('DOY', TIMESTAMP '2019-11-26 15:56:46.241150'), + date_part('ISODOW', TIMESTAMP '2019-11-26 15:56:46.241150'), + date_part('MINUTE', TIMESTAMP '2019-11-26 15:56:46.241150'), + date_part('SECOND', TIMESTAMP '2019-11-26 15:56:46.241150'), + date_part('MS', TIMESTAMP '2019-11-26 15:56:46.241150'), + date_part('US', TIMESTAMP '2019-11-26 15:56:46.241150') +---- +1574783806.24115 3 21 201 2019 4 48 11 15 26 2 330 2 56 46.24115 46241.15 46241150 query RRRRRRRRRRRR -SELECT EXTRACT(quarter FROM DATE '2000-01-01'), - EXTRACT(quarter FROM DATE '2000-02-03'), - EXTRACT(quarter FROM DATE '2000-03-05'), - EXTRACT(quarter FROM DATE '2000-04-07'), - EXTRACT(quarter FROM DATE '2000-05-09'), - EXTRACT(quarter FROM DATE '2000-06-11'), - EXTRACT(quarter FROM DATE '2000-07-13'), - EXTRACT(quarter FROM DATE '2000-08-15'), - EXTRACT(quarter FROM DATE '2000-09-17'), - EXTRACT(quarter FROM DATE '2000-10-19'), - EXTRACT(quarter FROM DATE '2000-11-21'), - EXTRACT(quarter FROM DATE '2000-12-24') +SELECT EXTRACT(QUARTER FROM DATE '2000-01-01'), + EXTRACT(QUARTER FROM DATE '2000-02-03'), + EXTRACT(QUARTER FROM DATE '2000-03-05'), + EXTRACT(QUARTER FROM DATE '2000-04-07'), + EXTRACT(QUARTER FROM DATE '2000-05-09'), + EXTRACT(QUARTER FROM DATE '2000-06-11'), + EXTRACT(QUARTER FROM DATE '2000-07-13'), + EXTRACT(QUARTER FROM DATE '2000-08-15'), + EXTRACT(QUARTER FROM DATE '2000-09-17'), + EXTRACT(QUARTER FROM DATE '2000-10-19'), + EXTRACT(QUARTER FROM DATE '2000-11-21'), + EXTRACT(QUARTER FROM DATE '2000-12-24') ---- 1 1 1 2 2 2 3 3 3 4 4 4 query RRRRRRRR -SELECT EXTRACT(millennium FROM DATE '2001-01-01'), - EXTRACT(millennium FROM DATE '2000-01-01'), - EXTRACT(millennium FROM DATE '1999-01-01'), - EXTRACT(millennium FROM DATE '1001-01-01'), - EXTRACT(millennium FROM DATE '1000-01-01'), - EXTRACT(millennium FROM DATE '0001-01-01'), - EXTRACT(millennium FROM DATE '0001-01-01' - INTERVAL '1'SECOND), - EXTRACT(millennium FROM DATE '0001-01-01' - INTERVAL '1000 YEAR 1 SECOND') +SELECT EXTRACT(MILLENNIUM FROM DATE '2001-01-01'), + EXTRACT(MILLENNIUM FROM DATE '2000-01-01'), + EXTRACT(MILLENNIUM FROM DATE '1999-01-01'), + EXTRACT(MILLENNIUM FROM DATE '1001-01-01'), + EXTRACT(MILLENNIUM FROM DATE '1000-01-01'), + EXTRACT(MILLENNIUM FROM DATE '0001-01-01'), + EXTRACT(MILLENNIUM FROM DATE '0001-01-01' - INTERVAL '1'SECOND), + EXTRACT(MILLENNIUM FROM DATE '0001-01-01' - INTERVAL '1000 YEAR 1 SECOND') ---- 3 2 2 2 1 1 -1 -2 query RRRRRRRR -SELECT EXTRACT(century FROM DATE '2001-01-01'), - EXTRACT(century FROM DATE '2000-01-01'), - EXTRACT(century FROM DATE '1999-01-01'), - EXTRACT(century FROM DATE '1001-01-01'), - EXTRACT(century FROM DATE '1000-01-01'), - EXTRACT(century FROM DATE '0001-01-01'), - EXTRACT(century FROM DATE '0001-01-01' - INTERVAL '1'SECOND), - EXTRACT(century FROM DATE '0001-01-01' - INTERVAL '100 YEAR 1 SECOND') +SELECT EXTRACT(CENTURY FROM DATE '2001-01-01'), + EXTRACT(CENTURY FROM DATE '2000-01-01'), + EXTRACT(CENTURY FROM DATE '1999-01-01'), + EXTRACT(CENTURY FROM DATE '1001-01-01'), + EXTRACT(CENTURY FROM DATE '1000-01-01'), + EXTRACT(CENTURY FROM DATE '0001-01-01'), + EXTRACT(CENTURY FROM DATE '0001-01-01' - INTERVAL '1'SECOND), + EXTRACT(CENTURY FROM DATE '0001-01-01' - INTERVAL '100 YEAR 1 SECOND') ---- 21 20 20 11 10 1 -1 -2 query RRRRRRRR -SELECT EXTRACT(decade FROM DATE '2001-01-01'), - EXTRACT(decade FROM DATE '2000-01-01'), - EXTRACT(decade FROM DATE '1999-01-01'), - EXTRACT(decade FROM DATE '0001-01-01'), - EXTRACT(decade FROM DATE '0001-01-01' - INTERVAL '1'SECOND), - EXTRACT(decade FROM DATE '0001-01-01' - INTERVAL '1 YEAR 1 SECOND'), - EXTRACT(decade FROM DATE '0001-01-01' - INTERVAL '10 YEAR 1 SECOND'), - EXTRACT(decade FROM DATE '0001-01-01' - INTERVAL '11 YEAR 1 SECOND') +SELECT EXTRACT(DECADE FROM DATE '2001-01-01'), + EXTRACT(DECADE FROM DATE '2000-01-01'), + EXTRACT(DECADE FROM DATE '1999-01-01'), + EXTRACT(DECADE FROM DATE '0001-01-01'), + EXTRACT(DECADE FROM DATE '0001-01-01' - INTERVAL '1'SECOND), + EXTRACT(DECADE FROM DATE '0001-01-01' - INTERVAL '1 YEAR 1 SECOND'), + EXTRACT(DECADE FROM DATE '0001-01-01' - INTERVAL '10 YEAR 1 SECOND'), + EXTRACT(DECADE FROM DATE '0001-01-01' - INTERVAL '11 YEAR 1 SECOND') ---- 200 200 199 0 0 -1 -1 -2 query RR -SELECT EXTRACT(week FROM DATE '2000-01-01'), EXTRACT(week FROM DATE '2000-01-08') +SELECT EXTRACT(WEEK FROM DATE '2000-01-01'), EXTRACT(WEEK FROM DATE '2000-01-08') ---- 52 1 query RR -SELECT EXTRACT(doy FROM DATE '2000-01-01'), EXTRACT(doy FROM DATE '2000-12-31') +SELECT EXTRACT(DOY FROM DATE '2000-01-01'), EXTRACT(DOY FROM DATE '2000-12-31') ---- 1 366 +query error unit 'hour' not supported for type date +SELECT EXTRACT(HOUR FROM DATE '2000-12-31') + +query error unit 'seconds' not supported for type date +SELECT EXTRACT(SECOND FROM DATE '2000-12-31') + +query error unit 'milliseconds' not supported for type date +SELECT EXTRACT(MILLISECOND FROM DATE '2000-12-31') + +query error unit 'microseconds' not supported for type date +SELECT EXTRACT(MICROSECOND FROM DATE '2000-12-31') + +query RRRR +SELECT date_part('HOUR', DATE '2000-12-31'), + date_part('SECOND', DATE '2000-12-31'), + date_part('MILLISECOND', DATE '2000-12-31'), + date_part('MICROSECOND', DATE '2000-12-31') +---- +0 0 0 0 + query RR SELECT EXTRACT(EPOCH from INTERVAL '-1' MINUTE), EXTRACT(MINUTE from INTERVAL '-1' MINUTE) ---- @@ -875,6 +969,21 @@ SELECT EXTRACT(MONTH from INTERVAL '-13'MONTH), EXTRACT(MONTH from INTERVAL '15' ---- -1 3 +query RRRRRRRRRRR +SELECT date_part('EPOCH', INTERVAL '-1' MINUTE), + date_part('MINUTE', INTERVAL '-1' MINUTE), + date_part('EPOCH', INTERVAL '1' YEAR), + date_part('EPOCH', INTERVAL '1' MONTH) * 12, + date_part('MILLISECOND', INTERVAL '72.345678'SECOND), + date_part('MICROSECOND', INTERVAL '72.345678'SECOND), + date_part('DECADE', INTERVAL '39'YEAR), + date_part('CENTURY', INTERVAL '399'YEAR), + date_part('MILLENNIUM', INTERVAL '3999'YEAR), + date_part('MONTH', INTERVAL '-13'MONTH), + date_part('MONTH', INTERVAL '15'MONTH) +---- +-60 -1 31557600 31104000 12345.678 12345678 3 3 3 -1 3 + query T SELECT to_char(TIMESTAMPTZ '1997-02-03 11:12:59.9', 'YYYY-MM-DD HH24:MI:SS.MS TZ') ---- diff --git a/test/sqllogictest/tpch.slt b/test/sqllogictest/tpch.slt index 598242b070916..601ead1757c1a 100644 --- a/test/sqllogictest/tpch.slt +++ b/test/sqllogictest/tpch.slt @@ -554,7 +554,7 @@ ORDER BY | | delta %0 %4.(#3) %3.(#1) %2.(#0) %1.(#0) %0.(#0) | Filter (#17 <= 1996-12-31), (#17 >= 1995-01-01), (((#41 = "FRANCE") && (#45 = "GERMANY")) || ((#41 = "GERMANY") && (#45 = "FRANCE"))) | Project (#12, #13, #17, #41, #45) -| Reduce group=(#3, #4, date_part_year_tstz(datetotstz(#2))) +| Reduce group=(#3, #4, extract_year_d(#2)) | | agg sum((#0 * (1 - #1))) Finish order_by=(#0 asc, #1 asc, #2 asc) limit=none offset=0 project=(#0..#3) @@ -647,7 +647,7 @@ ORDER BY | | delta %7 %5.(#2) %4.(#3) %3.(#1) %2.(#0) %0.(#0) %1.(#0) %6.(#0) | Filter (#58 = "AMERICA"), (#36 <= 1996-12-31), (#36 >= 1995-01-01), ("ECONOMY ANODIZED STEEL" = varchartostr(#4)) | Project (#21, #22, #36, #54) -| Reduce group=(date_part_year_tstz(datetotstz(#2))) +| Reduce group=(extract_year_d(#2)) | | agg sum(if (#3 = "BRAZIL") then {(#0 * (1 - #1))} else {0}) | | agg sum((#0 * (1 - #1))) | Map (#1 / #2) @@ -728,7 +728,7 @@ ORDER BY | | delta %5 %1.(#3) %2.(#2) %3.(#0, #1) %0.(#0) %4.(#0) | Filter "^.*green.*$" ~(varchartostr(#1)) | Project (#20..#22, #35, #41, #47) -| Reduce group=(#5, date_part_year_tstz(datetotstz(#4))) +| Reduce group=(#5, extract_year_d(#4)) | | agg sum(((#1 * (1 - #2)) - (#3 * #0))) Finish order_by=(#0 asc, #1 desc) limit=none offset=0 project=(#0..#2) diff --git a/test/testdrive/date_func.td b/test/testdrive/date_func.td index 9f0fc92d69c91..f3c6a8cb37fd7 100644 --- a/test/testdrive/date_func.td +++ b/test/testdrive/date_func.td @@ -89,8 +89,8 @@ contains:unit '' not recognized contains:unit '' not recognized -> SELECT EXTRACT('second' FROM CAST('2011-11-11' AS DATE)); -0 +! SELECT EXTRACT('second' FROM CAST('2011-11-11' AS DATE)); +contains:unit 'seconds' not supported for type date > SELECT EXTRACT('second' FROM CAST('2011-11-11' AS TIMESTAMP)); 0