Skip to content

Commit af7ac4c

Browse files
authored
feat(query): enhance datetime functions (#18027)
* feat(query): enhance datetime functions * 1. to_char as alias of to_string 2. delete TZM
1 parent e79627c commit af7ac4c

File tree

16 files changed

+259
-87
lines changed

16 files changed

+259
-87
lines changed

src/query/ast/src/parser/expr.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1214,6 +1214,13 @@ pub fn expr_element(i: Input) -> IResult<WithSpan<ExprElement>> {
12141214
|(_, _, unit, _, date, _)| ExprElement::DateTrunc { unit, date },
12151215
);
12161216

1217+
let trunc = map(
1218+
rule! {
1219+
TRUNC ~ "(" ~ #subexpr(0) ~ "," ~ #interval_kind ~ ")"
1220+
},
1221+
|(_, _, date, _, unit, _)| ExprElement::DateTrunc { unit, date },
1222+
);
1223+
12171224
let last_day = map(
12181225
rule! {
12191226
LAST_DAY ~ "(" ~ #subexpr(0) ~ ("," ~ #interval_kind)? ~ ")"
@@ -1326,7 +1333,8 @@ pub fn expr_element(i: Input) -> IResult<WithSpan<ExprElement>> {
13261333
| #date_diff : "`DATE_DIFF(..., ..., (YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE | SECOND | DOY | DOW))`"
13271334
| #date_sub : "`DATE_SUB(..., ..., (YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE | SECOND | DOY | DOW))`"
13281335
| #date_between : "`DATE_BETWEEN((YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE | SECOND | DOY | DOW), ..., ...,)`"
1329-
| #date_trunc : "`DATE_TRUNC((YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE | SECOND), ...)`"
1336+
| #date_trunc : "`DATE_TRUNC((YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE | SECOND | WEEK), ...)`"
1337+
| #trunc : "`TRUNC(..., (YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE | SECOND | WEEK))`"
13301338
| #last_day : "`LAST_DAY(..., (YEAR | QUARTER | MONTH | WEEK)))`"
13311339
| #previous_day : "`PREVIOUS_DAY(..., (Sunday | Monday | Tuesday | Wednesday | Thursday | Friday | Saturday))`"
13321340
| #next_day : "`NEXT_DAY(..., (Sunday | Monday | Tuesday | Wednesday | Thursday | Friday | Saturday))`"

src/query/ast/src/parser/token.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,8 @@ pub enum TokenKind {
517517
DATESUB,
518518
#[token("DATE_TRUNC", ignore(ascii_case))]
519519
DATE_TRUNC,
520+
#[token("TRUNC", ignore(ascii_case))]
521+
TRUNC,
520522
#[token("DATETIME", ignore(ascii_case))]
521523
DATETIME,
522524
#[token("DAY", ignore(ascii_case))]
@@ -1672,6 +1674,7 @@ impl TokenKind {
16721674
| TokenKind::DATE_SUB
16731675
| TokenKind::DATE_BETWEEN
16741676
| TokenKind::DATE_TRUNC
1677+
| TokenKind::TRUNC
16751678
| TokenKind::LAST_DAY
16761679
| TokenKind::PREVIOUS_DAY
16771680
| TokenKind::NEXT_DAY

src/query/ast/tests/it/parser.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1264,6 +1264,8 @@ fn test_expr() {
12641264
r#"extract(year from d)"#,
12651265
r#"date_part(year, d)"#,
12661266
r#"datepart(year, d)"#,
1267+
r#"date_trunc(week, to_timestamp(1630812366))"#,
1268+
r#"trunc(to_timestamp(1630812366), week)"#,
12671269
r#"DATEDIFF(SECOND, to_timestamp('2024-01-01 21:01:35.423179'), to_timestamp('2023-12-31 09:38:18.165575'))"#,
12681270
r#"last_day(to_date('2024-10-22'), week)"#,
12691271
r#"last_day(to_date('2024-10-22'))"#,

src/query/ast/tests/it/testdata/expr-error.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ error:
5252
--> SQL:1:10
5353
|
5454
1 | CAST(col1)
55-
| ---- ^ unexpected `)`, expecting `AS`, `,`, `(`, `IS`, `NOT`, `IN`, `EXISTS`, `BETWEEN`, `+`, `-`, `*`, `/`, `//`, `DIV`, `%`, `||`, `<->`, `>`, `<`, `>=`, `<=`, `=`, `<>`, `!=`, `^`, `AND`, `OR`, `XOR`, `LIKE`, `REGEXP`, `RLIKE`, `SOUNDS`, <BitWiseOr>, <BitWiseAnd>, <BitWiseXor>, <ShiftLeft>, <ShiftRight>, `->`, `->>`, `#>`, `#>>`, `?`, `?|`, `?&`, `@>`, `<@`, `@?`, `@@`, `#-`, <Factorial>, <SquareRoot>, <BitWiseNot>, <CubeRoot>, <Abs>, `CAST`, `TRY_CAST`, `::`, `POSITION`, `IdentVariable`, `DATE_ADD`, or 41 more ...
55+
| ---- ^ unexpected `)`, expecting `AS`, `,`, `(`, `IS`, `NOT`, `IN`, `EXISTS`, `BETWEEN`, `+`, `-`, `*`, `/`, `//`, `DIV`, `%`, `||`, `<->`, `>`, `<`, `>=`, `<=`, `=`, `<>`, `!=`, `^`, `AND`, `OR`, `XOR`, `LIKE`, `REGEXP`, `RLIKE`, `SOUNDS`, <BitWiseOr>, <BitWiseAnd>, <BitWiseXor>, <ShiftLeft>, <ShiftRight>, `->`, `->>`, `#>`, `#>>`, `?`, `?|`, `?&`, `@>`, `<@`, `@?`, `@@`, `#-`, <Factorial>, <SquareRoot>, <BitWiseNot>, <CubeRoot>, <Abs>, `CAST`, `TRY_CAST`, `::`, `POSITION`, `IdentVariable`, `DATE_ADD`, or 42 more ...
5656
| |
5757
| while parsing `CAST(... AS ...)`
5858
| while parsing expression
@@ -81,7 +81,7 @@ error:
8181
1 | $ abc + 3
8282
| ^
8383
| |
84-
| unexpected `$`, expecting `IS`, `IN`, `EXISTS`, `BETWEEN`, `+`, `-`, `*`, `/`, `//`, `DIV`, `%`, `||`, `<->`, `>`, `<`, `>=`, `<=`, `=`, `<>`, `!=`, `^`, `AND`, `OR`, `XOR`, `LIKE`, `NOT`, `REGEXP`, `RLIKE`, `SOUNDS`, <BitWiseOr>, <BitWiseAnd>, <BitWiseXor>, <ShiftLeft>, <ShiftRight>, `->`, `->>`, `#>`, `#>>`, `?`, `?|`, `?&`, `@>`, `<@`, `@?`, `@@`, `#-`, <Factorial>, <SquareRoot>, <BitWiseNot>, <CubeRoot>, <Abs>, `CAST`, `TRY_CAST`, `::`, `POSITION`, `IdentVariable`, `DATE_ADD`, `DATE_DIFF`, `DATEDIFF`, `DATESUB`, or 39 more ...
84+
| unexpected `$`, expecting `IS`, `IN`, `EXISTS`, `BETWEEN`, `+`, `-`, `*`, `/`, `//`, `DIV`, `%`, `||`, `<->`, `>`, `<`, `>=`, `<=`, `=`, `<>`, `!=`, `^`, `AND`, `OR`, `XOR`, `LIKE`, `NOT`, `REGEXP`, `RLIKE`, `SOUNDS`, <BitWiseOr>, <BitWiseAnd>, <BitWiseXor>, <ShiftLeft>, <ShiftRight>, `->`, `->>`, `#>`, `#>>`, `?`, `?|`, `?&`, `@>`, `<@`, `@?`, `@@`, `#-`, <Factorial>, <SquareRoot>, <BitWiseNot>, <CubeRoot>, <Abs>, `CAST`, `TRY_CAST`, `::`, `POSITION`, `IdentVariable`, `DATE_ADD`, `DATE_DIFF`, `DATEDIFF`, `DATESUB`, or 40 more ...
8585
| while parsing expression
8686

8787

src/query/ast/tests/it/testdata/expr.txt

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1842,6 +1842,92 @@ DatePart {
18421842
}
18431843

18441844

1845+
---------- Input ----------
1846+
date_trunc(week, to_timestamp(1630812366))
1847+
---------- Output ---------
1848+
DATE_TRUNC(WEEK, to_timestamp(1630812366))
1849+
---------- AST ------------
1850+
DateTrunc {
1851+
span: Some(
1852+
0..42,
1853+
),
1854+
unit: Week,
1855+
date: FunctionCall {
1856+
span: Some(
1857+
17..41,
1858+
),
1859+
func: FunctionCall {
1860+
distinct: false,
1861+
name: Identifier {
1862+
span: Some(
1863+
17..29,
1864+
),
1865+
name: "to_timestamp",
1866+
quote: None,
1867+
ident_type: None,
1868+
},
1869+
args: [
1870+
Literal {
1871+
span: Some(
1872+
30..40,
1873+
),
1874+
value: UInt64(
1875+
1630812366,
1876+
),
1877+
},
1878+
],
1879+
params: [],
1880+
order_by: [],
1881+
window: None,
1882+
lambda: None,
1883+
},
1884+
},
1885+
}
1886+
1887+
1888+
---------- Input ----------
1889+
trunc(to_timestamp(1630812366), week)
1890+
---------- Output ---------
1891+
DATE_TRUNC(WEEK, to_timestamp(1630812366))
1892+
---------- AST ------------
1893+
DateTrunc {
1894+
span: Some(
1895+
0..37,
1896+
),
1897+
unit: Week,
1898+
date: FunctionCall {
1899+
span: Some(
1900+
6..30,
1901+
),
1902+
func: FunctionCall {
1903+
distinct: false,
1904+
name: Identifier {
1905+
span: Some(
1906+
6..18,
1907+
),
1908+
name: "to_timestamp",
1909+
quote: None,
1910+
ident_type: None,
1911+
},
1912+
args: [
1913+
Literal {
1914+
span: Some(
1915+
19..29,
1916+
),
1917+
value: UInt64(
1918+
1630812366,
1919+
),
1920+
},
1921+
],
1922+
params: [],
1923+
order_by: [],
1924+
window: None,
1925+
lambda: None,
1926+
},
1927+
},
1928+
}
1929+
1930+
18451931
---------- Input ----------
18461932
DATEDIFF(SECOND, to_timestamp('2024-01-01 21:01:35.423179'), to_timestamp('2023-12-31 09:38:18.165575'))
18471933
---------- Output ---------

src/query/ast/tests/it/testdata/stmt-error.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -560,7 +560,7 @@ error:
560560
--> SQL:1:41
561561
|
562562
1 | SELECT * FROM t GROUP BY GROUPING SETS ()
563-
| ------ ^ unexpected `)`, expecting `(`, `IS`, `IN`, `EXISTS`, `BETWEEN`, `+`, `-`, `*`, `/`, `//`, `DIV`, `%`, `||`, `<->`, `>`, `<`, `>=`, `<=`, `=`, `<>`, `!=`, `^`, `AND`, `OR`, `XOR`, `LIKE`, `NOT`, `REGEXP`, `RLIKE`, `SOUNDS`, <BitWiseOr>, <BitWiseAnd>, <BitWiseXor>, <ShiftLeft>, <ShiftRight>, `->`, `->>`, `#>`, `#>>`, `?`, `?|`, `?&`, `@>`, `<@`, `@?`, `@@`, `#-`, <Factorial>, <SquareRoot>, <BitWiseNot>, <CubeRoot>, <Abs>, `CAST`, `TRY_CAST`, `::`, `POSITION`, `IdentVariable`, `DATE_ADD`, `DATE_DIFF`, `DATEDIFF`, or 39 more ...
563+
| ------ ^ unexpected `)`, expecting `(`, `IS`, `IN`, `EXISTS`, `BETWEEN`, `+`, `-`, `*`, `/`, `//`, `DIV`, `%`, `||`, `<->`, `>`, `<`, `>=`, `<=`, `=`, `<>`, `!=`, `^`, `AND`, `OR`, `XOR`, `LIKE`, `NOT`, `REGEXP`, `RLIKE`, `SOUNDS`, <BitWiseOr>, <BitWiseAnd>, <BitWiseXor>, <ShiftLeft>, <ShiftRight>, `->`, `->>`, `#>`, `#>>`, `?`, `?|`, `?&`, `@>`, `<@`, `@?`, `@@`, `#-`, <Factorial>, <SquareRoot>, <BitWiseNot>, <CubeRoot>, <Abs>, `CAST`, `TRY_CAST`, `::`, `POSITION`, `IdentVariable`, `DATE_ADD`, `DATE_DIFF`, `DATEDIFF`, or 40 more ...
564564
| |
565565
| while parsing `SELECT ...`
566566

@@ -982,7 +982,7 @@ error:
982982
--> SQL:1:65
983983
|
984984
1 | CREATE FUNCTION IF NOT EXISTS isnotempty AS(p) -> not(is_null(p)
985-
| ------ -- ---- ^ unexpected end of input, expecting `)`, `(`, `WITHIN`, `IGNORE`, `RESPECT`, `OVER`, `IS`, `NOT`, `IN`, `EXISTS`, `BETWEEN`, `+`, `-`, `*`, `/`, `//`, `DIV`, `%`, `||`, `<->`, `>`, `<`, `>=`, `<=`, `=`, `<>`, `!=`, `^`, `AND`, `OR`, `XOR`, `LIKE`, `REGEXP`, `RLIKE`, `SOUNDS`, <BitWiseOr>, <BitWiseAnd>, <BitWiseXor>, <ShiftLeft>, <ShiftRight>, `->`, `->>`, `#>`, `#>>`, `?`, `?|`, `?&`, `@>`, `<@`, `@?`, `@@`, `#-`, <Factorial>, <SquareRoot>, <BitWiseNot>, <CubeRoot>, <Abs>, `CAST`, `TRY_CAST`, `::`, or 45 more ...
985+
| ------ -- ---- ^ unexpected end of input, expecting `)`, `(`, `WITHIN`, `IGNORE`, `RESPECT`, `OVER`, `IS`, `NOT`, `IN`, `EXISTS`, `BETWEEN`, `+`, `-`, `*`, `/`, `//`, `DIV`, `%`, `||`, `<->`, `>`, `<`, `>=`, `<=`, `=`, `<>`, `!=`, `^`, `AND`, `OR`, `XOR`, `LIKE`, `REGEXP`, `RLIKE`, `SOUNDS`, <BitWiseOr>, <BitWiseAnd>, <BitWiseXor>, <ShiftLeft>, <ShiftRight>, `->`, `->>`, `#>`, `#>>`, `?`, `?|`, `?&`, `@>`, `<@`, `@?`, `@@`, `#-`, <Factorial>, <SquareRoot>, <BitWiseNot>, <CubeRoot>, <Abs>, `CAST`, `TRY_CAST`, `::`, or 46 more ...
986986
| | | | |
987987
| | | | while parsing `(<expr> [, ...])`
988988
| | | while parsing expression

src/query/expression/src/function.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,8 @@ pub struct FunctionContext {
172172
pub parse_datetime_ignore_remainder: bool,
173173
pub enable_strict_datetime_parser: bool,
174174
pub random_function_seed: bool,
175+
pub week_start: u8,
176+
pub date_format_style: String,
175177
}
176178

177179
impl Default for FunctionContext {
@@ -192,6 +194,8 @@ impl Default for FunctionContext {
192194
parse_datetime_ignore_remainder: false,
193195
enable_strict_datetime_parser: true,
194196
random_function_seed: false,
197+
week_start: 0,
198+
date_format_style: "mysql".to_string(),
195199
}
196200
}
197201
}

src/query/expression/src/utils/date_helper.rs

Lines changed: 44 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
use std::sync::LazyLock;
16+
1517
use databend_common_exception::Result;
1618
use jiff::civil::date;
1719
use jiff::civil::datetime;
@@ -1315,43 +1317,47 @@ pub fn previous_or_next_day(dt: &Zoned, target: Weekday, is_previous: bool) -> i
13151317
datetime_to_date_inner_number(dt) + dir * days_diff
13161318
}
13171319

1318-
pub struct PGDateTimeFormatter;
1319-
1320-
impl PGDateTimeFormatter {
1321-
pub fn format(dt: Zoned, format_string: &str) -> String {
1322-
let mut result = format_string.to_string();
1323-
let mut format_map: Vec<(&str, fn(&Zoned) -> String)> = Vec::new();
1324-
1325-
format_map.push(("YYYY", |dt| dt.strftime("%Y").to_string()));
1326-
format_map.push(("YY", |dt| dt.strftime("%y").to_string()));
1327-
format_map.push(("MM", |dt| dt.strftime("%m").to_string()));
1328-
format_map.push(("MON", |dt| dt.strftime("%b").to_string()));
1329-
format_map.push(("MMMM", |dt| dt.strftime("%B").to_string()));
1330-
format_map.push(("DD", |dt| dt.strftime("%d").to_string()));
1331-
format_map.push(("DY", |dt| dt.strftime("%a").to_string()));
1332-
format_map.push(("HH24", |dt| dt.strftime("%H").to_string()));
1333-
format_map.push(("HH12", |dt| dt.strftime("%I").to_string()));
1334-
format_map.push(("AM", |dt| dt.strftime("%p").to_string()));
1335-
format_map.push(("PM", |dt| dt.strftime("%p").to_string()));
1336-
format_map.push(("MI", |dt| dt.strftime("%M").to_string()));
1337-
format_map.push(("SS", |dt| dt.strftime("%S").to_string()));
1338-
format_map.push(("FF", |dt| dt.strftime("%f").to_string()));
1339-
format_map.push(("TZH", |dt| {
1340-
dt.strftime("%z").to_string().chars().take(3).collect()
1341-
}));
1342-
format_map.push(("TZM", |dt| {
1343-
dt.strftime("%z")
1344-
.to_string()
1345-
.chars()
1346-
.skip(3)
1347-
.take(2)
1348-
.collect()
1349-
}));
1350-
format_map.push(("UUUU", |dt| dt.strftime("%G").to_string()));
1351-
for (key, func) in &format_map {
1352-
let reg = regex::Regex::new(&format!(r"(?i){}", key)).unwrap();
1353-
result = reg.replace_all(&result, func(&dt)).to_string();
1354-
}
1355-
result
1320+
static PG_STRFTIME_MAPPINGS: LazyLock<Vec<(&'static str, &'static str)>> = LazyLock::new(|| {
1321+
let mut mappings = vec![
1322+
("YYYY", "%Y"),
1323+
("YY", "%y"),
1324+
("MMMM", "%B"),
1325+
("MON", "%b"),
1326+
("MM", "%m"),
1327+
("DD", "%d"),
1328+
("DY", "%a"),
1329+
("HH24", "%H"),
1330+
("HH12", "%I"),
1331+
("AM", "%p"),
1332+
("PM", "%p"),
1333+
("MI", "%M"),
1334+
("SS", "%S"),
1335+
("FF", "%f"),
1336+
("UUUU", "%G"),
1337+
("TZHTZM", "%z"),
1338+
("TZH:TZM", "%z"),
1339+
("TZH", "%:::z"),
1340+
];
1341+
// Sort by key length in descending order to ensure
1342+
// longer patterns are matched first and to avoid short patterns replacing part of long patterns prematurely.
1343+
mappings.sort_by(|a, b| b.0.len().cmp(&a.0.len()));
1344+
mappings
1345+
});
1346+
1347+
#[inline]
1348+
pub fn pg_format_to_strftime(pg_format_string: &str) -> String {
1349+
let mut result = pg_format_string.to_string();
1350+
for (pg_key, strftime_code) in PG_STRFTIME_MAPPINGS.iter() {
1351+
let pattern = if *pg_key == "MON" {
1352+
// should keep "month". Only "MON" as a single string escape it.
1353+
format!(r"(?i)\b{}\b", regex::escape(pg_key))
1354+
} else {
1355+
format!(r"(?i){}", regex::escape(pg_key))
1356+
};
1357+
let reg = regex::Regex::new(&pattern).expect("Failed to compile regex for format key");
1358+
1359+
// Use replace_all to substitute all occurrences of the PG key with the strftime code.
1360+
result = reg.replace_all(&result, *strftime_code).to_string();
13561361
}
1362+
result
13571363
}

src/query/functions/src/scalars/other.rs

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ use databend_common_base::base::convert_byte_size;
2121
use databend_common_base::base::convert_number_size;
2222
use databend_common_base::base::uuid::Uuid;
2323
use databend_common_base::base::OrderedFloat;
24-
use databend_common_expression::date_helper::DateConverter;
25-
use databend_common_expression::date_helper::PGDateTimeFormatter;
2624
use databend_common_expression::error_to_null;
2725
use databend_common_expression::types::boolean::BooleanDomain;
2826
use databend_common_expression::types::nullable::NullableColumn;
@@ -397,8 +395,9 @@ fn register_grouping(registry: &mut FunctionRegistry) {
397395
}
398396

399397
fn register_num_to_char(registry: &mut FunctionRegistry) {
398+
registry.register_aliases("to_string", &["to_char"]);
400399
registry.register_passthrough_nullable_2_arg::<Int64Type, StringType, StringType, _, _>(
401-
"to_char",
400+
"to_string",
402401
|_, _, _| FunctionDomain::MayThrow,
403402
vectorize_with_builder_2_arg::<Int64Type, StringType, StringType>(
404403
|value, fmt, builder, ctx| {
@@ -427,7 +426,7 @@ fn register_num_to_char(registry: &mut FunctionRegistry) {
427426
);
428427

429428
registry.register_passthrough_nullable_2_arg::<Float32Type, StringType, StringType, _, _>(
430-
"to_char",
429+
"to_string",
431430
|_, _, _| FunctionDomain::MayThrow,
432431
vectorize_with_builder_2_arg::<Float32Type, StringType, StringType>(
433432
|value, fmt, builder, ctx| {
@@ -457,7 +456,7 @@ fn register_num_to_char(registry: &mut FunctionRegistry) {
457456
);
458457

459458
registry.register_passthrough_nullable_2_arg::<Float64Type, StringType, StringType, _, _>(
460-
"to_char",
459+
"to_string",
461460
|_, _, _| FunctionDomain::MayThrow,
462461
vectorize_with_builder_2_arg::<Float64Type, StringType, StringType>(
463462
|value, fmt, builder, ctx| {
@@ -485,19 +484,6 @@ fn register_num_to_char(registry: &mut FunctionRegistry) {
485484
},
486485
),
487486
);
488-
489-
registry.register_passthrough_nullable_2_arg::<TimestampType, StringType, StringType, _, _>(
490-
"to_char",
491-
|_, _, _| FunctionDomain::Full,
492-
vectorize_with_builder_2_arg::<TimestampType, StringType, StringType>(
493-
|micros, format, output, ctx| {
494-
let ts = micros.to_timestamp(ctx.func_ctx.tz.clone());
495-
let res = PGDateTimeFormatter::format(ts, format);
496-
output.put_str(&res);
497-
output.commit_row();
498-
},
499-
),
500-
);
501487
}
502488

503489
/// Compute `grouping` by `grouping_id` and `cols`.

0 commit comments

Comments
 (0)