Skip to content

Commit 100f800

Browse files
ovrFrank-TXS
authored andcommitted
feat(cubesql): Filter push down for date_part('year', ?col) = ?literal (cube-js#9749)
Allowing filters to be pushed down with DATE_PART('year') by pushing it as inDateRange. Now, such queries will be accelerated with pre-aggregations.
1 parent cc9a7b1 commit 100f800

File tree

2 files changed

+198
-33
lines changed

2 files changed

+198
-33
lines changed

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

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9482,7 +9482,105 @@ ORDER BY "source"."str0" ASC
94829482
}
94839483

94849484
#[tokio::test]
9485-
async fn test_tableau_filter_by_year() {
9485+
async fn test_filter_date_part_by_year() {
9486+
init_testing_logger();
9487+
9488+
fn assert_expected_result(query_plan: QueryPlan) {
9489+
assert_eq!(
9490+
query_plan.as_logical_plan().find_cube_scan().request,
9491+
V1LoadRequestQuery {
9492+
measures: Some(vec!["KibanaSampleDataEcommerce.count".to_string()]),
9493+
dimensions: Some(vec![]),
9494+
segments: Some(vec![]),
9495+
time_dimensions: Some(vec![V1LoadRequestQueryTimeDimension {
9496+
dimension: "KibanaSampleDataEcommerce.order_date".to_string(),
9497+
granularity: Some("year".to_string()),
9498+
date_range: Some(json!(vec![
9499+
"2019-01-01".to_string(),
9500+
"2019-12-31".to_string(),
9501+
])),
9502+
},]),
9503+
order: Some(vec![]),
9504+
..Default::default()
9505+
}
9506+
)
9507+
}
9508+
9509+
assert_expected_result(
9510+
convert_select_to_query_plan(
9511+
r#"
9512+
SELECT
9513+
COUNT(*) AS "count",
9514+
date_part('YEAR', "KibanaSampleDataEcommerce"."order_date") AS "yr:completedAt:ok"
9515+
FROM "public"."KibanaSampleDataEcommerce" "KibanaSampleDataEcommerce"
9516+
WHERE date_part('YEAR', "KibanaSampleDataEcommerce"."order_date") = 2019
9517+
GROUP BY 2
9518+
;"#
9519+
.to_string(),
9520+
DatabaseProtocol::PostgreSQL,
9521+
)
9522+
.await,
9523+
);
9524+
9525+
// Same as above, but with string literal.
9526+
assert_expected_result(
9527+
convert_select_to_query_plan(
9528+
r#"
9529+
SELECT
9530+
COUNT(*) AS "count",
9531+
date_part('YEAR', "KibanaSampleDataEcommerce"."order_date") AS "yr:completedAt:ok"
9532+
FROM "public"."KibanaSampleDataEcommerce" "KibanaSampleDataEcommerce"
9533+
WHERE date_part('YEAR', "KibanaSampleDataEcommerce"."order_date") = '2019'
9534+
GROUP BY 2
9535+
;"#
9536+
.to_string(),
9537+
DatabaseProtocol::PostgreSQL,
9538+
)
9539+
.await,
9540+
)
9541+
}
9542+
9543+
#[tokio::test]
9544+
async fn test_filter_extract_by_year() {
9545+
init_testing_logger();
9546+
9547+
let logical_plan = convert_select_to_query_plan(
9548+
r#"
9549+
SELECT
9550+
COUNT(*) AS "count",
9551+
EXTRACT(YEAR FROM "KibanaSampleDataEcommerce"."order_date") AS "yr:completedAt:ok"
9552+
FROM "public"."KibanaSampleDataEcommerce" "KibanaSampleDataEcommerce"
9553+
WHERE EXTRACT(YEAR FROM "KibanaSampleDataEcommerce"."order_date") = 2019
9554+
GROUP BY 2
9555+
;"#
9556+
.to_string(),
9557+
DatabaseProtocol::PostgreSQL,
9558+
)
9559+
.await
9560+
.as_logical_plan();
9561+
9562+
assert_eq!(
9563+
logical_plan.find_cube_scan().request,
9564+
V1LoadRequestQuery {
9565+
measures: Some(vec!["KibanaSampleDataEcommerce.count".to_string()]),
9566+
dimensions: Some(vec![]),
9567+
segments: Some(vec![]),
9568+
time_dimensions: Some(vec![V1LoadRequestQueryTimeDimension {
9569+
dimension: "KibanaSampleDataEcommerce.order_date".to_string(),
9570+
granularity: Some("year".to_string()),
9571+
date_range: Some(json!(vec![
9572+
"2019-01-01".to_string(),
9573+
"2019-12-31".to_string(),
9574+
])),
9575+
},]),
9576+
order: Some(vec![]),
9577+
..Default::default()
9578+
}
9579+
)
9580+
}
9581+
9582+
#[tokio::test]
9583+
async fn test_tableau_filter_extract_by_year() {
94869584
init_testing_logger();
94879585

94889586
let logical_plan = convert_select_to_query_plan(

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

Lines changed: 99 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1668,8 +1668,64 @@ impl RewriteRules for FilterRules {
16681668
"?filter_aliases",
16691669
),
16701670
),
1671+
// DATE_PART('year', "KibanaSampleDataEcommerce"."order_date") = 2019
16711672
transforming_rewrite(
16721673
"extract-year-equals",
1674+
filter_replacer(
1675+
binary_expr(
1676+
self.fun_expr(
1677+
"DatePart",
1678+
vec![literal_string("YEAR"), column_expr("?column")],
1679+
),
1680+
"=",
1681+
literal_expr("?year"),
1682+
),
1683+
"?alias_to_cube",
1684+
"?members",
1685+
"?filter_aliases",
1686+
),
1687+
filter_member("?member", "FilterMemberOp:inDateRange", "?values"),
1688+
self.transform_filter_extract_year_equals(
1689+
"?year",
1690+
"?column",
1691+
"?alias_to_cube",
1692+
"?members",
1693+
"?member",
1694+
"?values",
1695+
"?filter_aliases",
1696+
),
1697+
),
1698+
// Same as the rule above, but it uses different case for granularity.
1699+
// TODO: Remove, whenever we will fix bug with granularity cases. CORE-1761
1700+
transforming_rewrite(
1701+
"extract-year-equals-lower-case",
1702+
filter_replacer(
1703+
binary_expr(
1704+
self.fun_expr(
1705+
"DatePart",
1706+
vec![literal_string("year"), column_expr("?column")],
1707+
),
1708+
"=",
1709+
literal_expr("?year"),
1710+
),
1711+
"?alias_to_cube",
1712+
"?members",
1713+
"?filter_aliases",
1714+
),
1715+
filter_member("?member", "FilterMemberOp:inDateRange", "?values"),
1716+
self.transform_filter_extract_year_equals(
1717+
"?year",
1718+
"?column",
1719+
"?alias_to_cube",
1720+
"?members",
1721+
"?member",
1722+
"?values",
1723+
"?filter_aliases",
1724+
),
1725+
),
1726+
// TRUNC(EXTRACT(YEAR FROM "KibanaSampleDataEcommerce"."order_date")) = 2019
1727+
transforming_rewrite(
1728+
"extract-trunc-year-equals",
16731729
filter_replacer(
16741730
binary_expr(
16751731
self.fun_expr(
@@ -3579,43 +3635,54 @@ impl FilterRules {
35793635
.collect();
35803636
for year in years {
35813637
for aliases in aliases_es.iter() {
3582-
if let ScalarValue::Int64(Some(year)) = year {
3583-
if !(1000..=9999).contains(&year) {
3584-
continue;
3585-
}
3586-
3587-
if let Some((member_name, cube)) = Self::filter_member_name(
3588-
egraph,
3589-
subst,
3590-
&meta_context,
3591-
alias_to_cube_var,
3592-
column_var,
3593-
members_var,
3594-
&aliases,
3595-
) {
3596-
if !cube.contains_member(&member_name) {
3638+
let year = match year {
3639+
ScalarValue::Int64(Some(year)) => year,
3640+
ScalarValue::Int32(Some(year)) => year as i64,
3641+
ScalarValue::Utf8(Some(ref year_str)) if year_str.len() == 4 => {
3642+
if let Ok(year) = year_str.parse::<i64>() {
3643+
year
3644+
} else {
35973645
continue;
35983646
}
3647+
}
3648+
_ => continue,
3649+
};
35993650

3600-
subst.insert(
3601-
member_var,
3602-
egraph.add(LogicalPlanLanguage::FilterMemberMember(
3603-
FilterMemberMember(member_name.to_string()),
3604-
)),
3605-
);
3606-
3607-
subst.insert(
3608-
values_var,
3609-
egraph.add(LogicalPlanLanguage::FilterMemberValues(
3610-
FilterMemberValues(vec![
3611-
format!("{}-01-01", year),
3612-
format!("{}-12-31", year),
3613-
]),
3614-
)),
3615-
);
3651+
if !(1000..=9999).contains(&year) {
3652+
continue;
3653+
}
36163654

3617-
return true;
3655+
if let Some((member_name, cube)) = Self::filter_member_name(
3656+
egraph,
3657+
subst,
3658+
&meta_context,
3659+
alias_to_cube_var,
3660+
column_var,
3661+
members_var,
3662+
&aliases,
3663+
) {
3664+
if !cube.contains_member(&member_name) {
3665+
continue;
36183666
}
3667+
3668+
subst.insert(
3669+
member_var,
3670+
egraph.add(LogicalPlanLanguage::FilterMemberMember(
3671+
FilterMemberMember(member_name.to_string()),
3672+
)),
3673+
);
3674+
3675+
subst.insert(
3676+
values_var,
3677+
egraph.add(LogicalPlanLanguage::FilterMemberValues(
3678+
FilterMemberValues(vec![
3679+
format!("{}-01-01", year),
3680+
format!("{}-12-31", year),
3681+
]),
3682+
)),
3683+
);
3684+
3685+
return true;
36193686
}
36203687
}
36213688
}

0 commit comments

Comments
 (0)