Skip to content

Commit 69ba0ee

Browse files
committed
fix(cubesql): Support DATE_TRUNC equals literal string
1 parent 4969903 commit 69ba0ee

File tree

2 files changed

+123
-108
lines changed

2 files changed

+123
-108
lines changed

rust/cubesql/cubesql/src/compile/mod.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23347,4 +23347,65 @@ LIMIT {{ limit }}{% endif %}"#.to_string(),
2334723347
}
2334823348
)
2334923349
}
23350+
23351+
#[tokio::test]
23352+
async fn test_date_trunc_eq_literal() {
23353+
init_logger();
23354+
23355+
let logical_plan = convert_select_to_query_plan(
23356+
r#"
23357+
SELECT
23358+
DATE_TRUNC('quarter', order_date) as "c0",
23359+
DATE_PART('quarter', order_date) as "c1"
23360+
FROM
23361+
"KibanaSampleDataEcommerce" as "KibanaSampleDataEcommerce"
23362+
WHERE
23363+
(
23364+
DATE_TRUNC('year', order_date) = '2024-01-01 00:00:00.0'
23365+
)
23366+
GROUP BY
23367+
DATE_TRUNC('quarter', order_date),
23368+
DATE_PART('quarter', order_date)
23369+
ORDER BY
23370+
CASE
23371+
WHEN DATE_TRUNC('quarter', order_date) IS NULL THEN 1
23372+
ELSE 0
23373+
END,
23374+
DATE_TRUNC('quarter', order_date) ASC
23375+
;"#
23376+
.to_string(),
23377+
DatabaseProtocol::PostgreSQL,
23378+
)
23379+
.await
23380+
.as_logical_plan();
23381+
23382+
assert_eq!(
23383+
logical_plan.find_cube_scan().request,
23384+
V1LoadRequestQuery {
23385+
measures: Some(vec![]),
23386+
dimensions: Some(vec![]),
23387+
segments: Some(vec![]),
23388+
time_dimensions: Some(vec![
23389+
V1LoadRequestQueryTimeDimension {
23390+
dimension: "KibanaSampleDataEcommerce.order_date".to_string(),
23391+
granularity: Some("quarter".to_string()),
23392+
date_range: Some(json!(vec![
23393+
"2024-01-01T00:00:00.000Z".to_string(),
23394+
"2024-12-31T23:59:59.999Z".to_string(),
23395+
])),
23396+
},
23397+
V1LoadRequestQueryTimeDimension {
23398+
dimension: "KibanaSampleDataEcommerce.order_date".to_string(),
23399+
granularity: Some("quarter".to_string()),
23400+
date_range: None,
23401+
},
23402+
]),
23403+
order: None,
23404+
limit: None,
23405+
offset: None,
23406+
filters: None,
23407+
ungrouped: None,
23408+
}
23409+
)
23410+
}
2335023411
}

rust/cubesql/cubesql/src/compile/rewrite/rules/filters.rs

Lines changed: 62 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -852,54 +852,6 @@ impl RewriteRules for FilterRules {
852852
"?filter_aliases",
853853
),
854854
),
855-
transforming_rewrite(
856-
"filter-replacer-date-trunc-equals",
857-
filter_replacer(
858-
binary_expr(
859-
self.fun_expr(
860-
"DateTrunc",
861-
vec![literal_expr("?granularity"), column_expr("?column")],
862-
),
863-
"=",
864-
self.fun_expr(
865-
"DateTrunc",
866-
vec![literal_expr("?granularity"), literal_expr("?date")],
867-
),
868-
),
869-
"?alias_to_cube",
870-
"?members",
871-
"?filter_aliases",
872-
),
873-
filter_replacer(
874-
binary_expr(
875-
binary_expr(
876-
column_expr("?column"),
877-
">=",
878-
self.fun_expr(
879-
"DateTrunc",
880-
vec![literal_expr("?granularity"), literal_expr("?date")],
881-
),
882-
),
883-
"AND",
884-
binary_expr(
885-
column_expr("?column"),
886-
"<",
887-
binary_expr(
888-
self.fun_expr(
889-
"DateTrunc",
890-
vec![literal_expr("?granularity"), literal_expr("?date")],
891-
),
892-
"+",
893-
literal_expr("?interval"),
894-
),
895-
),
896-
),
897-
"?alias_to_cube",
898-
"?members",
899-
"?filter_aliases",
900-
),
901-
self.transform_granularity_to_interval("?granularity", "?interval"),
902-
),
903855
rewrite(
904856
"filter-str-pos-to-like",
905857
filter_replacer(
@@ -1756,12 +1708,12 @@ impl RewriteRules for FilterRules {
17561708
),
17571709
),
17581710
transforming_rewrite(
1759-
"filter-date-trunc-eq-literal-date",
1711+
"filter-date-trunc-eq-literal",
17601712
filter_replacer(
17611713
binary_expr(
17621714
self.fun_expr(
17631715
"DateTrunc",
1764-
vec![literal_expr("?granularity"), column_expr("?column")],
1716+
vec!["?granularity".to_string(), column_expr("?column")],
17651717
),
17661718
"=",
17671719
"?date".to_string(),
@@ -1772,41 +1724,19 @@ impl RewriteRules for FilterRules {
17721724
),
17731725
filter_replacer(
17741726
binary_expr(
1775-
binary_expr(
1776-
column_expr("?column"),
1777-
">=",
1778-
self.fun_expr(
1779-
"DateTrunc",
1780-
vec![literal_expr("?granularity"), "?date".to_string()],
1781-
),
1782-
),
1727+
binary_expr(column_expr("?column"), ">=", literal_expr("?start_date")),
17831728
"AND",
1784-
binary_expr(
1785-
column_expr("?column"),
1786-
"<",
1787-
self.fun_expr(
1788-
"DateTrunc",
1789-
vec![
1790-
literal_expr("?granularity"),
1791-
udf_expr(
1792-
"date_add",
1793-
vec![
1794-
"?date".to_string(),
1795-
literal_expr("?date_add_interval"),
1796-
],
1797-
),
1798-
],
1799-
),
1800-
),
1729+
binary_expr(column_expr("?column"), "<", literal_expr("?end_date")),
18011730
),
18021731
"?alias_to_cube",
18031732
"?members",
18041733
"?filter_aliases",
18051734
),
1806-
self.transform_date_trunc_eq_literal_date(
1735+
self.transform_date_trunc_eq_literal(
18071736
"?granularity",
18081737
"?date",
1809-
"?date_add_interval",
1738+
"?start_date",
1739+
"?end_date",
18101740
),
18111741
),
18121742
rewrite(
@@ -3949,43 +3879,57 @@ impl FilterRules {
39493879
}
39503880
}
39513881

3952-
fn transform_date_trunc_eq_literal_date(
3882+
fn transform_date_trunc_eq_literal(
39533883
&self,
39543884
granularity_var: &'static str,
39553885
date_var: &'static str,
3956-
date_add_interval_var: &'static str,
3886+
start_date_var: &'static str,
3887+
end_date_var: &'static str,
39573888
) -> impl Fn(&mut EGraph<LogicalPlanLanguage, LogicalPlanAnalysis>, &mut Subst) -> bool {
39583889
let granularity_var = var!(granularity_var);
39593890
let date_var = var!(date_var);
3960-
let date_add_interval_var = var!(date_add_interval_var);
3891+
let start_date_var = var!(start_date_var);
3892+
let end_date_var = var!(end_date_var);
39613893
move |egraph, subst| {
3962-
for granularity in var_iter!(egraph[subst[granularity_var]], LiteralExprValue) {
3963-
if let ScalarValue::Utf8(Some(granularity)) = granularity {
3964-
if let Some(date_add_interval) =
3965-
utils::granularity_str_to_interval(&granularity)
3966-
{
3967-
if let Some(ConstantFolding::Scalar(date)) =
3968-
&egraph[subst[date_var]].data.constant
3969-
{
3970-
if let ScalarValue::TimestampNanosecond(Some(date), None) = date {
3971-
if let Some(true) =
3972-
utils::is_literal_date_trunced(*date, &granularity)
3973-
{
3974-
subst.insert(
3975-
date_add_interval_var,
3976-
egraph.add(LogicalPlanLanguage::LiteralExprValue(
3977-
LiteralExprValue(date_add_interval),
3978-
)),
3979-
);
3894+
let Some(ConstantFolding::Scalar(ScalarValue::Utf8(Some(granularity)))) =
3895+
&egraph[subst[granularity_var]].data.constant
3896+
else {
3897+
return false;
3898+
};
39803899

3981-
return true;
3982-
}
3983-
}
3984-
}
3985-
}
3986-
}
3987-
}
3988-
false
3900+
let Some(ConstantFolding::Scalar(date)) = &egraph[subst[date_var]].data.constant else {
3901+
return false;
3902+
};
3903+
let Some(Some(date)) = Self::scalar_dt_to_naive_datetime(date) else {
3904+
return false;
3905+
};
3906+
3907+
let Some((start_date, end_date)) =
3908+
Self::naive_datetime_to_range_by_granularity(date, granularity)
3909+
else {
3910+
return false;
3911+
};
3912+
3913+
let (Some(start_date), Some(end_date)) = (
3914+
start_date.timestamp_nanos_opt(),
3915+
end_date.timestamp_nanos_opt(),
3916+
) else {
3917+
return false;
3918+
};
3919+
3920+
subst.insert(
3921+
start_date_var,
3922+
egraph.add(LogicalPlanLanguage::LiteralExprValue(LiteralExprValue(
3923+
ScalarValue::TimestampNanosecond(Some(start_date), None),
3924+
))),
3925+
);
3926+
subst.insert(
3927+
end_date_var,
3928+
egraph.add(LogicalPlanLanguage::LiteralExprValue(LiteralExprValue(
3929+
ScalarValue::TimestampNanosecond(Some(end_date), None),
3930+
))),
3931+
);
3932+
true
39893933
}
39903934
}
39913935

@@ -4515,7 +4459,7 @@ impl FilterRules {
45154459
for negated in var_iter!(egraph[subst[negated_var]], InListExprNegated) {
45164460
let Some(values) = list
45174461
.into_iter()
4518-
.map(|literal| Self::scalar_utf8_dt_to_naive_datetime(literal))
4462+
.map(|literal| Self::scalar_dt_to_naive_datetime(literal))
45194463
.collect::<Option<HashSet<_>>>()
45204464
.map(|values| {
45214465
let mut values = values.into_iter().collect::<Vec<_>>();
@@ -4626,7 +4570,17 @@ impl FilterRules {
46264570
// The outer Option's purpose is to signal when the type is incorrect
46274571
// or parsing couldn't interpret the value as a NativeDateTime.
46284572
// The inner Option is None when the ScalarValue is None.
4629-
fn scalar_utf8_dt_to_naive_datetime(literal: &ScalarValue) -> Option<Option<NaiveDateTime>> {
4573+
fn scalar_dt_to_naive_datetime(literal: &ScalarValue) -> Option<Option<NaiveDateTime>> {
4574+
if let ScalarValue::TimestampNanosecond(ts, None) = literal {
4575+
let Some(ts) = ts else {
4576+
return Some(None);
4577+
};
4578+
let ts_seconds = *ts / 1_000_000_000;
4579+
let ts_nanos = (*ts % 1_000_000_000) as u32;
4580+
let dt = NaiveDateTime::from_timestamp_opt(ts_seconds, ts_nanos).map(|dt| Some(dt));
4581+
return dt;
4582+
};
4583+
46304584
let ScalarValue::Utf8(str) = literal else {
46314585
return None;
46324586
};

0 commit comments

Comments
 (0)