Skip to content

Commit 7b1ff0d

Browse files
authored
feat(cubesql): Support NOT SQL push down (#7422)
1 parent 8d925d2 commit 7b1ff0d

File tree

7 files changed

+140
-1
lines changed

7 files changed

+140
-1
lines changed

packages/cubejs-schema-compiler/src/adapter/BaseQuery.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2486,6 +2486,7 @@ class BaseQuery {
24862486
window_function: '{{ fun_call }} OVER ({% if partition_by_concat %}PARTITION BY {{ partition_by_concat }}{% if order_by_concat %} {% endif %}{% endif %}{% if order_by_concat %}ORDER BY {{ order_by_concat }}{% endif %})',
24872487
in_list: '{{ expr }} {% if negated %}NOT {% endif %}IN ({{ in_exprs_concat }})',
24882488
negative: '-({{ expr }})',
2489+
not: 'NOT ({{ expr }})',
24892490
},
24902491
quotes: {
24912492
identifiers: '"',

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -930,7 +930,27 @@ impl CubeScanWrapperNode {
930930
// Expr::Like(_) => {}-=
931931
// Expr::ILike(_) => {}
932932
// Expr::SimilarTo(_) => {}
933-
// Expr::Not(_) => {}
933+
Expr::Not(expr) => {
934+
let (expr, sql_query) = Self::generate_sql_for_expr(
935+
plan.clone(),
936+
sql_query,
937+
sql_generator.clone(),
938+
*expr,
939+
ungrouped_scan_node.clone(),
940+
)
941+
.await?;
942+
let resulting_sql =
943+
sql_generator
944+
.get_sql_templates()
945+
.not_expr(expr)
946+
.map_err(|e| {
947+
DataFusionError::Internal(format!(
948+
"Can't generate SQL for not expr: {}",
949+
e
950+
))
951+
})?;
952+
Ok((resulting_sql, sql_query))
953+
}
934954
Expr::IsNotNull(expr) => {
935955
let (expr, sql_query) = Self::generate_sql_for_expr(
936956
plan.clone(),

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19767,4 +19767,38 @@ ORDER BY \"COUNT(count)\" DESC"
1976719767
.sql
1976819768
.contains("-("));
1976919769
}
19770+
19771+
#[tokio::test]
19772+
async fn test_not_expr() {
19773+
if !Rewriter::sql_push_down_enabled() {
19774+
return;
19775+
}
19776+
init_logger();
19777+
19778+
let query_plan = convert_select_to_query_plan(
19779+
"
19780+
SELECT NOT has_subscription AS has_no_subscription
19781+
FROM KibanaSampleDataEcommerce AS k
19782+
GROUP BY 1
19783+
ORDER BY 1 DESC
19784+
"
19785+
.to_string(),
19786+
DatabaseProtocol::PostgreSQL,
19787+
)
19788+
.await;
19789+
19790+
let physical_plan = query_plan.as_physical_plan().await.unwrap();
19791+
println!(
19792+
"Physical plan: {}",
19793+
displayable(physical_plan.as_ref()).indent()
19794+
);
19795+
19796+
let logical_plan = query_plan.as_logical_plan();
19797+
assert!(logical_plan
19798+
.find_cube_scan_wrapper()
19799+
.wrapped_sql
19800+
.unwrap()
19801+
.sql
19802+
.contains("NOT ("));
19803+
}
1977019804
}

rust/cubesql/cubesql/src/compile/rewrite/rules/wrapper/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ mod is_null_expr;
1212
mod limit;
1313
mod literal;
1414
mod negative_expr;
15+
mod not_expr;
1516
mod order;
1617
mod projection;
1718
mod scalar_function;
@@ -64,6 +65,7 @@ impl RewriteRules for WrapperRules {
6465
self.literal_rules(&mut rules);
6566
self.in_list_expr_rules(&mut rules);
6667
self.negative_expr_rules(&mut rules);
68+
self.not_expr_rules(&mut rules);
6769

6870
rules
6971
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
use crate::{
2+
compile::rewrite::{
3+
analysis::LogicalPlanAnalysis, not_expr, rewrite, rules::wrapper::WrapperRules,
4+
transforming_rewrite, wrapper_pullup_replacer, wrapper_pushdown_replacer,
5+
LogicalPlanLanguage, WrapperPullupReplacerAliasToCube,
6+
},
7+
var, var_iter,
8+
};
9+
use egg::{EGraph, Rewrite, Subst};
10+
11+
impl WrapperRules {
12+
pub fn not_expr_rules(
13+
&self,
14+
rules: &mut Vec<Rewrite<LogicalPlanLanguage, LogicalPlanAnalysis>>,
15+
) {
16+
rules.extend(vec![
17+
rewrite(
18+
"wrapper-not-push-down",
19+
wrapper_pushdown_replacer(
20+
not_expr("?expr"),
21+
"?alias_to_cube",
22+
"?ungrouped",
23+
"?cube_members",
24+
),
25+
not_expr(wrapper_pushdown_replacer(
26+
"?expr",
27+
"?alias_to_cube",
28+
"?ungrouped",
29+
"?cube_members",
30+
)),
31+
),
32+
transforming_rewrite(
33+
"wrapper-not-pull-up",
34+
not_expr(wrapper_pullup_replacer(
35+
"?expr",
36+
"?alias_to_cube",
37+
"?ungrouped",
38+
"?cube_members",
39+
)),
40+
wrapper_pullup_replacer(
41+
not_expr("?expr"),
42+
"?alias_to_cube",
43+
"?ungrouped",
44+
"?cube_members",
45+
),
46+
self.transform_not_expr("?alias_to_cube"),
47+
),
48+
]);
49+
}
50+
51+
fn transform_not_expr(
52+
&self,
53+
alias_to_cube_var: &'static str,
54+
) -> impl Fn(&mut EGraph<LogicalPlanLanguage, LogicalPlanAnalysis>, &mut Subst) -> bool {
55+
let alias_to_cube_var = var!(alias_to_cube_var);
56+
let meta = self.cube_context.meta.clone();
57+
move |egraph, subst| {
58+
for alias_to_cube in var_iter!(
59+
egraph[subst[alias_to_cube_var]],
60+
WrapperPullupReplacerAliasToCube
61+
)
62+
.cloned()
63+
{
64+
if let Some(sql_generator) = meta.sql_generator_by_alias_to_cube(&alias_to_cube) {
65+
if sql_generator
66+
.get_sql_templates()
67+
.templates
68+
.contains_key("expressions/not")
69+
{
70+
return true;
71+
}
72+
}
73+
}
74+
false
75+
}
76+
}
77+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ pub fn get_test_tenant_ctx() -> Arc<MetaContext> {
236236
("expressions/window_function".to_string(), "{{ fun_call }} OVER ({% if partition_by %}PARTITION BY {{ partition_by }}{% if order_by %} {% endif %}{% endif %}{% if order_by %}ORDER BY {{ order_by }}{% endif %})".to_string()),
237237
("expressions/in_list".to_string(), "{{ expr }} {% if negated %}NOT {% endif %}IN ({{ in_exprs_concat }})".to_string()),
238238
("expressions/negative".to_string(), "-({{ expr }})".to_string()),
239+
("expressions/not".to_string(), "NOT ({{ expr }})".to_string()),
239240
("quotes/identifiers".to_string(), "\"".to_string()),
240241
("quotes/escape".to_string(), "\"\"".to_string()),
241242
("params/param".to_string(), "${{ param_index + 1 }}".to_string())

rust/cubesql/cubesql/src/transport/service.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,10 @@ impl SqlTemplates {
502502
self.render_template("expressions/negative", context! { expr => expr })
503503
}
504504

505+
pub fn not_expr(&self, expr: String) -> Result<String, CubeError> {
506+
self.render_template("expressions/not", context! { expr => expr })
507+
}
508+
505509
pub fn sort_expr(
506510
&self,
507511
expr: String,

0 commit comments

Comments
 (0)