Skip to content

Commit 0fb5a56

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

File tree

2 files changed

+193
-4
lines changed

2 files changed

+193
-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: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,184 @@ 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(
588+
"${KibanaSampleDataEcommerce.order_date} >= timestamptz '2024-02-03T04:05:06.000Z'"
589+
));
590+
}
591+
592+
// TODO add more time zones
593+
// TODO add more TS syntax variants
594+
// TODO add TIMESTAMPTZ variant
595+
/// Using TIMESTAMP WITH TIME ZONE with actual timezone in ungrouped wrapper should render proper timestamptz in SQL
596+
#[tokio::test]
597+
async fn test_wrapper_timestamptz_ungrouped() {
598+
if !Rewriter::sql_push_down_enabled() {
599+
return;
600+
}
601+
init_testing_logger();
602+
603+
let query_plan = convert_select_to_query_plan(
604+
// language=PostgreSQL
605+
r#"
606+
SELECT
607+
customer_gender
608+
FROM KibanaSampleDataEcommerce
609+
WHERE
610+
order_date >= TIMESTAMP WITH TIME ZONE '2024-02-03T04:05:06Z'
611+
AND
612+
-- This filter should trigger pushdown
613+
LOWER(customer_gender) = 'male'
614+
;
615+
"#
616+
.to_string(),
617+
DatabaseProtocol::PostgreSQL,
618+
)
619+
.await;
620+
621+
let physical_plan = query_plan.as_physical_plan().await.unwrap();
622+
println!(
623+
"Physical plan: {}",
624+
displayable(physical_plan.as_ref()).indent()
625+
);
626+
627+
assert!(query_plan
628+
.as_logical_plan()
629+
.find_cube_scan_wrapper()
630+
.wrapped_sql
631+
.unwrap()
632+
.sql
633+
.contains(
634+
"${KibanaSampleDataEcommerce.order_date} >= timestamptz '2024-02-03T04:05:06.000Z'"
635+
));
636+
}
637+
638+
/// Using NOW() in wrapper should render proper timestamptz in SQL
639+
#[tokio::test]
640+
async fn test_wrapper_now() {
641+
if !Rewriter::sql_push_down_enabled() {
642+
return;
643+
}
644+
init_testing_logger();
645+
646+
let query_plan = convert_select_to_query_plan(
647+
// language=PostgreSQL
648+
r#"
649+
SELECT
650+
customer_gender
651+
FROM KibanaSampleDataEcommerce
652+
WHERE
653+
order_date >= NOW()
654+
AND
655+
-- This filter should trigger pushdown
656+
LOWER(customer_gender) = 'male'
657+
GROUP BY
658+
1
659+
;
660+
"#
661+
.to_string(),
662+
DatabaseProtocol::PostgreSQL,
663+
)
664+
.await;
665+
666+
let physical_plan = query_plan.as_physical_plan().await.unwrap();
667+
println!(
668+
"Physical plan: {}",
669+
displayable(physical_plan.as_ref()).indent()
670+
);
671+
672+
assert!(query_plan
673+
.as_logical_plan()
674+
.find_cube_scan_wrapper()
675+
.wrapped_sql
676+
.unwrap()
677+
.sql
678+
.contains("${KibanaSampleDataEcommerce.order_date} >= timestamptz"));
679+
}
680+
681+
/// Using NOW() in ungrouped wrapper should render proper timestamptz in SQL
682+
#[tokio::test]
683+
async fn test_wrapper_now_ungrouped() {
684+
if !Rewriter::sql_push_down_enabled() {
685+
return;
686+
}
687+
init_testing_logger();
688+
689+
let query_plan = convert_select_to_query_plan(
690+
// language=PostgreSQL
691+
r#"
692+
SELECT
693+
customer_gender
694+
FROM KibanaSampleDataEcommerce
695+
WHERE
696+
order_date >= NOW()
697+
AND
698+
-- This filter should trigger pushdown
699+
LOWER(customer_gender) = 'male'
700+
;
701+
"#
702+
.to_string(),
703+
DatabaseProtocol::PostgreSQL,
704+
)
705+
.await;
706+
707+
let physical_plan = query_plan.as_physical_plan().await.unwrap();
708+
println!(
709+
"Physical plan: {}",
710+
displayable(physical_plan.as_ref()).indent()
711+
);
712+
713+
assert!(query_plan
714+
.as_logical_plan()
715+
.find_cube_scan_wrapper()
716+
.wrapped_sql
717+
.unwrap()
718+
.sql
719+
.contains("${KibanaSampleDataEcommerce.order_date} >= timestamptz"));
720+
}
721+
544722
#[tokio::test]
545723
async fn test_case_wrapper() {
546724
if !Rewriter::sql_push_down_enabled() {

0 commit comments

Comments
 (0)