From 618d0f73382252798809a0b628236f0de6854514 Mon Sep 17 00:00:00 2001 From: freejw Date: Thu, 31 Oct 2024 14:41:55 +0800 Subject: [PATCH 1/3] add date function --- src/query/ast/src/ast/expr.rs | 55 ++++++ src/query/ast/src/ast/format/syntax/expr.rs | 18 ++ src/query/ast/src/parser/expr.rs | 118 ++++++++++++- src/query/ast/src/parser/token.rs | 23 +++ .../ast/tests/it/testdata/expr-error.txt | 4 +- .../ast/tests/it/testdata/stmt-error.txt | 4 +- src/query/expression/src/utils/date_helper.rs | 146 ++++++++++++++++ src/query/functions/src/scalars/datetime.rs | 162 ++++-------------- .../functions/tests/it/scalars/datetime.rs | 27 +++ .../functions/tests/it/scalars/parser.rs | 46 +++++ .../tests/it/scalars/testdata/datetime.txt | 162 ++++++++++++++++++ .../it/scalars/testdata/function_list.txt | 72 ++++++++ .../sql/src/planner/semantic/type_check.rs | 63 +++++++ .../functions/02_0012_function_datetimes.test | 110 ++++++++++++ 14 files changed, 868 insertions(+), 142 deletions(-) diff --git a/src/query/ast/src/ast/expr.rs b/src/query/ast/src/ast/expr.rs index b6c0b40ed866..d19f00b2278b 100644 --- a/src/query/ast/src/ast/expr.rs +++ b/src/query/ast/src/ast/expr.rs @@ -229,6 +229,21 @@ pub enum Expr { unit: IntervalKind, date: Box, }, + LastDay { + span: Span, + unit: IntervalKind, + date: Box, + }, + PreviousDay { + span: Span, + unit: Weekday, + date: Box, + }, + NextDay { + span: Span, + unit: Weekday, + date: Box, + }, Hole { span: Span, name: String, @@ -269,6 +284,9 @@ impl Expr { | Expr::DateDiff { span, .. } | Expr::DateSub { span, .. } | Expr::DateTrunc { span, .. } + | Expr::LastDay { span, .. } + | Expr::PreviousDay { span, .. } + | Expr::NextDay { span, .. } | Expr::Hole { span, .. } => *span, } } @@ -411,6 +429,9 @@ impl Expr { .. } => merge_span(merge_span(*span, interval.whole_span()), date.whole_span()), Expr::DateTrunc { span, date, .. } => merge_span(*span, date.whole_span()), + Expr::LastDay { span, date, .. } => merge_span(*span, date.whole_span()), + Expr::PreviousDay { span, date, .. } => merge_span(*span, date.whole_span()), + Expr::NextDay { span, date, .. } => merge_span(*span, date.whole_span()), Expr::Hole { span, .. } => *span, } } @@ -734,6 +755,15 @@ impl Display for Expr { Expr::DateTrunc { unit, date, .. } => { write!(f, "DATE_TRUNC({unit}, {date})")?; } + Expr::LastDay { unit, date, .. } => { + write!(f, "LAST_DAY({date}, {unit})")?; + } + Expr::PreviousDay { unit, date, .. } => { + write!(f, "PREVIOUS_DAY({date}, {unit})")?; + } + Expr::NextDay { unit, date, .. } => { + write!(f, "NEXT_DAY({date}, {unit})")?; + } Expr::Hole { name, .. } => { write!(f, ":{name}")?; } @@ -750,6 +780,31 @@ impl Display for Expr { } } +#[derive(Debug, Copy, Clone, PartialEq, Eq, Drive, DriveMut)] +pub enum Weekday { + Sunday, + Monday, + Tuesday, + Wednesday, + Thursday, + Friday, + Saturday, +} + +impl Display for Weekday { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.write_str(match self { + Weekday::Sunday => "SUNDAY", + Weekday::Monday => "MONDAY", + Weekday::Tuesday => "TUESDAY", + Weekday::Wednesday => "WEDNESDAY", + Weekday::Thursday => "THURSDAY", + Weekday::Friday => "FRIDAY", + Weekday::Saturday => "SATURDAY", + }) + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, Drive, DriveMut)] pub enum IntervalKind { Year, diff --git a/src/query/ast/src/ast/format/syntax/expr.rs b/src/query/ast/src/ast/format/syntax/expr.rs index f0c6b97f20fa..cba0c8d2e786 100644 --- a/src/query/ast/src/ast/format/syntax/expr.rs +++ b/src/query/ast/src/ast/format/syntax/expr.rs @@ -440,6 +440,24 @@ pub(crate) fn pretty_expr(expr: Expr) -> RcDoc<'static> { .append(RcDoc::space()) .append(pretty_expr(*date)) .append(RcDoc::text(")")), + Expr::LastDay { unit, date, .. } => RcDoc::text("LAST_DAY(") + .append(pretty_expr(*date)) + .append(RcDoc::text(",")) + .append(RcDoc::space()) + .append(RcDoc::text(unit.to_string())) + .append(RcDoc::text(")")), + Expr::PreviousDay { unit, date, .. } => RcDoc::text("PREVIOUS_DAY(") + .append(pretty_expr(*date)) + .append(RcDoc::text(",")) + .append(RcDoc::space()) + .append(RcDoc::text(unit.to_string())) + .append(RcDoc::text(")")), + Expr::NextDay { unit, date, .. } => RcDoc::text("NEXT_DAY(") + .append(pretty_expr(*date)) + .append(RcDoc::text(",")) + .append(RcDoc::space()) + .append(RcDoc::text(unit.to_string())) + .append(RcDoc::text(")")), Expr::Hole { name, .. } => RcDoc::text(":").append(RcDoc::text(name.to_string())), } } diff --git a/src/query/ast/src/parser/expr.rs b/src/query/ast/src/parser/expr.rs index ad498d9b23eb..599748fcd497 100644 --- a/src/query/ast/src/parser/expr.rs +++ b/src/query/ast/src/parser/expr.rs @@ -310,6 +310,18 @@ pub enum ExprElement { unit: IntervalKind, date: Expr, }, + LastDay { + unit: IntervalKind, + date: Expr, + }, + PreviousDay { + unit: Weekday, + date: Expr, + }, + NextDay { + unit: Weekday, + date: Expr, + }, Hole { name: String, }, @@ -416,6 +428,9 @@ impl ExprElement { ExprElement::DateDiff { .. } => Affix::Nilfix, ExprElement::DateSub { .. } => Affix::Nilfix, ExprElement::DateTrunc { .. } => Affix::Nilfix, + ExprElement::LastDay { .. } => Affix::Nilfix, + ExprElement::PreviousDay { .. } => Affix::Nilfix, + ExprElement::NextDay { .. } => Affix::Nilfix, ExprElement::Hole { .. } => Affix::Nilfix, ExprElement::VariableAccess { .. } => Affix::Nilfix, } @@ -459,6 +474,9 @@ impl Expr { Expr::DateDiff { .. } => Affix::Nilfix, Expr::DateSub { .. } => Affix::Nilfix, Expr::DateTrunc { .. } => Affix::Nilfix, + Expr::LastDay { .. } => Affix::Nilfix, + Expr::PreviousDay { .. } => Affix::Nilfix, + Expr::NextDay { .. } => Affix::Nilfix, Expr::Hole { .. } => Affix::Nilfix, } } @@ -657,6 +675,21 @@ impl<'a, I: Iterator>> PrattParser for ExprP unit, date: Box::new(date), }, + ExprElement::LastDay { unit, date } => Expr::LastDay { + span: transform_span(elem.span.tokens), + unit, + date: Box::new(date), + }, + ExprElement::PreviousDay { unit, date } => Expr::PreviousDay { + span: transform_span(elem.span.tokens), + unit, + date: Box::new(date), + }, + ExprElement::NextDay { unit, date } => Expr::NextDay { + span: transform_span(elem.span.tokens), + unit, + date: Box::new(date), + }, ExprElement::Hole { name } => Expr::Hole { span: transform_span(elem.span.tokens), name, @@ -1222,6 +1255,27 @@ pub fn expr_element(i: Input) -> IResult> { |(_, _, unit, _, date, _)| ExprElement::DateTrunc { unit, date }, ); + let last_day = map( + rule! { + LAST_DAY ~ "(" ~ #subexpr(0) ~ "," ~ #interval_kind ~ ")" + }, + |(_, _, date, _, unit, _)| ExprElement::LastDay { unit, date }, + ); + + let previous_day = map( + rule! { + PREVIOUS_DAY ~ "(" ~ #subexpr(0) ~ "," ~ #weekday ~ ")" + }, + |(_, _, date, _, unit, _)| ExprElement::PreviousDay { unit, date }, + ); + + let next_day = map( + rule! { + NEXT_DAY ~ "(" ~ #subexpr(0) ~ "," ~ #weekday ~ ")" + }, + |(_, _, date, _, unit, _)| ExprElement::NextDay { unit, date }, + ); + let date_expr = map( rule! { DATE ~ #consumed(literal_string) @@ -1281,19 +1335,25 @@ pub fn expr_element(i: Input) -> IResult> { | #json_op : "" | #unary_op : "" | #cast : "`CAST(... AS ...)`" - | #date_add: "`DATE_ADD(..., ..., (YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE | SECOND | DOY | DOW))`" - | #date_diff: "`DATE_DIFF(..., ..., (YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE | SECOND | DOY | DOW))`" - | #date_sub: "`DATE_SUB(..., ..., (YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE | SECOND | DOY | DOW))`" - | #date_trunc: "`DATE_TRUNC((YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE | SECOND), ...)`" - | #date_expr: "`DATE `" - | #timestamp_expr: "`TIMESTAMP `" - | #interval: "`INTERVAL ... (YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE | SECOND | DOY | DOW)`" | #pg_cast : "`::`" - | #extract : "`EXTRACT((YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE | SECOND | WEEK) FROM ...)`" - | #date_part : "`DATE_PART((YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE | SECOND | WEEK), ...)`" | #position : "`POSITION(... IN ...)`" | #variable_access: "`$`" ), + rule! ( + #date_add : "`DATE_ADD(..., ..., (YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE | SECOND | DOY | DOW))`" + | #date_diff : "`DATE_DIFF(..., ..., (YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE | SECOND | DOY | DOW))`" + | #date_sub : "`DATE_SUB(..., ..., (YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE | SECOND | DOY | DOW))`" + | #date_trunc : "`DATE_TRUNC((YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE | SECOND), ...)`" + | #last_day : "`LAST_DAY(..., (YEAR | QUARTER | MONTH | WEEK)))`" + | #previous_day : "`PREVIOUS_DAY(..., (Sunday | Monday | Tuesday | Wednesday | Thursday | Friday | Saturday))`" + | #next_day : "`NEXT_DAY(..., (Sunday | Monday | Tuesday | Wednesday | Thursday | Friday | Saturday))`" + | #date_expr : "`DATE `" + | #timestamp_expr : "`TIMESTAMP `" + | #interval : "`INTERVAL ... (YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE | SECOND | DOY | DOW)`" + | #extract : "`EXTRACT((YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE | SECOND | WEEK) FROM ...)`" + | #date_part : "`DATE_PART((YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE | SECOND | WEEK), ...)`" + + ), rule!( #substring : "`SUBSTRING(... [FROM ...] [FOR ...])`" | #trim : "`TRIM(...)`" @@ -1692,6 +1752,46 @@ pub fn type_name(i: Input) -> IResult { )(i) } +pub fn weekday(i: Input) -> IResult { + alt(( + value(Weekday::Sunday, rule! { SUNDAY }), + value(Weekday::Monday, rule! { MONDAY }), + value(Weekday::Tuesday, rule! { TUESDAY }), + value(Weekday::Wednesday, rule! { WEDNESDAY }), + value(Weekday::Thursday, rule! { THURSDAY }), + value(Weekday::Friday, rule! { FRIDAY }), + value(Weekday::Saturday, rule! { SATURDAY }), + value( + Weekday::Sunday, + rule! { #literal_string_eq_ignore_case("SUNDAY") }, + ), + value( + Weekday::Monday, + rule! { #literal_string_eq_ignore_case("MONDAY") }, + ), + value( + Weekday::Tuesday, + rule! { #literal_string_eq_ignore_case("TUESDAY") }, + ), + value( + Weekday::Wednesday, + rule! { #literal_string_eq_ignore_case("WEDNESDAY") }, + ), + value( + Weekday::Thursday, + rule! { #literal_string_eq_ignore_case("THURSDAY") }, + ), + value( + Weekday::Friday, + rule! { #literal_string_eq_ignore_case("FRIDAY") }, + ), + value( + Weekday::Saturday, + rule! { #literal_string_eq_ignore_case("SATURDAY") }, + ), + ))(i) +} + pub fn interval_kind(i: Input) -> IResult { alt(( value(IntervalKind::Year, rule! { YEAR }), diff --git a/src/query/ast/src/parser/token.rs b/src/query/ast/src/parser/token.rs index 5905bdb1a4bb..657ca0ccf0c7 100644 --- a/src/query/ast/src/parser/token.rs +++ b/src/query/ast/src/parser/token.rs @@ -637,6 +637,8 @@ pub enum TokenKind { FORMATS, #[token("FRAGMENTS", ignore(ascii_case))] FRAGMENTS, + #[token("FRIDAY", ignore(ascii_case))] + FRIDAY, #[token("FROM", ignore(ascii_case))] FROM, #[token("FULL", ignore(ascii_case))] @@ -725,6 +727,8 @@ pub enum TokenKind { INTO, #[token("INVERTED", ignore(ascii_case))] INVERTED, + #[token("PREVIOUS_DAY", ignore(ascii_case))] + PREVIOUS_DAY, #[token("PROCEDURE", ignore(ascii_case))] PROCEDURE, #[token("PROCEDURES", ignore(ascii_case))] @@ -749,6 +753,8 @@ pub enum TokenKind { KEY, #[token("KILL", ignore(ascii_case))] KILL, + #[token("LAST_DAY", ignore(ascii_case))] + LAST_DAY, #[token("LATERAL", ignore(ascii_case))] LATERAL, #[token("LINEAR", ignore(ascii_case))] @@ -816,6 +822,8 @@ pub enum TokenKind { MATERIALIZED, #[token("MUST_CHANGE_PASSWORD", ignore(ascii_case))] MUST_CHANGE_PASSWORD, + #[token("NEXT_DAY", ignore(ascii_case))] + NEXT_DAY, #[token("NON_DISPLAY", ignore(ascii_case))] NON_DISPLAY, #[token("NATURAL", ignore(ascii_case))] @@ -1016,6 +1024,8 @@ pub enum TokenKind { OPTIMIZED, #[token("DECORRELATED", ignore(ascii_case))] DECORRELATED, + #[token("SATURDAY", ignore(ascii_case))] + SATURDAY, #[token("SCHEMA", ignore(ascii_case))] SCHEMA, #[token("SCHEMAS", ignore(ascii_case))] @@ -1062,6 +1072,8 @@ pub enum TokenKind { SIZE_LIMIT, #[token("MAX_FILES", ignore(ascii_case))] MAX_FILES, + #[token("MONDAY", ignore(ascii_case))] + MONDAY, #[token("SKIP_HEADER", ignore(ascii_case))] SKIP_HEADER, #[token("SMALLINT", ignore(ascii_case))] @@ -1138,6 +1150,8 @@ pub enum TokenKind { TENANT, #[token("THEN", ignore(ascii_case))] THEN, + #[token("THURSDAY", ignore(ascii_case))] + THURSDAY, #[token("TIMESTAMP", ignore(ascii_case))] TIMESTAMP, #[token("TIMEZONE_HOUR", ignore(ascii_case))] @@ -1166,6 +1180,8 @@ pub enum TokenKind { TRY_CAST, #[token("TSV", ignore(ascii_case))] TSV, + #[token("TUESDAY", ignore(ascii_case))] + TUESDAY, #[token("TUPLE", ignore(ascii_case))] TUPLE, #[token("TYPE", ignore(ascii_case))] @@ -1314,6 +1330,8 @@ pub enum TokenKind { ENABLED, #[token("WEBHOOK", ignore(ascii_case))] WEBHOOK, + #[token("WEDNESDAY", ignore(ascii_case))] + WEDNESDAY, #[token("ERROR_INTEGRATION", ignore(ascii_case))] ERROR_INTEGRATION, #[token("AUTO_INGEST", ignore(ascii_case))] @@ -1354,6 +1372,8 @@ pub enum TokenKind { SOURCE, #[token("SQL", ignore(ascii_case))] SQL, + #[token("SUNDAY", ignore(ascii_case))] + SUNDAY, } // Reference: https://www.postgresql.org/docs/current/sql-keywords-appendix.html @@ -1593,6 +1613,9 @@ impl TokenKind { | TokenKind::DATE_DIFF | TokenKind::DATE_SUB | TokenKind::DATE_TRUNC + | TokenKind::LAST_DAY + | TokenKind::PREVIOUS_DAY + | TokenKind::NEXT_DAY | TokenKind::IGNORE_RESULT ) } diff --git a/src/query/ast/tests/it/testdata/expr-error.txt b/src/query/ast/tests/it/testdata/expr-error.txt index 925d9338ce1f..01717c62edfc 100644 --- a/src/query/ast/tests/it/testdata/expr-error.txt +++ b/src/query/ast/tests/it/testdata/expr-error.txt @@ -52,7 +52,7 @@ error: --> SQL:1:10 | 1 | CAST(col1) - | ---- ^ unexpected `)`, expecting `AS`, `,`, `(`, `IS`, `NOT`, `IN`, `EXISTS`, `BETWEEN`, `+`, `-`, `*`, `/`, `//`, `DIV`, `%`, `||`, `<->`, `>`, `<`, `>=`, `<=`, `=`, `<>`, `!=`, `^`, `AND`, `OR`, `XOR`, `LIKE`, `REGEXP`, `RLIKE`, `SOUNDS`, , , , , , `->`, `->>`, `#>`, `#>>`, `?`, `?|`, `?&`, `@>`, `<@`, `@?`, `@@`, `#-`, , , , , , `CAST`, `TRY_CAST`, `DATE_ADD`, `DATE_DIFF`, `DATE_SUB`, `DATE_TRUNC`, or 33 more ... + | ---- ^ unexpected `)`, expecting `AS`, `,`, `(`, `IS`, `NOT`, `IN`, `EXISTS`, `BETWEEN`, `+`, `-`, `*`, `/`, `//`, `DIV`, `%`, `||`, `<->`, `>`, `<`, `>=`, `<=`, `=`, `<>`, `!=`, `^`, `AND`, `OR`, `XOR`, `LIKE`, `REGEXP`, `RLIKE`, `SOUNDS`, , , , , , `->`, `->>`, `#>`, `#>>`, `?`, `?|`, `?&`, `@>`, `<@`, `@?`, `@@`, `#-`, , , , , , `CAST`, `TRY_CAST`, `::`, `POSITION`, `IdentVariable`, `DATE_ADD`, or 36 more ... | | | while parsing `CAST(... AS ...)` | while parsing expression @@ -81,7 +81,7 @@ error: 1 | $ abc + 3 | ^ | | - | unexpected `$`, expecting `IS`, `IN`, `EXISTS`, `BETWEEN`, `+`, `-`, `*`, `/`, `//`, `DIV`, `%`, `||`, `<->`, `>`, `<`, `>=`, `<=`, `=`, `<>`, `!=`, `^`, `AND`, `OR`, `XOR`, `LIKE`, `NOT`, `REGEXP`, `RLIKE`, `SOUNDS`, , , , , , `->`, `->>`, `#>`, `#>>`, `?`, `?|`, `?&`, `@>`, `<@`, `@?`, `@@`, `#-`, , , , , , `CAST`, `TRY_CAST`, `DATE_ADD`, `DATE_DIFF`, `DATE_SUB`, `DATE_TRUNC`, `DATE`, `TIMESTAMP`, `INTERVAL`, or 31 more ... + | unexpected `$`, expecting `IS`, `IN`, `EXISTS`, `BETWEEN`, `+`, `-`, `*`, `/`, `//`, `DIV`, `%`, `||`, `<->`, `>`, `<`, `>=`, `<=`, `=`, `<>`, `!=`, `^`, `AND`, `OR`, `XOR`, `LIKE`, `NOT`, `REGEXP`, `RLIKE`, `SOUNDS`, , , , , , `->`, `->>`, `#>`, `#>>`, `?`, `?|`, `?&`, `@>`, `<@`, `@?`, `@@`, `#-`, , , , , , `CAST`, `TRY_CAST`, `::`, `POSITION`, `IdentVariable`, `DATE_ADD`, `DATE_DIFF`, `DATE_SUB`, `DATE_TRUNC`, or 34 more ... | while parsing expression diff --git a/src/query/ast/tests/it/testdata/stmt-error.txt b/src/query/ast/tests/it/testdata/stmt-error.txt index 49cf494c9d72..b2f5720cd3c1 100644 --- a/src/query/ast/tests/it/testdata/stmt-error.txt +++ b/src/query/ast/tests/it/testdata/stmt-error.txt @@ -443,7 +443,7 @@ error: --> SQL:1:41 | 1 | SELECT * FROM t GROUP BY GROUPING SETS () - | ------ ^ unexpected `)`, expecting `(`, `IS`, `IN`, `EXISTS`, `BETWEEN`, `+`, `-`, `*`, `/`, `//`, `DIV`, `%`, `||`, `<->`, `>`, `<`, `>=`, `<=`, `=`, `<>`, `!=`, `^`, `AND`, `OR`, `XOR`, `LIKE`, `NOT`, `REGEXP`, `RLIKE`, `SOUNDS`, , , , , , `->`, `->>`, `#>`, `#>>`, `?`, `?|`, `?&`, `@>`, `<@`, `@?`, `@@`, `#-`, , , , , , `CAST`, `TRY_CAST`, `DATE_ADD`, `DATE_DIFF`, `DATE_SUB`, `DATE_TRUNC`, `DATE`, `TIMESTAMP`, or 31 more ... + | ------ ^ unexpected `)`, expecting `(`, `IS`, `IN`, `EXISTS`, `BETWEEN`, `+`, `-`, `*`, `/`, `//`, `DIV`, `%`, `||`, `<->`, `>`, `<`, `>=`, `<=`, `=`, `<>`, `!=`, `^`, `AND`, `OR`, `XOR`, `LIKE`, `NOT`, `REGEXP`, `RLIKE`, `SOUNDS`, , , , , , `->`, `->>`, `#>`, `#>>`, `?`, `?|`, `?&`, `@>`, `<@`, `@?`, `@@`, `#-`, , , , , , `CAST`, `TRY_CAST`, `::`, `POSITION`, `IdentVariable`, `DATE_ADD`, `DATE_DIFF`, `DATE_SUB`, or 34 more ... | | | while parsing `SELECT ...` @@ -865,7 +865,7 @@ error: --> SQL:1:65 | 1 | CREATE FUNCTION IF NOT EXISTS isnotempty AS(p) -> not(is_null(p) - | ------ -- ---- ^ unexpected end of input, expecting `)`, `IGNORE`, `RESPECT`, `OVER`, `(`, `IS`, `NOT`, `IN`, `EXISTS`, `BETWEEN`, `+`, `-`, `*`, `/`, `//`, `DIV`, `%`, `||`, `<->`, `>`, `<`, `>=`, `<=`, `=`, `<>`, `!=`, `^`, `AND`, `OR`, `XOR`, `LIKE`, `REGEXP`, `RLIKE`, `SOUNDS`, , , , , , `->`, `->>`, `#>`, `#>>`, `?`, `?|`, `?&`, `@>`, `<@`, `@?`, `@@`, `#-`, , , , , , `CAST`, `TRY_CAST`, `DATE_ADD`, `DATE_DIFF`, or 36 more ... + | ------ -- ---- ^ unexpected end of input, expecting `)`, `IGNORE`, `RESPECT`, `OVER`, `(`, `IS`, `NOT`, `IN`, `EXISTS`, `BETWEEN`, `+`, `-`, `*`, `/`, `//`, `DIV`, `%`, `||`, `<->`, `>`, `<`, `>=`, `<=`, `=`, `<>`, `!=`, `^`, `AND`, `OR`, `XOR`, `LIKE`, `REGEXP`, `RLIKE`, `SOUNDS`, , , , , , `->`, `->>`, `#>`, `#>>`, `?`, `?|`, `?&`, `@>`, `<@`, `@?`, `@@`, `#-`, , , , , , `CAST`, `TRY_CAST`, `::`, `POSITION`, or 39 more ... | | | | | | | | | while parsing `( [, ...])` | | | while parsing expression diff --git a/src/query/expression/src/utils/date_helper.rs b/src/query/expression/src/utils/date_helper.rs index 6991b5094395..0a16501063be 100644 --- a/src/query/expression/src/utils/date_helper.rs +++ b/src/query/expression/src/utils/date_helper.rs @@ -26,6 +26,7 @@ use chrono::Offset; use chrono::TimeZone; use chrono::Timelike; use chrono::Utc; +use chrono::Weekday; use chrono_tz::Tz; use databend_common_exception::ErrorCode; use databend_common_exception::Result; @@ -710,6 +711,24 @@ pub struct ToStartOfMonth; pub struct ToStartOfQuarter; pub struct ToStartOfYear; pub struct ToStartOfISOYear; +pub struct ToLastOfWeek; +pub struct ToLastOfMonth; +pub struct ToLastOfQuarter; +pub struct ToLastOfYear; +pub struct ToPreviousMonday; +pub struct ToPreviousTuesday; +pub struct ToPreviousWednesday; +pub struct ToPreviousThursday; +pub struct ToPreviousFriday; +pub struct ToPreviousSaturday; +pub struct ToPreviousSunday; +pub struct ToNextMonday; +pub struct ToNextTuesday; +pub struct ToNextWednesday; +pub struct ToNextThursday; +pub struct ToNextFriday; +pub struct ToNextSaturday; +pub struct ToNextSunday; impl ToNumber for ToLastMonday { fn to_number(dt: &DateTime) -> i32 { @@ -760,3 +779,130 @@ impl ToNumber for ToStartOfISOYear { datetime_to_date_inner_number(&iso_dt) } } + +impl ToNumber for ToLastOfWeek { + fn to_number(dt: &DateTime) -> i32 { + datetime_to_date_inner_number(dt) - dt.date_naive().weekday().num_days_from_monday() as i32 + + 6 + } +} + +impl ToNumber for ToLastOfMonth { + fn to_number(dt: &DateTime) -> i32 { + let day = last_day_of_year_month(dt.year(), dt.month()); + datetime_to_date_inner_number(&dt.with_day(day).unwrap()) + } +} + +impl ToNumber for ToLastOfQuarter { + fn to_number(dt: &DateTime) -> i32 { + let new_month = dt.month0() / 3 * 3 + 3; + let day = last_day_of_year_month(dt.year(), new_month); + datetime_to_date_inner_number(&dt.with_month(new_month).unwrap().with_day(day).unwrap()) + } +} + +impl ToNumber for ToLastOfYear { + fn to_number(dt: &DateTime) -> i32 { + let day = last_day_of_year_month(dt.year(), 12); + datetime_to_date_inner_number(&dt.with_month(12).unwrap().with_day(day).unwrap()) + } +} + +impl ToNumber for ToPreviousMonday { + fn to_number(dt: &DateTime) -> i32 { + previous_or_next_day(dt, Weekday::Mon, true) + } +} + +impl ToNumber for ToPreviousTuesday { + fn to_number(dt: &DateTime) -> i32 { + previous_or_next_day(dt, Weekday::Tue, true) + } +} + +impl ToNumber for ToPreviousWednesday { + fn to_number(dt: &DateTime) -> i32 { + previous_or_next_day(dt, Weekday::Wed, true) + } +} + +impl ToNumber for ToPreviousThursday { + fn to_number(dt: &DateTime) -> i32 { + previous_or_next_day(dt, Weekday::Thu, true) + } +} + +impl ToNumber for ToPreviousFriday { + fn to_number(dt: &DateTime) -> i32 { + previous_or_next_day(dt, Weekday::Fri, true) + } +} + +impl ToNumber for ToPreviousSaturday { + fn to_number(dt: &DateTime) -> i32 { + previous_or_next_day(dt, Weekday::Sat, true) + } +} + +impl ToNumber for ToPreviousSunday { + fn to_number(dt: &DateTime) -> i32 { + previous_or_next_day(dt, Weekday::Sun, true) + } +} + +impl ToNumber for ToNextMonday { + fn to_number(dt: &DateTime) -> i32 { + previous_or_next_day(dt, Weekday::Mon, false) + } +} + +impl ToNumber for ToNextTuesday { + fn to_number(dt: &DateTime) -> i32 { + previous_or_next_day(dt, Weekday::Tue, false) + } +} + +impl ToNumber for ToNextWednesday { + fn to_number(dt: &DateTime) -> i32 { + previous_or_next_day(dt, Weekday::Wed, false) + } +} + +impl ToNumber for ToNextThursday { + fn to_number(dt: &DateTime) -> i32 { + previous_or_next_day(dt, Weekday::Thu, false) + } +} + +impl ToNumber for ToNextFriday { + fn to_number(dt: &DateTime) -> i32 { + previous_or_next_day(dt, Weekday::Fri, false) + } +} + +impl ToNumber for ToNextSaturday { + fn to_number(dt: &DateTime) -> i32 { + previous_or_next_day(dt, Weekday::Sat, false) + } +} + +impl ToNumber for ToNextSunday { + fn to_number(dt: &DateTime) -> i32 { + previous_or_next_day(dt, Weekday::Sun, false) + } +} + +pub fn previous_or_next_day(dt: &DateTime, target: Weekday, is_previous: bool) -> i32 { + let dir = if is_previous { -1 } else { 1 }; + + let mut days_diff = (dir + * (target.num_days_from_monday() as i32 + - dt.date_naive().weekday().num_days_from_monday() as i32) + + 7) + % 7; + + days_diff = if days_diff == 0 { 7 } else { days_diff }; + + datetime_to_date_inner_number(dt) + dir * days_diff +} diff --git a/src/query/functions/src/scalars/datetime.rs b/src/query/functions/src/scalars/datetime.rs index b8755f33c10e..bf4caf79cd61 100644 --- a/src/query/functions/src/scalars/datetime.rs +++ b/src/query/functions/src/scalars/datetime.rs @@ -1733,55 +1733,31 @@ fn register_rounder_functions(registry: &mut FunctionRegistry) { ); // date | timestamp -> date - registry.register_passthrough_nullable_1_arg::( - "to_monday", - |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::(|val, output, ctx| { - match DateRounder::eval_date::( - val, - ctx.func_ctx.tz, - ctx.func_ctx.enable_dst_hour_fix, - ) { - Ok(t) => output.push(t), - Err(e) => { - ctx.set_error(output.len(), format!("cannot parse to type `Date`. {}", e)); - output.push(0); - } - } - }), - ); - registry.register_passthrough_nullable_1_arg::( - "to_monday", - |_, _| FunctionDomain::Full, - vectorize_1_arg::(|val, ctx| { - DateRounder::eval_timestamp::(val, ctx.func_ctx.tz) - }), - ); + rounder_functions_helper::(registry, "to_monday"); + rounder_functions_helper::(registry, "to_start_of_week"); + rounder_functions_helper::(registry, "to_start_of_month"); + rounder_functions_helper::(registry, "to_start_of_quarter"); + rounder_functions_helper::(registry, "to_start_of_year"); + rounder_functions_helper::(registry, "to_start_of_iso_year"); + rounder_functions_helper::(registry, "to_last_of_week"); + rounder_functions_helper::(registry, "to_last_of_month"); + rounder_functions_helper::(registry, "to_last_of_quarter"); + rounder_functions_helper::(registry, "to_last_of_year"); + rounder_functions_helper::(registry, "to_previous_monday"); + rounder_functions_helper::(registry, "to_previous_tuesday"); + rounder_functions_helper::(registry, "to_previous_wednesday"); + rounder_functions_helper::(registry, "to_previous_thursday"); + rounder_functions_helper::(registry, "to_previous_friday"); + rounder_functions_helper::(registry, "to_previous_saturday"); + rounder_functions_helper::(registry, "to_previous_sunday"); + rounder_functions_helper::(registry, "to_next_monday"); + rounder_functions_helper::(registry, "to_next_tuesday"); + rounder_functions_helper::(registry, "to_next_wednesday"); + rounder_functions_helper::(registry, "to_next_thursday"); + rounder_functions_helper::(registry, "to_next_friday"); + rounder_functions_helper::(registry, "to_next_saturday"); + rounder_functions_helper::(registry, "to_next_sunday"); - registry.register_passthrough_nullable_1_arg::( - "to_start_of_week", - |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::(|val, output, ctx| { - match DateRounder::eval_date::( - val, - ctx.func_ctx.tz, - ctx.func_ctx.enable_dst_hour_fix, - ) { - Ok(t) => output.push(t), - Err(e) => { - ctx.set_error(output.len(), format!("cannot parse to type `Date`. {}", e)); - output.push(0); - } - } - }), - ); - registry.register_passthrough_nullable_1_arg::( - "to_start_of_week", - |_, _| FunctionDomain::Full, - vectorize_1_arg::(|val, ctx| { - DateRounder::eval_timestamp::(val, ctx.func_ctx.tz) - }), - ); registry.register_passthrough_nullable_2_arg::( "to_start_of_week", |_, _, _| FunctionDomain::Full, @@ -1824,87 +1800,15 @@ fn register_rounder_functions(registry: &mut FunctionRegistry) { } }), ); +} +fn rounder_functions_helper(registry: &mut FunctionRegistry, name: &str) +where T: ToNumber { registry.register_passthrough_nullable_1_arg::( - "to_start_of_month", - |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::(|val, output, ctx| { - match DateRounder::eval_date::( - val, - ctx.func_ctx.tz, - ctx.func_ctx.enable_dst_hour_fix, - ) { - Ok(t) => output.push(t), - Err(e) => { - ctx.set_error(output.len(), format!("cannot parse to type `Date`. {}", e)); - output.push(0); - } - } - }), - ); - registry.register_passthrough_nullable_1_arg::( - "to_start_of_month", - |_, _| FunctionDomain::Full, - vectorize_1_arg::(|val, ctx| { - DateRounder::eval_timestamp::(val, ctx.func_ctx.tz) - }), - ); - - registry.register_passthrough_nullable_1_arg::( - "to_start_of_quarter", - |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::(|val, output, ctx| { - match DateRounder::eval_date::( - val, - ctx.func_ctx.tz, - ctx.func_ctx.enable_dst_hour_fix, - ) { - Ok(t) => output.push(t), - Err(e) => { - ctx.set_error(output.len(), format!("cannot parse to type `Date`. {}", e)); - output.push(0); - } - } - }), - ); - registry.register_passthrough_nullable_1_arg::( - "to_start_of_quarter", - |_, _| FunctionDomain::Full, - vectorize_1_arg::(|val, ctx| { - DateRounder::eval_timestamp::(val, ctx.func_ctx.tz) - }), - ); - - registry.register_passthrough_nullable_1_arg::( - "to_start_of_year", - |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::(|val, output, ctx| { - match DateRounder::eval_date::( - val, - ctx.func_ctx.tz, - ctx.func_ctx.enable_dst_hour_fix, - ) { - Ok(t) => output.push(t), - Err(e) => { - ctx.set_error(output.len(), format!("cannot parse to type `Date`. {}", e)); - output.push(0); - } - } - }), - ); - registry.register_passthrough_nullable_1_arg::( - "to_start_of_year", - |_, _| FunctionDomain::Full, - vectorize_1_arg::(|val, ctx| { - DateRounder::eval_timestamp::(val, ctx.func_ctx.tz) - }), - ); - - registry.register_passthrough_nullable_1_arg::( - "to_start_of_iso_year", + name, |_, _| FunctionDomain::Full, - vectorize_with_builder_1_arg::(|val, output, ctx| { - match DateRounder::eval_date::( + vectorize_with_builder_1_arg::(move |val, output, ctx| { + match DateRounder::eval_date::( val, ctx.func_ctx.tz, ctx.func_ctx.enable_dst_hour_fix, @@ -1918,10 +1822,10 @@ fn register_rounder_functions(registry: &mut FunctionRegistry) { }), ); registry.register_passthrough_nullable_1_arg::( - "to_start_of_iso_year", + name, |_, _| FunctionDomain::Full, - vectorize_1_arg::(|val, ctx| { - DateRounder::eval_timestamp::(val, ctx.func_ctx.tz) + vectorize_1_arg::(move |val, ctx| { + DateRounder::eval_timestamp::(val, ctx.func_ctx.tz) }), ); } diff --git a/src/query/functions/tests/it/scalars/datetime.rs b/src/query/functions/tests/it/scalars/datetime.rs index 783dc6b1f0ef..19c3f0870231 100644 --- a/src/query/functions/tests/it/scalars/datetime.rs +++ b/src/query/functions/tests/it/scalars/datetime.rs @@ -597,6 +597,33 @@ fn test_rounder_functions(file: &mut impl Write) { run_ast(file, "date_trunc(hour, to_timestamp(1630812366))", &[]); run_ast(file, "date_trunc(minute, to_timestamp(1630812366))", &[]); run_ast(file, "date_trunc(second, to_timestamp(1630812366))", &[]); + + run_ast(file, "last_day(to_timestamp(1630812366), year)", &[]); + run_ast(file, "last_day(to_timestamp(1630812366), quarter)", &[]); + run_ast(file, "last_day(to_timestamp(1630812366), month)", &[]); + run_ast(file, "last_day(to_timestamp(1630812366), week)", &[]); + + run_ast(file, "previous_day(to_timestamp(1630812366), monday)", &[]); + run_ast(file, "previous_day(to_timestamp(1630812366), tuesday)", &[]); + run_ast( + file, + "previous_day(to_timestamp(1630812366), wednesday)", + &[], + ); + run_ast(file, "previous_day(to_timestamp(1630812366), thursday)", &[ + ]); + run_ast(file, "previous_day(to_timestamp(1630812366), friday)", &[]); + run_ast(file, "previous_day(to_timestamp(1630812366), saturday)", &[ + ]); + run_ast(file, "previous_day(to_timestamp(1630812366), sunday)", &[]); + + run_ast(file, "next_day(to_timestamp(1630812366), monday)", &[]); + run_ast(file, "next_day(to_timestamp(1630812366), tuesday)", &[]); + run_ast(file, "next_day(to_timestamp(1630812366), wednesday)", &[]); + run_ast(file, "next_day(to_timestamp(1630812366), thursday)", &[]); + run_ast(file, "next_day(to_timestamp(1630812366), friday)", &[]); + run_ast(file, "next_day(to_timestamp(1630812366), saturday)", &[]); + run_ast(file, "next_day(to_timestamp(1630812366), sunday)", &[]); } fn test_date_date_diff(file: &mut impl Write) { diff --git a/src/query/functions/tests/it/scalars/parser.rs b/src/query/functions/tests/it/scalars/parser.rs index b1111bbdcc1a..238b4074942c 100644 --- a/src/query/functions/tests/it/scalars/parser.rs +++ b/src/query/functions/tests/it/scalars/parser.rs @@ -20,6 +20,7 @@ use databend_common_ast::ast::IntervalKind; use databend_common_ast::ast::Literal as ASTLiteral; use databend_common_ast::ast::MapAccessor; use databend_common_ast::ast::UnaryOperator; +use databend_common_ast::ast::Weekday; use databend_common_ast::parser::parse_expr; use databend_common_ast::parser::tokenize_sql; use databend_common_ast::parser::Dialect; @@ -56,6 +57,18 @@ macro_rules! with_interval_mapped_name { } } +macro_rules! with_weekday_mapped_name { + (| $t:tt | $($tail:tt)*) => { + match_template::match_template! { + $t = [ + Monday => "monday", Tuesday => "tuesday", Wednesday => "wednesday", Thursday => "thursday", Friday => "friday", + Saturday => "saturday", Sunday => "sunday", + ], + $($tail)* + } + } +} + macro_rules! transform_interval_add_sub { ($span: expr, $columns: expr, $op: expr, $unit: expr, $date: expr, $interval: expr) => { if $op == BinaryOperator::Plus { @@ -473,6 +486,39 @@ pub fn transform_expr(ast: AExpr, columns: &[(&str, DataType)]) -> RawExpr { } }) } + AExpr::LastDay { span, unit, date } => { + with_interval_mapped_name!(|INTERVAL| match unit { + IntervalKind::INTERVAL => RawExpr::FunctionCall { + span, + name: concat!("to_last_of_", INTERVAL).to_string(), + params: vec![], + args: vec![transform_expr(*date, columns),], + }, + kind => { + unimplemented!("{kind:?} is not supported") + } + }) + } + AExpr::PreviousDay { span, unit, date } => { + with_weekday_mapped_name!(|WEEKDAY| match unit { + Weekday::WEEKDAY => RawExpr::FunctionCall { + span, + name: concat!("to_previous_", WEEKDAY).to_string(), + params: vec![], + args: vec![transform_expr(*date, columns),], + }, + }) + } + AExpr::NextDay { span, unit, date } => { + with_weekday_mapped_name!(|WEEKDAY| match unit { + Weekday::WEEKDAY => RawExpr::FunctionCall { + span, + name: concat!("to_next_", WEEKDAY).to_string(), + params: vec![], + args: vec![transform_expr(*date, columns),], + }, + }) + } AExpr::InList { span, expr, diff --git a/src/query/functions/tests/it/scalars/testdata/datetime.txt b/src/query/functions/tests/it/scalars/testdata/datetime.txt index 673c15f75c74..90d727817217 100644 --- a/src/query/functions/tests/it/scalars/testdata/datetime.txt +++ b/src/query/functions/tests/it/scalars/testdata/datetime.txt @@ -3359,6 +3359,168 @@ output domain : {1630812366000000..=1630812366000000} output : '2021-09-05 03:26:06.000000' +ast : last_day(to_timestamp(1630812366), year) +raw expr : to_last_of_year(to_timestamp(1630812366)) +checked expr : to_last_of_year(to_timestamp(to_int64(1630812366_u32))) +optimized expr : 18992 +output type : Date +output domain : {18992..=18992} +output : '2021-12-31' + + +ast : last_day(to_timestamp(1630812366), quarter) +raw expr : to_last_of_quarter(to_timestamp(1630812366)) +checked expr : to_last_of_quarter(to_timestamp(to_int64(1630812366_u32))) +optimized expr : 18900 +output type : Date +output domain : {18900..=18900} +output : '2021-09-30' + + +ast : last_day(to_timestamp(1630812366), month) +raw expr : to_last_of_month(to_timestamp(1630812366)) +checked expr : to_last_of_month(to_timestamp(to_int64(1630812366_u32))) +optimized expr : 18900 +output type : Date +output domain : {18900..=18900} +output : '2021-09-30' + + +ast : last_day(to_timestamp(1630812366), week) +raw expr : to_last_of_week(to_timestamp(1630812366)) +checked expr : to_last_of_week(to_timestamp(to_int64(1630812366_u32))) +optimized expr : 18875 +output type : Date +output domain : {18875..=18875} +output : '2021-09-05' + + +ast : previous_day(to_timestamp(1630812366), monday) +raw expr : to_previous_monday(to_timestamp(1630812366)) +checked expr : to_previous_monday(to_timestamp(to_int64(1630812366_u32))) +optimized expr : 18869 +output type : Date +output domain : {18869..=18869} +output : '2021-08-30' + + +ast : previous_day(to_timestamp(1630812366), tuesday) +raw expr : to_previous_tuesday(to_timestamp(1630812366)) +checked expr : to_previous_tuesday(to_timestamp(to_int64(1630812366_u32))) +optimized expr : 18870 +output type : Date +output domain : {18870..=18870} +output : '2021-08-31' + + +ast : previous_day(to_timestamp(1630812366), wednesday) +raw expr : to_previous_wednesday(to_timestamp(1630812366)) +checked expr : to_previous_wednesday(to_timestamp(to_int64(1630812366_u32))) +optimized expr : 18871 +output type : Date +output domain : {18871..=18871} +output : '2021-09-01' + + +ast : previous_day(to_timestamp(1630812366), thursday) +raw expr : to_previous_thursday(to_timestamp(1630812366)) +checked expr : to_previous_thursday(to_timestamp(to_int64(1630812366_u32))) +optimized expr : 18872 +output type : Date +output domain : {18872..=18872} +output : '2021-09-02' + + +ast : previous_day(to_timestamp(1630812366), friday) +raw expr : to_previous_friday(to_timestamp(1630812366)) +checked expr : to_previous_friday(to_timestamp(to_int64(1630812366_u32))) +optimized expr : 18873 +output type : Date +output domain : {18873..=18873} +output : '2021-09-03' + + +ast : previous_day(to_timestamp(1630812366), saturday) +raw expr : to_previous_saturday(to_timestamp(1630812366)) +checked expr : to_previous_saturday(to_timestamp(to_int64(1630812366_u32))) +optimized expr : 18874 +output type : Date +output domain : {18874..=18874} +output : '2021-09-04' + + +ast : previous_day(to_timestamp(1630812366), sunday) +raw expr : to_previous_sunday(to_timestamp(1630812366)) +checked expr : to_previous_sunday(to_timestamp(to_int64(1630812366_u32))) +optimized expr : 18868 +output type : Date +output domain : {18868..=18868} +output : '2021-08-29' + + +ast : next_day(to_timestamp(1630812366), monday) +raw expr : to_next_monday(to_timestamp(1630812366)) +checked expr : to_next_monday(to_timestamp(to_int64(1630812366_u32))) +optimized expr : 18876 +output type : Date +output domain : {18876..=18876} +output : '2021-09-06' + + +ast : next_day(to_timestamp(1630812366), tuesday) +raw expr : to_next_tuesday(to_timestamp(1630812366)) +checked expr : to_next_tuesday(to_timestamp(to_int64(1630812366_u32))) +optimized expr : 18877 +output type : Date +output domain : {18877..=18877} +output : '2021-09-07' + + +ast : next_day(to_timestamp(1630812366), wednesday) +raw expr : to_next_wednesday(to_timestamp(1630812366)) +checked expr : to_next_wednesday(to_timestamp(to_int64(1630812366_u32))) +optimized expr : 18878 +output type : Date +output domain : {18878..=18878} +output : '2021-09-08' + + +ast : next_day(to_timestamp(1630812366), thursday) +raw expr : to_next_thursday(to_timestamp(1630812366)) +checked expr : to_next_thursday(to_timestamp(to_int64(1630812366_u32))) +optimized expr : 18879 +output type : Date +output domain : {18879..=18879} +output : '2021-09-09' + + +ast : next_day(to_timestamp(1630812366), friday) +raw expr : to_next_friday(to_timestamp(1630812366)) +checked expr : to_next_friday(to_timestamp(to_int64(1630812366_u32))) +optimized expr : 18880 +output type : Date +output domain : {18880..=18880} +output : '2021-09-10' + + +ast : next_day(to_timestamp(1630812366), saturday) +raw expr : to_next_saturday(to_timestamp(1630812366)) +checked expr : to_next_saturday(to_timestamp(to_int64(1630812366_u32))) +optimized expr : 18881 +output type : Date +output domain : {18881..=18881} +output : '2021-09-11' + + +ast : next_day(to_timestamp(1630812366), sunday) +raw expr : to_next_sunday(to_timestamp(1630812366)) +checked expr : to_next_sunday(to_timestamp(to_int64(1630812366_u32))) +optimized expr : 18882 +output type : Date +output domain : {18882..=18882} +output : '2021-09-12' + + ast : date_diff(year, to_date(0), to_date(10000)) raw expr : diff_years(to_date(10000), to_date(0)) checked expr : diff_years(to_date(to_int64(10000_u16)), to_date(to_int64(0_u8))) diff --git a/src/query/functions/tests/it/scalars/testdata/function_list.txt b/src/query/functions/tests/it/scalars/testdata/function_list.txt index 7d4cf1b16a97..067aad86342c 100644 --- a/src/query/functions/tests/it/scalars/testdata/function_list.txt +++ b/src/query/functions/tests/it/scalars/testdata/function_list.txt @@ -3980,6 +3980,22 @@ Functions overloads: 23 to_int8(Float64 NULL) :: Int8 NULL 24 to_int8(Boolean) :: Int8 25 to_int8(Boolean NULL) :: Int8 NULL +0 to_last_of_month(Date) :: Date +1 to_last_of_month(Date NULL) :: Date NULL +2 to_last_of_month(Timestamp) :: Date +3 to_last_of_month(Timestamp NULL) :: Date NULL +0 to_last_of_quarter(Date) :: Date +1 to_last_of_quarter(Date NULL) :: Date NULL +2 to_last_of_quarter(Timestamp) :: Date +3 to_last_of_quarter(Timestamp NULL) :: Date NULL +0 to_last_of_week(Date) :: Date +1 to_last_of_week(Date NULL) :: Date NULL +2 to_last_of_week(Timestamp) :: Date +3 to_last_of_week(Timestamp NULL) :: Date NULL +0 to_last_of_year(Date) :: Date +1 to_last_of_year(Date NULL) :: Date NULL +2 to_last_of_year(Timestamp) :: Date +3 to_last_of_year(Timestamp NULL) :: Date NULL 0 to_minute(Timestamp) :: UInt8 1 to_minute(Timestamp NULL) :: UInt8 NULL 0 to_monday(Date) :: Date @@ -3990,8 +4006,64 @@ Functions overloads: 1 to_month(Date NULL) :: UInt8 NULL 2 to_month(Timestamp) :: UInt8 3 to_month(Timestamp NULL) :: UInt8 NULL +0 to_next_friday(Date) :: Date +1 to_next_friday(Date NULL) :: Date NULL +2 to_next_friday(Timestamp) :: Date +3 to_next_friday(Timestamp NULL) :: Date NULL +0 to_next_monday(Date) :: Date +1 to_next_monday(Date NULL) :: Date NULL +2 to_next_monday(Timestamp) :: Date +3 to_next_monday(Timestamp NULL) :: Date NULL +0 to_next_saturday(Date) :: Date +1 to_next_saturday(Date NULL) :: Date NULL +2 to_next_saturday(Timestamp) :: Date +3 to_next_saturday(Timestamp NULL) :: Date NULL +0 to_next_sunday(Date) :: Date +1 to_next_sunday(Date NULL) :: Date NULL +2 to_next_sunday(Timestamp) :: Date +3 to_next_sunday(Timestamp NULL) :: Date NULL +0 to_next_thursday(Date) :: Date +1 to_next_thursday(Date NULL) :: Date NULL +2 to_next_thursday(Timestamp) :: Date +3 to_next_thursday(Timestamp NULL) :: Date NULL +0 to_next_tuesday(Date) :: Date +1 to_next_tuesday(Date NULL) :: Date NULL +2 to_next_tuesday(Timestamp) :: Date +3 to_next_tuesday(Timestamp NULL) :: Date NULL +0 to_next_wednesday(Date) :: Date +1 to_next_wednesday(Date NULL) :: Date NULL +2 to_next_wednesday(Timestamp) :: Date +3 to_next_wednesday(Timestamp NULL) :: Date NULL 0 to_nullable(NULL) :: NULL 1 to_nullable(T0 NULL) :: T0 NULL +0 to_previous_friday(Date) :: Date +1 to_previous_friday(Date NULL) :: Date NULL +2 to_previous_friday(Timestamp) :: Date +3 to_previous_friday(Timestamp NULL) :: Date NULL +0 to_previous_monday(Date) :: Date +1 to_previous_monday(Date NULL) :: Date NULL +2 to_previous_monday(Timestamp) :: Date +3 to_previous_monday(Timestamp NULL) :: Date NULL +0 to_previous_saturday(Date) :: Date +1 to_previous_saturday(Date NULL) :: Date NULL +2 to_previous_saturday(Timestamp) :: Date +3 to_previous_saturday(Timestamp NULL) :: Date NULL +0 to_previous_sunday(Date) :: Date +1 to_previous_sunday(Date NULL) :: Date NULL +2 to_previous_sunday(Timestamp) :: Date +3 to_previous_sunday(Timestamp NULL) :: Date NULL +0 to_previous_thursday(Date) :: Date +1 to_previous_thursday(Date NULL) :: Date NULL +2 to_previous_thursday(Timestamp) :: Date +3 to_previous_thursday(Timestamp NULL) :: Date NULL +0 to_previous_tuesday(Date) :: Date +1 to_previous_tuesday(Date NULL) :: Date NULL +2 to_previous_tuesday(Timestamp) :: Date +3 to_previous_tuesday(Timestamp NULL) :: Date NULL +0 to_previous_wednesday(Date) :: Date +1 to_previous_wednesday(Date NULL) :: Date NULL +2 to_previous_wednesday(Timestamp) :: Date +3 to_previous_wednesday(Timestamp NULL) :: Date NULL 0 to_quarter(Date) :: UInt8 1 to_quarter(Date NULL) :: UInt8 NULL 2 to_quarter(Timestamp) :: UInt8 diff --git a/src/query/sql/src/planner/semantic/type_check.rs b/src/query/sql/src/planner/semantic/type_check.rs index 43538c30792f..59d92d251b03 100644 --- a/src/query/sql/src/planner/semantic/type_check.rs +++ b/src/query/sql/src/planner/semantic/type_check.rs @@ -38,6 +38,7 @@ use databend_common_ast::ast::TrimWhere; use databend_common_ast::ast::TypeName; use databend_common_ast::ast::UnaryOperator; use databend_common_ast::ast::UriLocation; +use databend_common_ast::ast::Weekday as ASTWeekday; use databend_common_ast::ast::Window; use databend_common_ast::ast::WindowFrame; use databend_common_ast::ast::WindowFrameBound; @@ -1060,6 +1061,15 @@ impl<'a> TypeChecker<'a> { Expr::DateTrunc { span, unit, date, .. } => self.resolve_date_trunc(*span, date, unit)?, + Expr::LastDay { + span, unit, date, .. + } => self.resolve_last_day(*span, date, unit)?, + Expr::PreviousDay { + span, unit, date, .. + } => self.resolve_previous_or_next_day(*span, date, unit, true)?, + Expr::NextDay { + span, unit, date, .. + } => self.resolve_previous_or_next_day(*span, date, unit, false)?, Expr::Trim { span, expr, @@ -2981,6 +2991,59 @@ impl<'a> TypeChecker<'a> { } } + pub fn resolve_last_day( + &mut self, + span: Span, + date: &Expr, + kind: &ASTIntervalKind, + ) -> Result> { + match kind { + ASTIntervalKind::Year => { + self.resolve_function(span, "to_last_of_year", vec![], &[date]) + } + ASTIntervalKind::Quarter => { + self.resolve_function(span, "to_last_of_quarter", vec![], &[date]) + } + ASTIntervalKind::Month => { + self.resolve_function(span, "to_last_of_month", vec![], &[date]) + } + ASTIntervalKind::Week => { + self.resolve_function(span, "to_last_of_week", vec![], &[date]) + } + _ => Err(ErrorCode::SemanticError( + "Only these interval types are currently supported: [year, quarter, month, week]" + .to_string(), + ) + .set_span(span)), + } + } + + pub fn resolve_previous_or_next_day( + &mut self, + span: Span, + date: &Expr, + weekday: &ASTWeekday, + is_previous: bool, + ) -> Result> { + let prefix = if is_previous { + "to_previous_" + } else { + "to_next_" + }; + + let func_name = match weekday { + ASTWeekday::Monday => format!("{}monday", prefix), + ASTWeekday::Tuesday => format!("{}tuesday", prefix), + ASTWeekday::Wednesday => format!("{}wednesday", prefix), + ASTWeekday::Thursday => format!("{}thursday", prefix), + ASTWeekday::Friday => format!("{}friday", prefix), + ASTWeekday::Saturday => format!("{}saturday", prefix), + ASTWeekday::Sunday => format!("{}sunday", prefix), + }; + + self.resolve_function(span, &func_name, vec![], &[date]) + } + pub fn resolve_subquery( &mut self, typ: SubqueryType, diff --git a/tests/sqllogictests/suites/query/functions/02_0012_function_datetimes.test b/tests/sqllogictests/suites/query/functions/02_0012_function_datetimes.test index 74e047e658c2..ad9f1873ac4a 100644 --- a/tests/sqllogictests/suites/query/functions/02_0012_function_datetimes.test +++ b/tests/sqllogictests/suites/query/functions/02_0012_function_datetimes.test @@ -1170,6 +1170,116 @@ select to_datetime('2022-04-01 06:50:20') < '2022-04-02 04:50:20' ---- 1 +query T +select last_day(to_date('2024-10-22'), week); +---- +2024-10-27 + +query T +select last_day(to_date('2024-10-27'), week); +---- +2024-10-27 + +query T +select last_day(to_date('2024-02-01'), month); +---- +2024-02-29 + +query T +select last_day(to_date('2024-02-01'), quarter); +---- +2024-03-31 + +query T +select last_day(to_date('2024-02-01'), year); +---- +2024-12-31 + +query T +select last_day(to_timestamp('2024-10-24 09:38:18.165575'), week); +---- +2024-10-27 + +query T +select last_day(to_timestamp('2024-10-27 09:38:18.165575'), week); +---- +2024-10-27 + +query T +select last_day(to_timestamp('2024-02-11 09:38:18.165575'), month); +---- +2024-02-29 + +query T +select last_day(to_timestamp('2024-02-11 09:38:18.165575'), quarter); +---- +2024-03-31 + +query T +select last_day(to_timestamp('2024-02-11 09:38:18.165575'), year); +---- +2024-12-31 + +query T +select previous_day(to_date('2024-10-25'), monday); +---- +2024-10-21 + +query T +select previous_day(to_date('2024-10-25'), friday); +---- +2024-10-18 + +query T +select previous_day(to_date('2024-10-25'), saturday); +---- +2024-10-19 + +query T +select previous_day(to_timestamp('2024-10-25 09:38:18.165575'), monday); +---- +2024-10-21 + +query T +select previous_day(to_timestamp('2024-10-25 09:38:18.165575'), friday); +---- +2024-10-18 + +query T +select previous_day(to_timestamp('2024-10-25 09:38:18.165575'), sunday); +---- +2024-10-20 + +query T +select next_day(to_date('2024-10-25'), monday); +---- +2024-10-28 + +query T +select next_day(to_date('2024-10-25'), friday); +---- +2024-11-01 + +query T +select next_day(to_date('2024-10-25'), saturday); +---- +2024-10-26 + +query T +select next_day(to_timestamp('2024-10-25 09:38:18.165575'), monday); +---- +2024-10-28 + +query T +select next_day(to_timestamp('2024-10-25 09:38:18.165575'), friday); +---- +2024-11-01 + +query T +select next_day(to_timestamp('2024-10-25 09:38:18.165575'), sunday); +---- +2024-10-27 + statement ok drop table if exists ts From 50d2835c7a3af27cf90dcad9a228e5d8f601e5bf Mon Sep 17 00:00:00 2001 From: freejw Date: Wed, 6 Nov 2024 14:37:03 +0800 Subject: [PATCH 2/3] add tests --- .../02_0012_function_datetimes_tz.test | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/tests/sqllogictests/suites/query/functions/02_0012_function_datetimes_tz.test b/tests/sqllogictests/suites/query/functions/02_0012_function_datetimes_tz.test index 9ccaa2e1a9e2..445a1139a9ae 100644 --- a/tests/sqllogictests/suites/query/functions/02_0012_function_datetimes_tz.test +++ b/tests/sqllogictests/suites/query/functions/02_0012_function_datetimes_tz.test @@ -21,6 +21,116 @@ select to_timestamp('2000-01-01 00:00:00') statement ok set timezone='Asia/Shanghai' +query T +select last_day(to_date('2024-10-22'), week); +---- +2024-10-27 + +query T +select last_day(to_date('2024-10-27'), week); +---- +2024-10-27 + +query T +select last_day(to_date('2024-02-01'), month); +---- +2024-02-29 + +query T +select last_day(to_date('2024-02-01'), quarter); +---- +2024-03-31 + +query T +select last_day(to_date('2024-02-01'), year); +---- +2024-12-31 + +query T +select last_day(to_timestamp('2024-10-24 09:38:18.165575'), week); +---- +2024-10-27 + +query T +select last_day(to_timestamp('2024-10-27 09:38:18.165575'), week); +---- +2024-10-27 + +query T +select last_day(to_timestamp('2024-02-11 09:38:18.165575'), month); +---- +2024-02-29 + +query T +select last_day(to_timestamp('2024-02-11 09:38:18.165575'), quarter); +---- +2024-03-31 + +query T +select last_day(to_timestamp('2024-02-11 09:38:18.165575'), year); +---- +2024-12-31 + +query T +select previous_day(to_date('2024-10-25'), monday); +---- +2024-10-21 + +query T +select previous_day(to_date('2024-10-25'), friday); +---- +2024-10-18 + +query T +select previous_day(to_date('2024-10-25'), saturday); +---- +2024-10-19 + +query T +select previous_day(to_timestamp('2024-10-25 09:38:18.165575'), monday); +---- +2024-10-21 + +query T +select previous_day(to_timestamp('2024-10-25 09:38:18.165575'), friday); +---- +2024-10-18 + +query T +select previous_day(to_timestamp('2024-10-25 09:38:18.165575'), sunday); +---- +2024-10-20 + +query T +select next_day(to_date('2024-10-25'), monday); +---- +2024-10-28 + +query T +select next_day(to_date('2024-10-25'), friday); +---- +2024-11-01 + +query T +select next_day(to_date('2024-10-25'), saturday); +---- +2024-10-26 + +query T +select next_day(to_timestamp('2024-10-25 09:38:18.165575'), monday); +---- +2024-10-28 + +query T +select next_day(to_timestamp('2024-10-25 09:38:18.165575'), friday); +---- +2024-11-01 + +query T +select next_day(to_timestamp('2024-10-25 09:38:18.165575'), sunday); +---- +2024-10-27 + query T select to_timestamp(1630320462000000) ---- From 2bfa5adba61ee5fe9e888285af00f928d69b6237 Mon Sep 17 00:00:00 2001 From: freejw Date: Thu, 7 Nov 2024 19:14:25 +0800 Subject: [PATCH 3/3] add tests --- .../02_0012_function_datetimes_tz.test | 77 ++++++++++++++++--- 1 file changed, 66 insertions(+), 11 deletions(-) diff --git a/tests/sqllogictests/suites/query/functions/02_0012_function_datetimes_tz.test b/tests/sqllogictests/suites/query/functions/02_0012_function_datetimes_tz.test index a302ac7da0b5..19c70da87074 100644 --- a/tests/sqllogictests/suites/query/functions/02_0012_function_datetimes_tz.test +++ b/tests/sqllogictests/suites/query/functions/02_0012_function_datetimes_tz.test @@ -47,27 +47,52 @@ select last_day(to_date('2024-02-01'), year); 2024-12-31 query T -select last_day(to_timestamp('2024-10-24 09:38:18.165575'), week); +select last_day(to_timestamp('2024-10-24 01:00:00'), week); ---- 2024-10-27 query T -select last_day(to_timestamp('2024-10-27 09:38:18.165575'), week); +select last_day(to_timestamp('2024-10-24 23:00:00'), week); ---- 2024-10-27 query T -select last_day(to_timestamp('2024-02-11 09:38:18.165575'), month); +select last_day(to_timestamp('2024-10-27 01:00:00'), week); +---- +2024-10-27 + +query T +select last_day(to_timestamp('2024-10-27 23:00:00'), week); +---- +2024-10-27 + +query T +select last_day(to_timestamp('2024-02-11 01:00:00'), month); +---- +2024-02-29 + +query T +select last_day(to_timestamp('2024-02-11 23:00:00'), month); ---- 2024-02-29 query T -select last_day(to_timestamp('2024-02-11 09:38:18.165575'), quarter); +select last_day(to_timestamp('2024-02-11 01:00:00'), quarter); +---- +2024-03-31 + +query T +select last_day(to_timestamp('2024-02-11 23:00:00'), quarter); ---- 2024-03-31 query T -select last_day(to_timestamp('2024-02-11 09:38:18.165575'), year); +select last_day(to_timestamp('2024-02-11 01:00:00'), year); +---- +2024-12-31 + +query T +select last_day(to_timestamp('2024-02-11 23:00:00'), year); ---- 2024-12-31 @@ -87,17 +112,32 @@ select previous_day(to_date('2024-10-25'), saturday); 2024-10-19 query T -select previous_day(to_timestamp('2024-10-25 09:38:18.165575'), monday); +select previous_day(to_timestamp('2024-10-25 01:00:00'), monday); +---- +2024-10-21 + +query T +select previous_day(to_timestamp('2024-10-25 23:00:00'), monday); ---- 2024-10-21 query T -select previous_day(to_timestamp('2024-10-25 09:38:18.165575'), friday); +select previous_day(to_timestamp('2024-10-25 01:00:00'), friday); +---- +2024-10-18 + +query T +select previous_day(to_timestamp('2024-10-25 23:00:00'), friday); ---- 2024-10-18 query T -select previous_day(to_timestamp('2024-10-25 09:38:18.165575'), sunday); +select previous_day(to_timestamp('2024-10-25 01:00:00'), sunday); +---- +2024-10-20 + +query T +select previous_day(to_timestamp('2024-10-25 23:00:00'), sunday); ---- 2024-10-20 @@ -117,17 +157,32 @@ select next_day(to_date('2024-10-25'), saturday); 2024-10-26 query T -select next_day(to_timestamp('2024-10-25 09:38:18.165575'), monday); +select next_day(to_timestamp('2024-10-25 01:00:00'), monday); +---- +2024-10-28 + +query T +select next_day(to_timestamp('2024-10-25 23:00:00'), monday); ---- 2024-10-28 query T -select next_day(to_timestamp('2024-10-25 09:38:18.165575'), friday); +select next_day(to_timestamp('2024-10-25 01:00:00'), friday); ---- 2024-11-01 query T -select next_day(to_timestamp('2024-10-25 09:38:18.165575'), sunday); +select next_day(to_timestamp('2024-10-25 23:00:00'), friday); +---- +2024-11-01 + +query T +select next_day(to_timestamp('2024-10-25 01:00:00'), sunday); +---- +2024-10-27 + +query T +select next_day(to_timestamp('2024-10-25 23:00:00'), sunday); ---- 2024-10-27