From 3e30de290702625d5b73cd297715a2ac92a9bdd8 Mon Sep 17 00:00:00 2001 From: BohuTANG Date: Wed, 29 Oct 2025 19:34:38 +0800 Subject: [PATCH 1/2] Support CURRENT_DATE/TIME keywords --- src/query/ast/src/parser/expr.rs | 30 ++++++++++++++++ src/query/ast/src/parser/token.rs | 8 +++-- .../src/scalars/timestamp/src/datetime.rs | 35 +++++++++++++++++++ .../it/scalars/testdata/function_list.txt | 2 ++ .../functions/02_0012_function_datetimes.test | 10 ++++++ 5 files changed, 83 insertions(+), 2 deletions(-) diff --git a/src/query/ast/src/parser/expr.rs b/src/query/ast/src/parser/expr.rs index 2d05a9a892b2d..a69c913fd1388 100644 --- a/src/query/ast/src/parser/expr.rs +++ b/src/query/ast/src/parser/expr.rs @@ -1499,6 +1499,34 @@ pub fn expr_element(i: Input) -> IResult> { |(_, not, _, _)| ExprElement::IsDistinctFrom { not: not.is_some() }, ); + let current_date = map(consumed(rule! { CURRENT_DATE }), |(span, _)| { + ExprElement::FunctionCall { + func: FunctionCall { + distinct: false, + name: Identifier::from_name(transform_span(span.tokens), "current_date"), + args: vec![], + params: vec![], + order_by: vec![], + window: None, + lambda: None, + }, + } + }); + + let current_time = map(consumed(rule! { CURRENT_TIME }), |(span, _)| { + ExprElement::FunctionCall { + func: FunctionCall { + distinct: false, + name: Identifier::from_name(transform_span(span.tokens), "current_time"), + args: vec![], + params: vec![], + order_by: vec![], + window: None, + lambda: None, + }, + } + }); + let current_timestamp = map(consumed(rule! { CURRENT_TIMESTAMP }), |(span, _)| { ExprElement::FunctionCall { func: FunctionCall { @@ -1573,6 +1601,8 @@ pub fn expr_element(i: Input) -> IResult> { | #dot_access : "" | #map_access : "[] | . | :" | #literal : "" + | #current_date: "CURRENT_DATE" + | #current_time: "CURRENT_TIME" | #current_timestamp: "CURRENT_TIMESTAMP" | #array : "`[, ...]`" | #map_expr : "`{ : , ... }`" diff --git a/src/query/ast/src/parser/token.rs b/src/query/ast/src/parser/token.rs index 850a5c68f3efa..074fdb09bebf7 100644 --- a/src/query/ast/src/parser/token.rs +++ b/src/query/ast/src/parser/token.rs @@ -497,10 +497,14 @@ pub enum TokenKind { CROSS, #[token("CSV", ignore(ascii_case))] CSV, - #[token("CURRENT", ignore(ascii_case))] - CURRENT, + #[token("CURRENT_DATE", ignore(ascii_case))] + CURRENT_DATE, + #[token("CURRENT_TIME", ignore(ascii_case))] + CURRENT_TIME, #[token("CURRENT_TIMESTAMP", ignore(ascii_case))] CURRENT_TIMESTAMP, + #[token("CURRENT", ignore(ascii_case))] + CURRENT, #[token("DATABASE", ignore(ascii_case))] DATABASE, #[token("DATABASES", ignore(ascii_case))] diff --git a/src/query/functions/src/scalars/timestamp/src/datetime.rs b/src/query/functions/src/scalars/timestamp/src/datetime.rs index 1ce40869f6348..31eee72679f7c 100644 --- a/src/query/functions/src/scalars/timestamp/src/datetime.rs +++ b/src/query/functions/src/scalars/timestamp/src/datetime.rs @@ -2103,11 +2103,16 @@ fn register_between_functions(registry: &mut FunctionRegistry) { fn register_real_time_functions(registry: &mut FunctionRegistry) { registry.register_aliases("now", &["current_timestamp"]); + registry.register_aliases("today", &["current_date"]); registry.properties.insert( "now".to_string(), FunctionProperty::default().non_deterministic(), ); + registry.properties.insert( + "current_time".to_string(), + FunctionProperty::default().non_deterministic(), + ); registry.properties.insert( "today".to_string(), FunctionProperty::default().non_deterministic(), @@ -2153,6 +2158,36 @@ fn register_real_time_functions(registry: &mut FunctionRegistry) { |ctx| Value::Scalar(ctx.func_ctx.now.timestamp().as_microsecond()), ); + registry.register_0_arg_core::( + "current_time", + |_| FunctionDomain::Full, + |ctx| { + let datetime = ctx + .func_ctx + .now + .with_time_zone(ctx.func_ctx.tz.clone()) + .datetime(); + let nanos = datetime.subsec_nanosecond(); + let value = if nanos == 0 { + format!( + "{:02}:{:02}:{:02}", + datetime.hour(), + datetime.minute(), + datetime.second() + ) + } else { + format!( + "{:02}:{:02}:{:02}.{:06}", + datetime.hour(), + datetime.minute(), + datetime.second(), + nanos / 1_000 + ) + }; + Value::Scalar(value) + }, + ); + registry.register_0_arg_core::( "today", |_| FunctionDomain::Full, 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 8011f1d6f967f..c755b011ac715 100644 --- a/src/query/functions/tests/it/scalars/testdata/function_list.txt +++ b/src/query/functions/tests/it/scalars/testdata/function_list.txt @@ -19,6 +19,7 @@ ceiling -> ceil char_length -> length character_length -> length chr -> char +current_date -> today current_timestamp -> now date -> to_date date_format -> to_string @@ -1360,6 +1361,7 @@ Functions overloads: 1 cot(Float64 NULL) :: Float64 NULL 0 crc32(String) :: UInt32 1 crc32(String NULL) :: UInt32 NULL +0 current_time() :: String 0 date_add_months(Date, Int64) :: Date 1 date_add_months(Date NULL, Int64 NULL) :: Date NULL 2 date_add_months(Timestamp, Int64) :: Timestamp 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 880966f82661b..0b2820854a470 100644 --- a/tests/sqllogictests/suites/query/functions/02_0012_function_datetimes.test +++ b/tests/sqllogictests/suites/query/functions/02_0012_function_datetimes.test @@ -222,6 +222,16 @@ select typeof(now() - now()) ---- BIGINT +query TTT +select typeof(CURRENT_DATE()), typeof(CURRENT_TIME()), typeof(CURRENT_TIMESTAMP()) +---- +DATE VARCHAR TIMESTAMP + +query B +select regexp_like(CURRENT_TIME(), '^[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{6})?$') +---- +1 + query B select typeof(to_unix_timestamp('2023-04-06 04:06:23.231808')) ---- From 73574cb365d0765f544cd70b59dd5ed59b1cc904 Mon Sep 17 00:00:00 2001 From: BohuTANG Date: Thu, 30 Oct 2025 10:46:08 +0800 Subject: [PATCH 2/2] align current_time with ANSI TIME semantics --- Cargo.toml | 2 +- .../src/scalars/timestamp/src/datetime.rs | 75 ++++++++++++------- .../functions/tests/it/scalars/datetime.rs | 28 +++++++ .../tests/it/scalars/testdata/datetime.txt | 38 ++++++++++ .../it/scalars/testdata/function_list.txt | 2 + .../functions/02_0012_function_datetimes.test | 7 +- 6 files changed, 125 insertions(+), 27 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 73df776befd9f..1143327f58ceb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -465,7 +465,7 @@ rmp-serde = "1.1.1" roaring = { version = "^0.10", features = ["serde"] } rotbl = { version = "0.2.9", features = [] } rust_decimal = "1.26" -rustix = "0.38.37" +rustix = { version = "0.38.37", features = ["fs"] } rustls = { version = "0.23.27", features = ["ring", "tls12"], default-features = false } rustls-pemfile = "2" rustls-pki-types = "1" diff --git a/src/query/functions/src/scalars/timestamp/src/datetime.rs b/src/query/functions/src/scalars/timestamp/src/datetime.rs index 31eee72679f7c..bff2a202ab780 100644 --- a/src/query/functions/src/scalars/timestamp/src/datetime.rs +++ b/src/query/functions/src/scalars/timestamp/src/datetime.rs @@ -68,6 +68,7 @@ use databend_common_expression::vectorize_with_builder_1_arg; use databend_common_expression::vectorize_with_builder_2_arg; use databend_common_expression::vectorize_with_builder_4_arg; use databend_common_expression::EvalContext; +use databend_common_expression::FunctionContext; use databend_common_expression::FunctionDomain; use databend_common_expression::FunctionProperty; use databend_common_expression::FunctionRegistry; @@ -2101,6 +2102,38 @@ fn register_between_functions(registry: &mut FunctionRegistry) { ]); } +fn normalize_time_precision(raw: i64) -> Result { + if (0..=9).contains(&raw) { + Ok(raw as u8) + } else { + Err(format!( + "Invalid fractional seconds precision `{raw}` for `current_time` (expect 0-9)" + )) + } +} + +fn current_time_string(func_ctx: &FunctionContext, precision: Option) -> String { + let datetime = func_ctx.now.with_time_zone(func_ctx.tz.clone()).datetime(); + let nanos = datetime.subsec_nanosecond() as u32; + let mut value = format!( + "{:02}:{:02}:{:02}", + datetime.hour(), + datetime.minute(), + datetime.second() + ); + + let precision = precision.unwrap_or(9).min(9); + if precision > 0 { + let divisor = 10_u32.pow(9 - precision as u32); + let truncated = nanos / divisor; + let frac = format!("{:0width$}", truncated, width = precision as usize); + value.push('.'); + value.push_str(&frac); + } + + value +} + fn register_real_time_functions(registry: &mut FunctionRegistry) { registry.register_aliases("now", &["current_timestamp"]); registry.register_aliases("today", &["current_date"]); @@ -2161,31 +2194,23 @@ fn register_real_time_functions(registry: &mut FunctionRegistry) { registry.register_0_arg_core::( "current_time", |_| FunctionDomain::Full, - |ctx| { - let datetime = ctx - .func_ctx - .now - .with_time_zone(ctx.func_ctx.tz.clone()) - .datetime(); - let nanos = datetime.subsec_nanosecond(); - let value = if nanos == 0 { - format!( - "{:02}:{:02}:{:02}", - datetime.hour(), - datetime.minute(), - datetime.second() - ) - } else { - format!( - "{:02}:{:02}:{:02}.{:06}", - datetime.hour(), - datetime.minute(), - datetime.second(), - nanos / 1_000 - ) - }; - Value::Scalar(value) - }, + |ctx| Value::Scalar(current_time_string(ctx.func_ctx, None)), + ); + + registry.register_passthrough_nullable_1_arg::( + "current_time", + |_, _| FunctionDomain::MayThrow, + vectorize_with_builder_1_arg::(|precision, output, ctx| { + match normalize_time_precision(precision) { + Ok(valid_precision) => { + output.put_and_commit(current_time_string(ctx.func_ctx, Some(valid_precision))); + } + Err(err) => { + ctx.set_error(output.len(), err); + output.commit_row(); + } + } + }), ); registry.register_0_arg_core::( diff --git a/src/query/functions/tests/it/scalars/datetime.rs b/src/query/functions/tests/it/scalars/datetime.rs index 6fba662c57b36..ba82c6bd44d04 100644 --- a/src/query/functions/tests/it/scalars/datetime.rs +++ b/src/query/functions/tests/it/scalars/datetime.rs @@ -13,12 +13,18 @@ // limitations under the License. use std::io::Write; +use std::str::FromStr; use databend_common_expression::types::*; use databend_common_expression::FromData; +use databend_common_expression::FunctionContext; use goldenfile::Mint; +use jiff::tz::TimeZone; +use jiff::Timestamp; use super::run_ast; +use super::run_ast_with_context; +use super::TestContext; #[test] fn test_datetime() { @@ -36,6 +42,7 @@ fn test_datetime() { test_to_number(file); test_rounder_functions(file); test_date_date_diff(file); + test_current_time(file); } fn test_to_timestamp(file: &mut impl Write) { @@ -742,3 +749,24 @@ fn test_date_date_diff(file: &mut impl Write) { &[], ); } + +fn test_current_time(file: &mut impl Write) { + let tz = TimeZone::UTC; + let now = Timestamp::from_str("2024-02-03T04:05:06.789123Z") + .unwrap() + .to_zoned(tz.clone()); + let func_ctx = FunctionContext { + tz: tz.clone(), + now, + ..FunctionContext::default() + }; + let ctx = TestContext { + func_ctx, + ..TestContext::default() + }; + + run_ast_with_context(file, "typeof(current_time())", ctx.clone()); + run_ast_with_context(file, "current_time()", ctx.clone()); + run_ast_with_context(file, "current_time(3)", ctx.clone()); + run_ast_with_context(file, "current_time(10)", ctx); +} diff --git a/src/query/functions/tests/it/scalars/testdata/datetime.txt b/src/query/functions/tests/it/scalars/testdata/datetime.txt index 3a956e7905c47..6352da18ab18d 100644 --- a/src/query/functions/tests/it/scalars/testdata/datetime.txt +++ b/src/query/functions/tests/it/scalars/testdata/datetime.txt @@ -3835,3 +3835,41 @@ output domain : {-31622400..=-31622400} output : -31622400 +ast : typeof(current_time()) +raw expr : typeof(current_time()) +checked expr : typeof(current_time<>()) +optimized expr : "VARCHAR" +func ctx : (modified) +output type : String +output domain : {"VARCHAR"..="VARCHAR"} +output : 'VARCHAR' + + +ast : current_time() +raw expr : current_time() +checked expr : current_time<>() +optimized expr : "04:05:06.789123000" +func ctx : (modified) +output type : String +output domain : {"04:05:06.789123000"..="04:05:06.789123000"} +output : '04:05:06.789123000' + + +ast : current_time(3) +raw expr : current_time(3) +checked expr : current_time(CAST(3_u8 AS Int64)) +optimized expr : "04:05:06.789" +func ctx : (modified) +output type : String +output domain : {"04:05:06.789"..="04:05:06.789"} +output : '04:05:06.789' + + +error: + --> SQL:1:1 + | +1 | current_time(10) + | ^^^^^^^^^^^^^^^^ Invalid fractional seconds precision `10` for `current_time` (expect 0-9) while evaluating function `current_time(10)` in expr `current_time(CAST(10 AS Int64))` + + + 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 c755b011ac715..087dd9890c424 100644 --- a/src/query/functions/tests/it/scalars/testdata/function_list.txt +++ b/src/query/functions/tests/it/scalars/testdata/function_list.txt @@ -1362,6 +1362,8 @@ Functions overloads: 0 crc32(String) :: UInt32 1 crc32(String NULL) :: UInt32 NULL 0 current_time() :: String +1 current_time(Int64) :: String +2 current_time(Int64 NULL) :: String NULL 0 date_add_months(Date, Int64) :: Date 1 date_add_months(Date NULL, Int64 NULL) :: Date NULL 2 date_add_months(Timestamp, Int64) :: Timestamp 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 0b2820854a470..5116724d2702b 100644 --- a/tests/sqllogictests/suites/query/functions/02_0012_function_datetimes.test +++ b/tests/sqllogictests/suites/query/functions/02_0012_function_datetimes.test @@ -228,7 +228,12 @@ select typeof(CURRENT_DATE()), typeof(CURRENT_TIME()), typeof(CURRENT_TIMESTAMP( DATE VARCHAR TIMESTAMP query B -select regexp_like(CURRENT_TIME(), '^[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{6})?$') +select regexp_like(CURRENT_TIME(), '^[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{1,9})?$') +---- +1 + +query B +select regexp_like(CURRENT_TIME(3), '^[0-9]{2}:[0-9]{2}:[0-9]{2}\.\d{3}$') ---- 1