Skip to content

Commit f0b2559

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

File tree

2 files changed

+187
-4
lines changed

2 files changed

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

0 commit comments

Comments
 (0)