Skip to content

Commit 991df86

Browse files
committed
fix(cubesql): Support explicit UTC as timezone in pushdown SQL generation
1 parent 6c19524 commit 991df86

File tree

2 files changed

+189
-4
lines changed

2 files changed

+189
-4
lines changed

rust/cubesql/cubesql/src/compile/engine/df/wrapper.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1706,26 +1706,37 @@ impl CubeScanWrapperNode {
17061706
}
17071707
}
17081708
// ScalarValue::Date64(_) => {}
1709-
ScalarValue::TimestampSecond(s, _) => {
1709+
1710+
// generate_sql_for_timestamp will call Utc constructors, so only support UTC zone for now
1711+
// DataFusion can return "UTC" for stuff like `NOW()` during constant folding
1712+
ScalarValue::TimestampSecond(s, tz)
1713+
if matches!(tz.as_deref(), None | Some("UTC")) =>
1714+
{
17101715
generate_sql_for_timestamp!(s, timestamp, sql_generator, sql_query)
17111716
}
1712-
ScalarValue::TimestampMillisecond(ms, None) => {
1717+
ScalarValue::TimestampMillisecond(ms, tz)
1718+
if matches!(tz.as_deref(), None | Some("UTC")) =>
1719+
{
17131720
generate_sql_for_timestamp!(
17141721
ms,
17151722
timestamp_millis_opt,
17161723
sql_generator,
17171724
sql_query
17181725
)
17191726
}
1720-
ScalarValue::TimestampMicrosecond(ms, None) => {
1727+
ScalarValue::TimestampMicrosecond(ms, tz)
1728+
if matches!(tz.as_deref(), None | Some("UTC")) =>
1729+
{
17211730
generate_sql_for_timestamp!(
17221731
ms,
17231732
timestamp_micros,
17241733
sql_generator,
17251734
sql_query
17261735
)
17271736
}
1728-
ScalarValue::TimestampNanosecond(nanoseconds, None) => {
1737+
ScalarValue::TimestampNanosecond(nanoseconds, tz)
1738+
if matches!(tz.as_deref(), None | Some("UTC")) =>
1739+
{
17291740
generate_sql_for_timestamp!(
17301741
nanoseconds,
17311742
timestamp_nanos,

rust/cubesql/cubesql/src/compile/test/test_wrapper.rs

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,180 @@ async fn test_simple_subquery_wrapper_filter_and_projection() {
541541
let _physical_plan = query_plan.as_physical_plan().await.unwrap();
542542
}
543543

544+
// TODO add more time zones
545+
// TODO add more TS syntax variants
546+
// TODO add TIMESTAMPTZ variant
547+
/// Using TIMESTAMP WITH TIME ZONE with actual timezone in wrapper should render proper timestamptz in SQL
548+
#[tokio::test]
549+
async fn test_wrapper_timestamptz() {
550+
if !Rewriter::sql_push_down_enabled() {
551+
return;
552+
}
553+
init_testing_logger();
554+
555+
let query_plan = convert_select_to_query_plan(
556+
// language=PostgreSQL
557+
r#"
558+
SELECT
559+
customer_gender
560+
FROM KibanaSampleDataEcommerce
561+
WHERE
562+
order_date >= TIMESTAMP WITH TIME ZONE '2024-02-03T04:05:06Z'
563+
AND
564+
-- This filter should trigger pushdown
565+
LOWER(customer_gender) = 'male'
566+
GROUP BY
567+
1
568+
;
569+
"#
570+
.to_string(),
571+
DatabaseProtocol::PostgreSQL,
572+
)
573+
.await;
574+
575+
let physical_plan = query_plan.as_physical_plan().await.unwrap();
576+
println!(
577+
"Physical plan: {}",
578+
displayable(physical_plan.as_ref()).indent()
579+
);
580+
581+
assert!(query_plan
582+
.as_logical_plan()
583+
.find_cube_scan_wrapper()
584+
.wrapped_sql
585+
.unwrap()
586+
.sql
587+
.contains("${KibanaSampleDataEcommerce.order_date} >= timestamptz '2024-02-03T04:05:06.000Z'"));
588+
}
589+
590+
// TODO add more time zones
591+
// TODO add more TS syntax variants
592+
// TODO add TIMESTAMPTZ variant
593+
/// Using TIMESTAMP WITH TIME ZONE with actual timezone in ungrouped wrapper should render proper timestamptz in SQL
594+
#[tokio::test]
595+
async fn test_wrapper_timestamptz_ungrouped() {
596+
if !Rewriter::sql_push_down_enabled() {
597+
return;
598+
}
599+
init_testing_logger();
600+
601+
let query_plan = convert_select_to_query_plan(
602+
// language=PostgreSQL
603+
r#"
604+
SELECT
605+
customer_gender
606+
FROM KibanaSampleDataEcommerce
607+
WHERE
608+
order_date >= TIMESTAMP WITH TIME ZONE '2024-02-03T04:05:06Z'
609+
AND
610+
-- This filter should trigger pushdown
611+
LOWER(customer_gender) = 'male'
612+
;
613+
"#
614+
.to_string(),
615+
DatabaseProtocol::PostgreSQL,
616+
)
617+
.await;
618+
619+
let physical_plan = query_plan.as_physical_plan().await.unwrap();
620+
println!(
621+
"Physical plan: {}",
622+
displayable(physical_plan.as_ref()).indent()
623+
);
624+
625+
assert!(query_plan
626+
.as_logical_plan()
627+
.find_cube_scan_wrapper()
628+
.wrapped_sql
629+
.unwrap()
630+
.sql
631+
.contains("${KibanaSampleDataEcommerce.order_date} >= timestamptz '2024-02-03T04:05:06.000Z'"));
632+
}
633+
634+
/// Using NOW() in wrapper should render proper timestamptz in SQL
635+
#[tokio::test]
636+
async fn test_wrapper_now() {
637+
if !Rewriter::sql_push_down_enabled() {
638+
return;
639+
}
640+
init_testing_logger();
641+
642+
let query_plan = convert_select_to_query_plan(
643+
// language=PostgreSQL
644+
r#"
645+
SELECT
646+
customer_gender
647+
FROM KibanaSampleDataEcommerce
648+
WHERE
649+
order_date >= NOW()
650+
AND
651+
-- This filter should trigger pushdown
652+
LOWER(customer_gender) = 'male'
653+
GROUP BY
654+
1
655+
;
656+
"#
657+
.to_string(),
658+
DatabaseProtocol::PostgreSQL,
659+
)
660+
.await;
661+
662+
let physical_plan = query_plan.as_physical_plan().await.unwrap();
663+
println!(
664+
"Physical plan: {}",
665+
displayable(physical_plan.as_ref()).indent()
666+
);
667+
668+
assert!(query_plan
669+
.as_logical_plan()
670+
.find_cube_scan_wrapper()
671+
.wrapped_sql
672+
.unwrap()
673+
.sql
674+
.contains("${KibanaSampleDataEcommerce.order_date} >= timestamptz"));
675+
}
676+
677+
/// Using NOW() in ungrouped wrapper should render proper timestamptz in SQL
678+
#[tokio::test]
679+
async fn test_wrapper_now_ungrouped() {
680+
if !Rewriter::sql_push_down_enabled() {
681+
return;
682+
}
683+
init_testing_logger();
684+
685+
let query_plan = convert_select_to_query_plan(
686+
// language=PostgreSQL
687+
r#"
688+
SELECT
689+
customer_gender
690+
FROM KibanaSampleDataEcommerce
691+
WHERE
692+
order_date >= NOW()
693+
AND
694+
-- This filter should trigger pushdown
695+
LOWER(customer_gender) = 'male'
696+
;
697+
"#
698+
.to_string(),
699+
DatabaseProtocol::PostgreSQL,
700+
)
701+
.await;
702+
703+
let physical_plan = query_plan.as_physical_plan().await.unwrap();
704+
println!(
705+
"Physical plan: {}",
706+
displayable(physical_plan.as_ref()).indent()
707+
);
708+
709+
assert!(query_plan
710+
.as_logical_plan()
711+
.find_cube_scan_wrapper()
712+
.wrapped_sql
713+
.unwrap()
714+
.sql
715+
.contains("${KibanaSampleDataEcommerce.order_date} >= timestamptz"));
716+
}
717+
544718
#[tokio::test]
545719
async fn test_case_wrapper() {
546720
if !Rewriter::sql_push_down_enabled() {

0 commit comments

Comments
 (0)