From 3a3e978b976baddd58f2b3dd3b59abfa13d9d564 Mon Sep 17 00:00:00 2001 From: Joseph Koshakow Date: Mon, 10 Jan 2022 18:49:27 -0500 Subject: [PATCH 01/16] Bring EXTRACT into alignment with PostgreSQL v14 Before this commit, EXTRACT was treated as an alias to date_part with slightly different syntax. Both of these functions returned a float data type. PostgreSQL v14 updated EXTRACT to return a numeric data type, which makes it compliant with the SQL standard. This commit updates the EXTRACT function so that it return a numeric data type so that it matches PostgreSQL. date_part still returns a float. Additionally PostgreSQL v14 implemented EXTRACT explicitly for the DATE data type. Previously DATEs were casted to TIMESTAMPs before extracting fields from them. This commit also explicitly implements EXTRACT for DATE types, so they aren't cast to a TIMESTAMP first. A consequence of this is that time related fields (e.g. SECOND) are now rejected when trying to EXTRACT a DATE type. However, The implementation for date_part still casts DATE types to TIMESTAMP types. This means that date_part does not reject time related fields for DATE types. This implementation matches PostgreSQL. The postgres commit that implements these changes can be found here: https://github.com/postgres/postgres/commit/a2da77cdb4661826482ebf2ddba1f953bc74afe4 This commit also implements extracting EPOCHs from TIME types, which wasn't previously implemented. Fixes #9853, #9870 --- doc/user/content/release-notes.md | 14 + doc/user/content/sql/functions/date_part.md | 75 ++++ doc/user/content/sql/functions/extract.md | 10 +- src/expr/src/scalar/func.rs | 430 ++++++++++++++------ src/expr/src/scalar/func/impls/interval.rs | 2 +- src/expr/src/scalar/mod.rs | 60 +++ src/repr/src/adt/datetime.rs | 2 +- src/repr/src/adt/interval.rs | 94 +++-- src/repr/src/adt/numeric.rs | 19 + src/sql-parser/src/parser.rs | 2 +- src/sql-parser/tests/sqlparser_common.rs | 2 +- src/sql-parser/tests/testdata/scalar | 149 ++++++- src/sql/src/func.rs | 7 + src/sql/src/plan/statement/scl.rs | 2 +- test/sqllogictest/chbench.slt | 6 +- test/sqllogictest/cockroach/extract.slt | 6 +- test/sqllogictest/dates-times.slt | 217 +++++++--- test/sqllogictest/tpch.slt | 6 +- 18 files changed, 846 insertions(+), 257 deletions(-) create mode 100644 doc/user/content/sql/functions/date_part.md diff --git a/doc/user/content/release-notes.md b/doc/user/content/release-notes.md index 564fc37087a34..1b07cf418fc7a 100644 --- a/doc/user/content/release-notes.md +++ b/doc/user/content/release-notes.md @@ -129,6 +129,20 @@ changes that have not yet been documented. Previous versions of Materialize would incorrectly return `0` in these cases. The new behavior matches PostgreSQL. +- **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. + - **Breaking change.** Disallow the string `'sNaN'` (in any casing) as a valid [`numeric`] value. 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..1cca358bf961e --- /dev/null +++ b/doc/user/content/sql/functions/date_part.md @@ -0,0 +1,75 @@ +--- +title: "date_part Function" +description: "Returns a specified time component from a time-based value" +menu: + main: + parent: 'sql-functions' +--- + +`date_part` is modeled on the traditional Ingres equivalent to the SQL-standard +function [`EXTRACT`](../extract). For 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 + +TODO: this svg doesn't exist +{{< 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') +AS sec_extr; +``` +```nofmt + sec_extr +---------- + 5.06 +``` + +### Extract century from date + +```sql +SELECT date_part('CENTURIES', DATE '2006-01-02') +AS sec_extr; +``` +```nofmt + sec_extr +---------- + 21 +``` diff --git a/doc/user/content/sql/functions/extract.md b/doc/user/content/sql/functions/extract.md index 1699eae17d84e..e8ac6faed822a 100644 --- a/doc/user/content/sql/functions/extract.md +++ b/doc/user/content/sql/functions/extract.md @@ -8,13 +8,15 @@ menu: `EXTRACT` returns some time component from a time-based value, such as the year from a Timestamp. +See [`date_part`](../date_part) for the traditional Ingres equivalent function. + ## Signatures {{< 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,7 +44,7 @@ decade | `DEC`, `DECS`, `DECADE`, `DECADES` ### Return value -`EXTRACT` returns a [`float`](../../types/float) value. +`EXTRACT` returns a [`numeric`](../../types/numeric) value. ## Examples diff --git a/src/expr/src/scalar/func.rs b/src/expr/src/scalar/func.rs index 6b73fc527357b..feb2d02b0fa33 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, LossyFrom, Numeric}; use repr::adt::regex::Regex; use repr::{strconv, ColumnName, ColumnType, Datum, DatumType, Row, RowArena, ScalarType}; @@ -1365,49 +1365,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: From + From + std::ops::Div + std::ops::Add, + { + T::from(self.hour() * 60 * 60 + self.minute() * 60) + self.extract_second::() + } + + fn extract_second(&self) -> T + where + T: From + From + std::ops::Div + std::ops::Add, + { + 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: From + From + std::ops::Div + std::ops::Add, + { + 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: From + From + std::ops::Div + std::ops::Add, + { + 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() + naive_date.timestamp_subsec_micros() as i64 } - 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; @@ -1431,56 +1482,15 @@ 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: From + + From + + LossyFrom + + std::ops::Div + + std::ops::Add, + { + T::lossy_from(self.timestamp()) + T::from(self.timestamp_subsec_micros()) / T::from(1e6) } fn truncate_microseconds(&self) -> Self { @@ -1715,32 +1725,50 @@ impl TimestampLike for chrono::DateTime { } } -fn date_part_interval<'a>(a: Datum<'a>, interval: Interval) -> Result, EvalError> { +fn extract_interval(a: Datum, interval: Interval) -> Result { let units = a.unwrap_str(); match units.parse() { - Ok(units) => date_part_interval_inner(units, interval), + Ok(units) => date_part_interval_inner::(units, interval), Err(_) => Err(EvalError::UnknownUnits(units.to_owned())), } } -fn date_part_interval_inner( +fn date_part_interval(a: Datum, interval: Interval) -> Result { + let units = a.unwrap_str(); + match units.parse() { + Ok(units) => date_part_interval_inner::(units, interval), + Err(_) => Err(EvalError::UnknownUnits(units.to_owned())), + } +} + +fn date_part_interval_inner( units: DateTimeUnits, interval: Interval, -) -> Result, EvalError> { +) -> Result, EvalError> +where + T: From + + From + + From + + LossyFrom + + std::ops::Mul + + std::ops::Div + + std::ops::Add + + Into>, +{ 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(T::from(interval.millennia()).into()), + DateTimeUnits::Century => Ok(T::from(interval.centuries()).into()), + DateTimeUnits::Decade => Ok(T::from(interval.decades()).into()), + DateTimeUnits::Year => Ok(T::from(interval.years()).into()), + DateTimeUnits::Quarter => Ok(T::from(interval.quarters()).into()), + DateTimeUnits::Month => Ok(T::from(interval.months()).into()), + DateTimeUnits::Day => Ok(T::lossy_from(interval.days()).into()), + DateTimeUnits::Hour => Ok(T::lossy_from(interval.hours()).into()), + DateTimeUnits::Minute => Ok(T::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 @@ -1755,40 +1783,56 @@ fn date_part_interval_inner( } } -fn date_part_time<'a, T>(a: Datum<'a>, time: T) -> Result, EvalError> +fn extract_time(a: Datum, time: T) -> Result +where + T: TimeLike, +{ + let units = a.unwrap_str(); + match units.parse() { + Ok(units) => date_part_time_inner::<_, Numeric>(units, time), + Err(_) => Err(EvalError::UnknownUnits(units.to_owned())), + } +} + +fn date_part_time(a: Datum, time: T) -> Result where T: TimeLike, { let units = a.unwrap_str(); match units.parse() { - Ok(units) => date_part_time_inner(units, time), + Ok(units) => date_part_time_inner::<_, f64>(units, time), 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, T, U>(units: DateTimeUnits, time: T) -> Result, EvalError> where T: TimeLike, + U: From + + From + + std::ops::Div + + std::ops::Add + + Into>, { 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(U::from(time.hour()).into()), + DateTimeUnits::Minute => Ok(U::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(), )), @@ -1801,39 +1845,103 @@ where } } -fn date_part_timestamp<'a, T>(a: Datum<'a>, ts: T) -> Result, EvalError> +fn extract_timestamp(a: Datum, ts: T) -> Result where T: TimestampLike, { let units = a.unwrap_str(); match units.parse() { - Ok(units) => date_part_timestamp_inner(units, ts), + Ok(units) => date_part_timestamp_inner::<_, Numeric>(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(a: Datum, ts: T) -> Result where T: TimestampLike, +{ + let units = a.unwrap_str(); + match units.parse() { + Ok(units) => date_part_timestamp_inner::<_, f64>(units, ts), + Err(_) => Err(EvalError::UnknownUnits(units.to_owned())), + } +} + +fn date_part_timestamp_inner<'a, T, U>(units: DateTimeUnits, ts: T) -> Result, EvalError> +where + T: TimestampLike, + U: From + + From + + From + + LossyFrom + + std::ops::Div + + std::ops::Add + + Into>, +{ + match units { + DateTimeUnits::Epoch => Ok(TimestampLike::extract_epoch::(&ts).into()), + DateTimeUnits::Millennium => Ok(U::from(ts.millennium()).into()), + DateTimeUnits::Century => Ok(U::from(ts.century()).into()), + DateTimeUnits::Decade => Ok(U::from(ts.decade()).into()), + DateTimeUnits::Year => Ok(U::from(ts.year()).into()), + DateTimeUnits::Quarter => Ok(U::from(ts.quarter()).into()), + DateTimeUnits::Week => Ok(U::from(ts.week()).into()), + DateTimeUnits::Month => Ok(U::from(ts.month()).into()), + DateTimeUnits::Day => Ok(U::from(ts.day()).into()), + DateTimeUnits::DayOfWeek => Ok(U::from(ts.day_of_week()).into()), + DateTimeUnits::DayOfYear => Ok(U::from(ts.ordinal()).into()), + DateTimeUnits::IsoDayOfWeek => Ok(U::from(ts.iso_day_of_week()).into()), + DateTimeUnits::Hour => Ok(U::from(ts.hour()).into()), + DateTimeUnits::Minute => Ok(U::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: Datum, date: T) -> Result +where + T: DateLike, +{ + let units = a.unwrap_str(); + match units.parse() { + Ok(units) => extract_date_inner(units, date), + Err(_) => Err(EvalError::UnknownUnits(units.to_owned())), + } +} + +fn extract_date_inner<'a, T>(units: DateTimeUnits, date: T) -> Result, EvalError> +where + T: DateLike, { 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(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 @@ -2147,6 +2255,11 @@ pub enum BinaryFunc { ToCharTimestampTz, DateBinTimestamp, DateBinTimestampTz, + ExtractInterval, + ExtractTime, + ExtractTimestamp, + ExtractTimestampTz, + ExtractDate, DatePartInterval, DatePartTime, DatePartTimestamp, @@ -2348,6 +2461,21 @@ impl BinaryFunc { DateTime::::from_utc(NaiveDateTime::from_timestamp(0, 0), Utc) )) } + BinaryFunc::ExtractInterval => { + eager!(|a, b: Datum| extract_interval(a, b.unwrap_interval())) + } + BinaryFunc::ExtractTime => { + eager!(|a, b: Datum| extract_time(a, b.unwrap_time())) + } + BinaryFunc::ExtractTimestamp => { + eager!(|a, b: Datum| extract_timestamp(a, b.unwrap_timestamp())) + } + BinaryFunc::ExtractTimestampTz => { + eager!(|a, b: Datum| extract_timestamp(a, b.unwrap_timestamptz())) + } + BinaryFunc::ExtractDate => { + eager!(|a, b: Datum| extract_date(a, b.unwrap_date())) + } BinaryFunc::DatePartInterval => { eager!(|a, b: Datum| date_part_interval(a, b.unwrap_interval())) } @@ -2523,6 +2651,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) } @@ -2804,6 +2936,11 @@ impl BinaryFunc { | ToCharTimestampTz | DateBinTimestamp | DateBinTimestampTz + | ExtractInterval + | ExtractTime + | ExtractTimestamp + | ExtractTimestampTz + | ExtractDate | DatePartInterval | DatePartTime | DatePartTimestamp @@ -2944,6 +3081,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"), @@ -3256,6 +3398,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), @@ -3632,10 +3779,25 @@ 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.unwrap_interval()) + } + ExtractTime(units) => date_part_time_inner::<_, Numeric>(*units, a.unwrap_time()), + 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.unwrap_date()), + DatePartInterval(units) => date_part_interval_inner::(*units, a.unwrap_interval()), + DatePartTime(units) => date_part_time_inner::<_, f64>(*units, a.unwrap_time()), + 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()), @@ -3853,6 +4015,12 @@ impl UnaryFunc { return_ty.default_embedded_value().nullable(false) } + ExtractInterval(_) + | ExtractTime(_) + | ExtractTimestamp(_) + | ExtractTimestampTz(_) + | ExtractDate(_) => ScalarType::Numeric { scale: None }.nullable(nullable), + DatePartInterval(_) | DatePartTime(_) | DatePartTimestamp(_) @@ -4075,6 +4243,11 @@ impl UnaryFunc { TimezoneTimestamp(_) => false, CastList1ToList2 { .. } | CastInPlace { .. } => false, JsonbTypeof | JsonbStripNulls | JsonbPretty | ListLength => false, + ExtractInterval(_) + | ExtractTime(_) + | ExtractTimestamp(_) + | ExtractTimestampTz(_) + | ExtractDate(_) => false, DatePartInterval(_) | DatePartTime(_) | DatePartTimestamp(_) @@ -4320,6 +4493,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 54646a8442cbc..8de6864db570f 100644 --- a/src/expr/src/scalar/mod.rs +++ b/src/expr/src/scalar/mod.rs @@ -374,6 +374,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 6fd929fd93f08..df07cdbe09c39 100644 --- a/src/repr/src/adt/datetime.rs +++ b/src/repr/src/adt/datetime.rs @@ -69,7 +69,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..5e6f3f0fe82ac 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::LossyFrom; /// 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: From + LossyFrom + std::ops::Div, + { + 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: From + LossyFrom + std::ops::Div, + { + 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: From + LossyFrom + std::ops::Div, + { + T::lossy_from((self.duration % 60_000_000_000) as i64) / T::from(1e3) } /// Computes the nanosecond part of the interval. @@ -249,11 +259,19 @@ 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: From + + From + + LossyFrom + + std::ops::Mul + + std::ops::Div + + std::ops::Add, + { + 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..3141b9d3269bb 100644 --- a/src/repr/src/adt/numeric.rs +++ b/src/repr/src/adt/numeric.rs @@ -616,3 +616,22 @@ pub fn extract_typ_mod(typ_mod: &[u64]) -> Result, anyhow::Error> { _ => unreachable!(), }) } + +/// Used to do potentially lossy value-to-value conversions while consuming the input value. Useful +/// for interoperability between Numeric and f64. +pub trait LossyFrom: Sized { + fn lossy_from(_: T) -> Self; +} + +impl LossyFrom for f64 { + fn lossy_from(i: i64) -> Self { + i as f64 + } +} + +impl LossyFrom for Numeric { + // This is not actually lossy but has to conform to the f64 impl + 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 d66c4e4c9ce3d..055c637abbdf5 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 04927252d5624..5612837d2da36 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 67efd19eeea9f..916534414bec3 100644 --- a/src/sql/src/func.rs +++ b/src/sql/src/func.rs @@ -1484,6 +1484,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) From d15534038d8391ba8a59052d8dec0353f82da9a1 Mon Sep 17 00:00:00 2001 From: Joseph Koshakow Date: Fri, 14 Jan 2022 11:23:37 -0500 Subject: [PATCH 02/16] Clean up generics with DecimalLike --- src/expr/src/scalar/func.rs | 39 +++++++++--------------------------- src/repr/src/adt/interval.rs | 15 +++++--------- src/repr/src/adt/numeric.rs | 28 +++++++++++++++++++------- 3 files changed, 35 insertions(+), 47 deletions(-) diff --git a/src/expr/src/scalar/func.rs b/src/expr/src/scalar/func.rs index feb2d02b0fa33..01d2d8429f73c 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, LossyFrom, Numeric}; +use repr::adt::numeric::{self, DecimalLike, Numeric}; use repr::adt::regex::Regex; use repr::{strconv, ColumnName, ColumnType, Datum, DatumType, Row, RowArena, ScalarType}; @@ -1369,14 +1369,14 @@ fn ascii<'a>(a: Datum<'a>) -> Datum<'a> { pub trait TimeLike: chrono::Timelike { fn extract_epoch(&self) -> T where - T: From + From + std::ops::Div + std::ops::Add, + T: DecimalLike, { T::from(self.hour() * 60 * 60 + self.minute() * 60) + self.extract_second::() } fn extract_second(&self) -> T where - T: From + From + std::ops::Div + std::ops::Add, + T: DecimalLike, { let s = T::from(self.second()); let ns = T::from(self.nanosecond()) / T::from(1e9); @@ -1385,7 +1385,7 @@ pub trait TimeLike: chrono::Timelike { fn extract_millisecond(&self) -> T where - T: From + From + std::ops::Div + std::ops::Add, + T: DecimalLike, { let s = T::from(self.second() * 1_000); let ns = T::from(self.nanosecond()) / T::from(1e6); @@ -1394,7 +1394,7 @@ pub trait TimeLike: chrono::Timelike { fn extract_microsecond(&self) -> T where - T: From + From + std::ops::Div + std::ops::Add, + T: DecimalLike, { let s = T::from(self.second() * 1_000_000); let ns = T::from(self.nanosecond()) / T::from(1e3); @@ -1484,11 +1484,7 @@ pub trait TimestampLike: fn extract_epoch(&self) -> T where - T: From - + From - + LossyFrom - + std::ops::Div - + std::ops::Add, + T: DecimalLike, { T::lossy_from(self.timestamp()) + T::from(self.timestamp_subsec_micros()) / T::from(1e6) } @@ -1746,14 +1742,7 @@ fn date_part_interval_inner( interval: Interval, ) -> Result, EvalError> where - T: From - + From - + From - + LossyFrom - + std::ops::Mul - + std::ops::Div - + std::ops::Add - + Into>, + T: DecimalLike + Into>, { match units { DateTimeUnits::Epoch => Ok(interval.as_seconds::().into()), @@ -1808,11 +1797,7 @@ where fn date_part_time_inner<'a, T, U>(units: DateTimeUnits, time: T) -> Result, EvalError> where T: TimeLike, - U: From - + From - + std::ops::Div - + std::ops::Add - + Into>, + U: DecimalLike + Into>, { match units { DateTimeUnits::Epoch => Ok(time.extract_epoch::().into()), @@ -1870,13 +1855,7 @@ where fn date_part_timestamp_inner<'a, T, U>(units: DateTimeUnits, ts: T) -> Result, EvalError> where T: TimestampLike, - U: From - + From - + From - + LossyFrom - + std::ops::Div - + std::ops::Add - + Into>, + U: DecimalLike + Into>, { match units { DateTimeUnits::Epoch => Ok(TimestampLike::extract_epoch::(&ts).into()), diff --git a/src/repr/src/adt/interval.rs b/src/repr/src/adt/interval.rs index 5e6f3f0fe82ac..d5dda63202c15 100644 --- a/src/repr/src/adt/interval.rs +++ b/src/repr/src/adt/interval.rs @@ -16,7 +16,7 @@ use anyhow::bail; use serde::{Deserialize, Serialize}; use crate::adt::datetime::DateTimeField; -use crate::adt::numeric::LossyFrom; +use crate::adt::numeric::DecimalLike; /// An interval of time meant to express SQL intervals. /// @@ -226,7 +226,7 @@ impl Interval { /// modulo 60.0. pub fn seconds(&self) -> T where - T: From + LossyFrom + std::ops::Div, + T: DecimalLike, { T::lossy_from((self.duration % 60_000_000_000) as i64) / T::from(1e9) } @@ -237,7 +237,7 @@ impl Interval { /// modulo 60.0. pub fn milliseconds(&self) -> T where - T: From + LossyFrom + std::ops::Div, + T: DecimalLike, { T::lossy_from((self.duration % 60_000_000_000) as i64) / T::from(1e6) } @@ -248,7 +248,7 @@ impl Interval { /// modulo 60.0. pub fn microseconds(&self) -> T where - T: From + LossyFrom + std::ops::Div, + T: DecimalLike, { T::lossy_from((self.duration % 60_000_000_000) as i64) / T::from(1e3) } @@ -261,12 +261,7 @@ impl Interval { /// Computes the total number of seconds in the interval. pub fn as_seconds(&self) -> T where - T: From - + From - + LossyFrom - + std::ops::Mul - + std::ops::Div - + std::ops::Add, + T: DecimalLike, { T::from(self.years() * 60 * 60 * 24) * T::from(365.25) + T::from(self.months() * 60 * 60 * 24 * 30) diff --git a/src/repr/src/adt/numeric.rs b/src/repr/src/adt/numeric.rs index 3141b9d3269bb..e0b1f545eaa2a 100644 --- a/src/repr/src/adt/numeric.rs +++ b/src/repr/src/adt/numeric.rs @@ -617,20 +617,34 @@ pub fn extract_typ_mod(typ_mod: &[u64]) -> Result, anyhow::Error> { }) } -/// Used to do potentially lossy value-to-value conversions while consuming the input value. Useful -/// for interoperability between Numeric and f64. -pub trait LossyFrom: Sized { - fn lossy_from(_: T) -> Self; +/// 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 LossyFrom for f64 { +impl DecimalLike for f64 { fn lossy_from(i: i64) -> Self { i as f64 } } -impl LossyFrom for Numeric { - // This is not actually lossy but has to conform to the f64 impl +impl DecimalLike for Numeric { fn lossy_from(i: i64) -> Self { Numeric::from(i) } From c4cced3359a3ce0c95c21ee15cb9a6028999dd1f Mon Sep 17 00:00:00 2001 From: Joseph Koshakow Date: Sat, 15 Jan 2022 12:41:57 -0500 Subject: [PATCH 03/16] Remove unnecessary nanoseconds from DateLike epoch --- src/expr/src/scalar/func.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/expr/src/scalar/func.rs b/src/expr/src/scalar/func.rs index 01d2d8429f73c..45b72eec110d0 100644 --- a/src/expr/src/scalar/func.rs +++ b/src/expr/src/scalar/func.rs @@ -1409,7 +1409,7 @@ 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() + naive_date.timestamp_subsec_micros() as i64 + naive_date.timestamp() } fn millennium(&self) -> i32 { From a43a0dde7d1d84980a83d6c01114753379b61d97 Mon Sep 17 00:00:00 2001 From: Joseph Koshakow Date: Mon, 24 Jan 2022 13:15:01 -0500 Subject: [PATCH 04/16] Fix user docs --- .../content/sql/functions/{date_part.md => date-part.md} | 1 - doc/user/content/sql/functions/extract.md | 2 +- doc/user/data/sql_funcs.yml | 6 +++++- doc/user/sql-grammar/sql-grammar.bnf | 2 ++ 4 files changed, 8 insertions(+), 3 deletions(-) rename doc/user/content/sql/functions/{date_part.md => date-part.md} (98%) diff --git a/doc/user/content/sql/functions/date_part.md b/doc/user/content/sql/functions/date-part.md similarity index 98% rename from doc/user/content/sql/functions/date_part.md rename to doc/user/content/sql/functions/date-part.md index 1cca358bf961e..ddadbe3f9194c 100644 --- a/doc/user/content/sql/functions/date_part.md +++ b/doc/user/content/sql/functions/date-part.md @@ -13,7 +13,6 @@ function [`EXTRACT`](../extract). For PostgreSQL compatibility, `date_part` retu ## Signatures -TODO: this svg doesn't exist {{< diagram "func-date-part.svg" >}} Parameter | Type | Description diff --git a/doc/user/content/sql/functions/extract.md b/doc/user/content/sql/functions/extract.md index e8ac6faed822a..1fe7306e014c5 100644 --- a/doc/user/content/sql/functions/extract.md +++ b/doc/user/content/sql/functions/extract.md @@ -8,7 +8,7 @@ menu: `EXTRACT` returns some time component from a time-based value, such as the year from a Timestamp. -See [`date_part`](../date_part) for the traditional Ingres equivalent function. +See [`date_part`](../date-part) for the traditional Ingres equivalent function. ## Signatures diff --git a/doc/user/data/sql_funcs.yml b/doc/user/data/sql_funcs.yml index 57dd863ad7159..b46e660e66759 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/sql-grammar/sql-grammar.bnf b/doc/user/sql-grammar/sql-grammar.bnf index 07202b7f90c1b..057e60a6a234f 100644 --- a/doc/user/sql-grammar/sql-grammar.bnf +++ b/doc/user/sql-grammar/sql-grammar.bnf @@ -345,6 +345,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 ::= From 92b92135182acb36a4a73a822248f9392438ed2b Mon Sep 17 00:00:00 2001 From: Joseph Koshakow Date: Tue, 25 Jan 2022 12:24:52 -0500 Subject: [PATCH 05/16] Remove mention of Ingres and date_part from extract docs --- doc/user/content/sql/functions/extract.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/doc/user/content/sql/functions/extract.md b/doc/user/content/sql/functions/extract.md index 1fe7306e014c5..03cf6f74cdc68 100644 --- a/doc/user/content/sql/functions/extract.md +++ b/doc/user/content/sql/functions/extract.md @@ -8,8 +8,6 @@ menu: `EXTRACT` returns some time component from a time-based value, such as the year from a Timestamp. -See [`date_part`](../date-part) for the traditional Ingres equivalent function. - ## Signatures {{< diagram "func-extract.svg" >}} From d6b93f06d927604bcafa0e156547cc6df9a32c54 Mon Sep 17 00:00:00 2001 From: Joseph Koshakow Date: Tue, 25 Jan 2022 12:27:55 -0500 Subject: [PATCH 06/16] Hoist generics up a level --- src/expr/src/scalar/func.rs | 63 +++++++++++-------------------------- 1 file changed, 19 insertions(+), 44 deletions(-) diff --git a/src/expr/src/scalar/func.rs b/src/expr/src/scalar/func.rs index 331e1acdb8fac..84e47c12d46c2 100644 --- a/src/expr/src/scalar/func.rs +++ b/src/expr/src/scalar/func.rs @@ -1719,18 +1719,13 @@ impl TimestampLike for chrono::DateTime { } } -fn extract_interval(a: Datum, interval: Interval) -> Result { - let units = a.unwrap_str(); - match units.parse() { - Ok(units) => date_part_interval_inner::(units, interval), - Err(_) => Err(EvalError::UnknownUnits(units.to_owned())), - } -} - -fn date_part_interval(a: Datum, interval: Interval) -> Result { +fn date_part_interval<'a, T>(a: Datum<'a>, interval: Interval) -> Result, EvalError> +where + T: 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, interval), Err(_) => Err(EvalError::UnknownUnits(units.to_owned())), } } @@ -1770,24 +1765,14 @@ where } } -fn extract_time(a: Datum, time: T) -> Result -where - T: TimeLike, -{ - let units = a.unwrap_str(); - match units.parse() { - Ok(units) => date_part_time_inner::<_, Numeric>(units, time), - Err(_) => Err(EvalError::UnknownUnits(units.to_owned())), - } -} - -fn date_part_time(a: Datum, time: T) -> Result +fn date_part_time<'a, T, U>(a: Datum<'a>, time: T) -> Result, EvalError> where T: TimeLike, + U: DecimalLike + Into>, { let units = a.unwrap_str(); match units.parse() { - Ok(units) => date_part_time_inner::<_, f64>(units, time), + Ok(units) => date_part_time_inner::<_, U>(units, time), Err(_) => Err(EvalError::UnknownUnits(units.to_owned())), } } @@ -1828,24 +1813,14 @@ where } } -fn extract_timestamp(a: Datum, ts: T) -> Result -where - T: TimestampLike, -{ - let units = a.unwrap_str(); - match units.parse() { - Ok(units) => date_part_timestamp_inner::<_, Numeric>(units, ts), - Err(_) => Err(EvalError::UnknownUnits(units.to_owned())), - } -} - -fn date_part_timestamp(a: Datum, ts: T) -> Result +fn date_part_timestamp<'a, T, U>(a: Datum<'a>, ts: T) -> Result, EvalError> where T: TimestampLike, + U: DecimalLike + Into>, { let units = a.unwrap_str(); match units.parse() { - Ok(units) => date_part_timestamp_inner::<_, f64>(units, ts), + Ok(units) => date_part_timestamp_inner::<_, U>(units, ts), Err(_) => Err(EvalError::UnknownUnits(units.to_owned())), } } @@ -2437,31 +2412,31 @@ impl BinaryFunc { )) } BinaryFunc::ExtractInterval => { - eager!(|a, b: Datum| extract_interval(a, b.unwrap_interval())) + eager!(|a, b: Datum| date_part_interval::(a, b.unwrap_interval())) } BinaryFunc::ExtractTime => { - eager!(|a, b: Datum| extract_time(a, b.unwrap_time())) + eager!(|a, b: Datum| date_part_time::<_, Numeric>(a, b.unwrap_time())) } BinaryFunc::ExtractTimestamp => { - eager!(|a, b: Datum| extract_timestamp(a, b.unwrap_timestamp())) + eager!(|a, b: Datum| date_part_timestamp::<_, Numeric>(a, b.unwrap_timestamp())) } BinaryFunc::ExtractTimestampTz => { - eager!(|a, b: Datum| extract_timestamp(a, b.unwrap_timestamptz())) + eager!(|a, b: Datum| date_part_timestamp::<_, Numeric>(a, b.unwrap_timestamptz())) } BinaryFunc::ExtractDate => { eager!(|a, b: Datum| extract_date(a, b.unwrap_date())) } BinaryFunc::DatePartInterval => { - eager!(|a, b: Datum| date_part_interval(a, b.unwrap_interval())) + eager!(|a, b: Datum| date_part_interval::(a, b.unwrap_interval())) } BinaryFunc::DatePartTime => { - eager!(|a, b: Datum| date_part_time(a, b.unwrap_time())) + eager!(|a, b: Datum| date_part_time::<_, f64>(a, b.unwrap_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())) From e5f434532356691bb803679a417e5c1dc15d44d6 Mon Sep 17 00:00:00 2001 From: Joseph Koshakow Date: Tue, 25 Jan 2022 13:04:29 -0500 Subject: [PATCH 07/16] Pushed unwrapping Datums down where possible --- src/expr/src/scalar/func.rs | 146 ++++++++++++++++++------------------ 1 file changed, 71 insertions(+), 75 deletions(-) diff --git a/src/expr/src/scalar/func.rs b/src/expr/src/scalar/func.rs index 84e47c12d46c2..cfd49c064f6cc 100644 --- a/src/expr/src/scalar/func.rs +++ b/src/expr/src/scalar/func.rs @@ -1719,38 +1719,39 @@ impl TimestampLike for chrono::DateTime { } } -fn date_part_interval<'a, T>(a: Datum<'a>, interval: Interval) -> Result, EvalError> +fn date_part_interval<'a, D>(a: Datum<'a>, b: Datum<'a>) -> Result, EvalError> where - T: DecimalLike + Into>, + 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, + interval: Datum, ) -> Result, EvalError> where - T: DecimalLike + Into>, + D: DecimalLike + Into>, { + let interval = interval.unwrap_interval(); match units { - DateTimeUnits::Epoch => Ok(interval.as_seconds::().into()), - DateTimeUnits::Millennium => Ok(T::from(interval.millennia()).into()), - DateTimeUnits::Century => Ok(T::from(interval.centuries()).into()), - DateTimeUnits::Decade => Ok(T::from(interval.decades()).into()), - DateTimeUnits::Year => Ok(T::from(interval.years()).into()), - DateTimeUnits::Quarter => Ok(T::from(interval.quarters()).into()), - DateTimeUnits::Month => Ok(T::from(interval.months()).into()), - DateTimeUnits::Day => Ok(T::lossy_from(interval.days()).into()), - DateTimeUnits::Hour => Ok(T::lossy_from(interval.hours()).into()), - DateTimeUnits::Minute => Ok(T::lossy_from(interval.minutes()).into()), - DateTimeUnits::Second => Ok(interval.seconds::().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 @@ -1765,30 +1766,32 @@ where } } -fn date_part_time<'a, T, U>(a: Datum<'a>, time: T) -> Result, EvalError> +fn date_part_time<'a, D>(a: Datum<'a>, b: Datum<'a>) -> Result, EvalError> where - T: TimeLike, - U: DecimalLike + Into>, + D: DecimalLike + Into>, { let units = a.unwrap_str(); match units.parse() { - Ok(units) => date_part_time_inner::<_, U>(units, time), + Ok(units) => date_part_time_inner::(units, b), Err(_) => Err(EvalError::UnknownUnits(units.to_owned())), } } -fn date_part_time_inner<'a, T, U>(units: DateTimeUnits, time: T) -> Result, EvalError> +fn date_part_time_inner<'a, D>( + units: DateTimeUnits, + time: Datum<'a>, +) -> Result, EvalError> where - T: TimeLike, - U: DecimalLike + Into>, + D: DecimalLike + Into>, { + let time = time.unwrap_time(); match units { - DateTimeUnits::Epoch => Ok(time.extract_epoch::().into()), - DateTimeUnits::Hour => Ok(U::from(time.hour()).into()), - DateTimeUnits::Minute => Ok(U::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::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 @@ -1813,41 +1816,41 @@ where } } -fn date_part_timestamp<'a, T, U>(a: Datum<'a>, ts: T) -> Result, EvalError> +fn date_part_timestamp<'a, T, D>(a: Datum<'a>, ts: T) -> Result, EvalError> where T: TimestampLike, - U: DecimalLike + Into>, + D: DecimalLike + Into>, { let units = a.unwrap_str(); match units.parse() { - Ok(units) => date_part_timestamp_inner::<_, U>(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, U>(units: DateTimeUnits, ts: T) -> Result, EvalError> +fn date_part_timestamp_inner<'a, T, D>(units: DateTimeUnits, ts: T) -> Result, EvalError> where T: TimestampLike, - U: DecimalLike + Into>, + D: DecimalLike + Into>, { match units { - DateTimeUnits::Epoch => Ok(TimestampLike::extract_epoch::(&ts).into()), - DateTimeUnits::Millennium => Ok(U::from(ts.millennium()).into()), - DateTimeUnits::Century => Ok(U::from(ts.century()).into()), - DateTimeUnits::Decade => Ok(U::from(ts.decade()).into()), - DateTimeUnits::Year => Ok(U::from(ts.year()).into()), - DateTimeUnits::Quarter => Ok(U::from(ts.quarter()).into()), - DateTimeUnits::Week => Ok(U::from(ts.week()).into()), - DateTimeUnits::Month => Ok(U::from(ts.month()).into()), - DateTimeUnits::Day => Ok(U::from(ts.day()).into()), - DateTimeUnits::DayOfWeek => Ok(U::from(ts.day_of_week()).into()), - DateTimeUnits::DayOfYear => Ok(U::from(ts.ordinal()).into()), - DateTimeUnits::IsoDayOfWeek => Ok(U::from(ts.iso_day_of_week()).into()), - DateTimeUnits::Hour => Ok(U::from(ts.hour()).into()), - DateTimeUnits::Minute => Ok(U::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::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 @@ -1858,21 +1861,16 @@ where } } -fn extract_date(a: Datum, date: T) -> Result -where - T: DateLike, -{ +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, date), + Ok(units) => extract_date_inner(units, b), Err(_) => Err(EvalError::UnknownUnits(units.to_owned())), } } -fn extract_date_inner<'a, T>(units: DateTimeUnits, date: T) -> Result, EvalError> -where - T: DateLike, -{ +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()), @@ -2412,10 +2410,10 @@ impl BinaryFunc { )) } BinaryFunc::ExtractInterval => { - eager!(|a, b: Datum| date_part_interval::(a, b.unwrap_interval())) + eager!(|a, b: Datum<'a>| date_part_interval::(a, b)) } BinaryFunc::ExtractTime => { - eager!(|a, b: Datum| date_part_time::<_, Numeric>(a, b.unwrap_time())) + eager!(|a, b: Datum<'a>| date_part_time::(a, b)) } BinaryFunc::ExtractTimestamp => { eager!(|a, b: Datum| date_part_timestamp::<_, Numeric>(a, b.unwrap_timestamp())) @@ -2424,13 +2422,13 @@ impl BinaryFunc { eager!(|a, b: Datum| date_part_timestamp::<_, Numeric>(a, b.unwrap_timestamptz())) } BinaryFunc::ExtractDate => { - eager!(|a, b: Datum| extract_date(a, b.unwrap_date())) + eager!(|a, b: Datum<'a>| extract_date(a, b)) } BinaryFunc::DatePartInterval => { - eager!(|a, b: Datum| date_part_interval::(a, b.unwrap_interval())) + eager!(|a, b: Datum<'a>| date_part_interval::(a, b)) } BinaryFunc::DatePartTime => { - eager!(|a, b: Datum| date_part_time::<_, f64>(a, b.unwrap_time())) + eager!(|a, b: Datum<'a>| date_part_time::(a, b)) } BinaryFunc::DatePartTimestamp => { eager!(|a, b: Datum| date_part_timestamp::<_, f64>(a, b.unwrap_timestamp())) @@ -3727,19 +3725,17 @@ impl UnaryFunc { CharLength => char_length(a), IsRegexpMatch(regex) => Ok(is_regexp_match_static(a, ®ex)), RegexpMatch(regex) => regexp_match_static(a, temp_storage, ®ex), - ExtractInterval(units) => { - date_part_interval_inner::(*units, a.unwrap_interval()) - } - ExtractTime(units) => date_part_time_inner::<_, Numeric>(*units, a.unwrap_time()), + 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.unwrap_date()), - DatePartInterval(units) => date_part_interval_inner::(*units, a.unwrap_interval()), - DatePartTime(units) => date_part_time_inner::<_, f64>(*units, a.unwrap_time()), + 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()) } From 70317bd948ce28648893acd4aca3193b59df091d Mon Sep 17 00:00:00 2001 From: Joseph Koshakow Date: Tue, 25 Jan 2022 13:04:48 -0500 Subject: [PATCH 08/16] Remove Ingres from and SQL standard from date-part docs --- doc/user/content/sql/functions/date-part.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/user/content/sql/functions/date-part.md b/doc/user/content/sql/functions/date-part.md index ddadbe3f9194c..63d21c430f866 100644 --- a/doc/user/content/sql/functions/date-part.md +++ b/doc/user/content/sql/functions/date-part.md @@ -6,10 +6,10 @@ menu: parent: 'sql-functions' --- -`date_part` is modeled on the traditional Ingres equivalent to the SQL-standard -function [`EXTRACT`](../extract). For 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. +`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 From 3fc700dc771e9a7e68cf59318d41b7eb2a21aa34 Mon Sep 17 00:00:00 2001 From: Joseph Koshakow Date: Tue, 25 Jan 2022 14:30:14 -0500 Subject: [PATCH 09/16] Remove alias from docs --- doc/user/content/sql/functions/date-part.md | 18 ++++++++---------- doc/user/content/sql/functions/extract.md | 16 +++++++--------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/doc/user/content/sql/functions/date-part.md b/doc/user/content/sql/functions/date-part.md index 63d21c430f866..8305bc10eb88e 100644 --- a/doc/user/content/sql/functions/date-part.md +++ b/doc/user/content/sql/functions/date-part.md @@ -52,23 +52,21 @@ day of year | `DOY` ### Extract second from timestamptz ```sql -SELECT date_part('S', TIMESTAMP '2006-01-02 15:04:05.06') -AS sec_extr; +SELECT date_part('S', TIMESTAMP '2006-01-02 15:04:05.06'); ``` ```nofmt - sec_extr ----------- - 5.06 + date_part +----------- + 5.06 ``` ### Extract century from date ```sql -SELECT date_part('CENTURIES', DATE '2006-01-02') -AS sec_extr; +SELECT date_part('CENTURIES', DATE '2006-01-02'); ``` ```nofmt - sec_extr ----------- - 21 + date_part +----------- + 21 ``` diff --git a/doc/user/content/sql/functions/extract.md b/doc/user/content/sql/functions/extract.md index 03cf6f74cdc68..6a079b7710525 100644 --- a/doc/user/content/sql/functions/extract.md +++ b/doc/user/content/sql/functions/extract.md @@ -49,23 +49,21 @@ decade | `DEC`, `DECS`, `DECADE`, `DECADES` ### 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 ``` From bd496cd1e7b6a74d4908eb1d75e514dfc557acf8 Mon Sep 17 00:00:00 2001 From: Joseph Koshakow Date: Tue, 25 Jan 2022 14:55:51 -0500 Subject: [PATCH 10/16] Fix release notes --- doc/user/content/release-notes.md | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/doc/user/content/release-notes.md b/doc/user/content/release-notes.md index 7c40ee2429d22..585c054e8f434 100644 --- a/doc/user/content/release-notes.md +++ b/doc/user/content/release-notes.md @@ -118,6 +118,20 @@ changes that have not yet been documented. of `CREATE SOURCE` and `CREATE SINK` now reject unknown parameters. (Example: [Confluent Schema Registry options](/sql/create-source/avro-kafka#confluent-schema-registry-options)). +- **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. @@ -163,22 +177,6 @@ boundary don't silently merge their release notes into the wrong place. - Fix a bug in the `ILIKE` operator where matching against a `char` value did not take trailing spaces into account {{% gh 10076 %}}. The new behavior matches the behavior of the `LIKE` operator. -- **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. - Allow wildcards in `LIKE` patterns to match newline characters {{% gh 10077 %}}. The new behavior matches PostgreSQL. From 1ea3baa1c39bc3093f10a1040e0c12d74f6a85d0 Mon Sep 17 00:00:00 2001 From: Joseph Koshakow Date: Mon, 31 Jan 2022 12:52:24 -0500 Subject: [PATCH 11/16] Fix lint issues --- doc/user/content/sql/functions/date-part.md | 4 ++-- src/expr/src/scalar/func.rs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/user/content/sql/functions/date-part.md b/doc/user/content/sql/functions/date-part.md index 8305bc10eb88e..aabc2d29c762e 100644 --- a/doc/user/content/sql/functions/date-part.md +++ b/doc/user/content/sql/functions/date-part.md @@ -7,8 +7,8 @@ menu: --- `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 +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 diff --git a/src/expr/src/scalar/func.rs b/src/expr/src/scalar/func.rs index 9cc547037fb32..7cfe2a5399311 100644 --- a/src/expr/src/scalar/func.rs +++ b/src/expr/src/scalar/func.rs @@ -2411,10 +2411,10 @@ impl BinaryFunc { )) } BinaryFunc::ExtractInterval => { - eager!(|a, b: Datum<'a>| date_part_interval::(a, b)) + eager!(date_part_interval::) } BinaryFunc::ExtractTime => { - eager!(|a, b: Datum<'a>| date_part_time::(a, b)) + eager!(date_part_time::) } BinaryFunc::ExtractTimestamp => { eager!(|a, b: Datum| date_part_timestamp::<_, Numeric>(a, b.unwrap_timestamp())) @@ -2423,13 +2423,13 @@ impl BinaryFunc { eager!(|a, b: Datum| date_part_timestamp::<_, Numeric>(a, b.unwrap_timestamptz())) } BinaryFunc::ExtractDate => { - eager!(|a, b: Datum<'a>| extract_date(a, b)) + eager!(extract_date) } BinaryFunc::DatePartInterval => { - eager!(|a, b: Datum<'a>| date_part_interval::(a, b)) + eager!(date_part_interval::) } BinaryFunc::DatePartTime => { - eager!(|a, b: Datum<'a>| date_part_time::(a, b)) + eager!(date_part_time::) } BinaryFunc::DatePartTimestamp => { eager!(|a, b: Datum| date_part_timestamp::<_, f64>(a, b.unwrap_timestamp())) From bcbfec78c10c5b9119390d8bf2bfa690b1ac9c49 Mon Sep 17 00:00:00 2001 From: Joseph Koshakow Date: Mon, 31 Jan 2022 12:57:44 -0500 Subject: [PATCH 12/16] Add date-part diagram --- .../partials/sql-grammar/func-date-part.svg | 195 ++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 doc/user/layouts/partials/sql-grammar/func-date-part.svg 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 + + + ) + + + + From 7faaf6fbcb1bc347421bc44d11971e1a19b07fd1 Mon Sep 17 00:00:00 2001 From: Joseph Koshakow Date: Mon, 31 Jan 2022 13:01:11 -0500 Subject: [PATCH 13/16] Fix date-part test --- test/testdrive/date_func.td | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/testdrive/date_func.td b/test/testdrive/date_func.td index 9f0fc92d69c91..339edb0138179 100644 --- a/test/testdrive/date_func.td +++ b/test/testdrive/date_func.td @@ -90,7 +90,7 @@ contains:unit '' not recognized > SELECT EXTRACT('second' FROM CAST('2011-11-11' AS DATE)); -0 +contains:unit '' not recognized > SELECT EXTRACT('second' FROM CAST('2011-11-11' AS TIMESTAMP)); 0 From b9ad09ae061544235aa7872854f9af822d839666 Mon Sep 17 00:00:00 2001 From: Joseph Koshakow Date: Mon, 31 Jan 2022 13:05:30 -0500 Subject: [PATCH 14/16] Fix test error msg --- test/testdrive/date_func.td | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/testdrive/date_func.td b/test/testdrive/date_func.td index 339edb0138179..546b04f6d4767 100644 --- a/test/testdrive/date_func.td +++ b/test/testdrive/date_func.td @@ -90,7 +90,7 @@ contains:unit '' not recognized > SELECT EXTRACT('second' FROM CAST('2011-11-11' AS DATE)); -contains:unit '' not recognized +contains:unit 'seconds' not supported for type date > SELECT EXTRACT('second' FROM CAST('2011-11-11' AS TIMESTAMP)); 0 From 45b95cc5174103cfc580877f1902121936396885 Mon Sep 17 00:00:00 2001 From: Joseph Koshakow Date: Mon, 31 Jan 2022 15:54:52 -0500 Subject: [PATCH 15/16] Fix release notes link --- doc/user/content/release-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/content/release-notes.md b/doc/user/content/release-notes.md index 5e8a52dbcb7e6..bec4dfb499c2e 100644 --- a/doc/user/content/release-notes.md +++ b/doc/user/content/release-notes.md @@ -126,7 +126,7 @@ changes that have not yet been documented. 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, + [`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 010d9949b5c842963d3828443cda072163cdc5e6 Mon Sep 17 00:00:00 2001 From: Joseph Koshakow Date: Mon, 31 Jan 2022 16:29:13 -0500 Subject: [PATCH 16/16] Fix testdrive --- test/testdrive/date_func.td | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/testdrive/date_func.td b/test/testdrive/date_func.td index 546b04f6d4767..f3c6a8cb37fd7 100644 --- a/test/testdrive/date_func.td +++ b/test/testdrive/date_func.td @@ -89,7 +89,7 @@ contains:unit '' not recognized contains:unit '' not recognized -> SELECT EXTRACT('second' FROM CAST('2011-11-11' AS DATE)); +! 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));