linkrs_query/optimizer/heuristic/join_optimization/
left_join_to_inner_join.rs1use crate::optimizer::heuristic::context::RewriteContext;
27use crate::optimizer::heuristic::pattern::Pattern;
28use crate::optimizer::heuristic::result::{RewriteError, RewriteResult, TransformResult};
29use crate::optimizer::heuristic::rule::{PushDownRule, RewriteRule};
30use crate::planning::plan::core::nodes::base::plan_node_traits::SingleInputNode;
31use crate::planning::plan::core::nodes::join::join_node::{InnerJoinNode, LeftJoinNode};
32use crate::planning::plan::core::nodes::operation::filter_node::FilterNode;
33use crate::planning::plan::PlanNodeEnum;
34use linkrs_core::types::expr::contextual::ContextualExpression;
35use linkrs_core::types::operators::BinaryOperator;
36use linkrs_core::Expression;
37
38#[derive(Debug)]
42pub struct LeftJoinToInnerJoinRule;
43
44impl LeftJoinToInnerJoinRule {
45 pub fn new() -> Self {
46 Self
47 }
48
49 fn check_is_not_null_condition(&self, expr: &Expression, right_col_names: &[String]) -> bool {
50 match expr {
51 Expression::Function { name, args } if name == "is_not_null" && args.len() == 1 => {
52 if let Expression::Variable(var_name) = args[0].as_expr() {
53 right_col_names.contains(var_name)
54 } else {
55 false
56 }
57 }
58 Expression::Binary { left, op, right } if *op == BinaryOperator::And => {
59 self.check_is_not_null_condition(left, right_col_names)
60 || self.check_is_not_null_condition(right, right_col_names)
61 }
62 Expression::Binary { left, op, right } if *op == BinaryOperator::Or => {
63 self.check_is_not_null_condition(left, right_col_names)
64 && self.check_is_not_null_condition(right, right_col_names)
65 }
66 Expression::Binary { left, op, right } => {
67 if *op == BinaryOperator::NotEqual {
68 if let (
69 Expression::Variable(var_name),
70 Expression::Literal(linkrs_core::Value::Null(_)),
71 ) = (left.as_ref(), right.as_ref())
72 {
73 return right_col_names.contains(var_name);
74 }
75 if let (
76 Expression::Literal(linkrs_core::Value::Null(_)),
77 Expression::Variable(var_name),
78 ) = (left.as_ref(), right.as_ref())
79 {
80 return right_col_names.contains(var_name);
81 }
82 }
83 false
84 }
85 _ => false,
86 }
87 }
88
89 fn has_not_null_on_right(
90 &self,
91 expr: &ContextualExpression,
92 right_col_names: &[String],
93 ) -> bool {
94 if let Some(expr_meta) = expr.expression() {
95 self.check_is_not_null_condition(expr_meta.inner(), right_col_names)
96 } else {
97 false
98 }
99 }
100
101 fn remove_not_null_conditions(
102 &self,
103 expr: &Expression,
104 right_col_names: &[String],
105 ) -> Option<Expression> {
106 match expr {
107 Expression::Function { name, args } if name == "is_not_null" && args.len() == 1 => {
108 if let Expression::Variable(var_name) = args[0].as_expr() {
109 if right_col_names.contains(var_name) {
110 return None;
111 }
112 }
113 Some(expr.clone())
114 }
115 Expression::Binary { left, op, right } if *op == BinaryOperator::And => {
116 let left_result = self.remove_not_null_conditions(left, right_col_names);
117 let right_result = self.remove_not_null_conditions(right, right_col_names);
118 match (left_result, right_result) {
119 (Some(l), Some(r)) => Some(Expression::and(l, r)),
120 (Some(l), None) => Some(l),
121 (None, Some(r)) => Some(r),
122 (None, None) => None,
123 }
124 }
125 Expression::Binary { left, op, right } if *op == BinaryOperator::Or => {
126 let left_result = self.remove_not_null_conditions(left, right_col_names);
127 let right_result = self.remove_not_null_conditions(right, right_col_names);
128 match (left_result, right_result) {
129 (Some(l), Some(r)) => Some(Expression::or(l, r)),
130 (Some(l), None) => Some(l),
131 (None, Some(r)) => Some(r),
132 (None, None) => None,
133 }
134 }
135 Expression::Binary { left, op, right } => {
136 if *op == BinaryOperator::NotEqual {
137 if let (
138 Expression::Variable(var_name),
139 Expression::Literal(linkrs_core::Value::Null(_)),
140 ) = (left.as_ref(), right.as_ref())
141 {
142 if right_col_names.contains(var_name) {
143 return None;
144 }
145 }
146 if let (
147 Expression::Literal(linkrs_core::Value::Null(_)),
148 Expression::Variable(var_name),
149 ) = (left.as_ref(), right.as_ref())
150 {
151 if right_col_names.contains(var_name) {
152 return None;
153 }
154 }
155 }
156 Some(expr.clone())
157 }
158 _ => Some(expr.clone()),
159 }
160 }
161
162 fn apply_to_left_join(
163 &self,
164 filter: &FilterNode,
165 join: &LeftJoinNode,
166 ) -> RewriteResult<Option<TransformResult>> {
167 let right_col_names = join.right_input().col_names().to_vec();
168 let filter_condition = filter.condition();
169
170 if !self.has_not_null_on_right(filter_condition, &right_col_names) {
171 return Ok(None);
172 }
173
174 let new_join = InnerJoinNode::new(
175 join.left_input().clone(),
176 join.right_input().clone(),
177 join.hash_keys().to_vec(),
178 join.probe_keys().to_vec(),
179 )
180 .map_err(|e| {
181 RewriteError::rewrite_failed(format!("Failed to create InnerJoinNode: {:?}", e))
182 })?;
183
184 if let Some(expr_meta) = filter_condition.expression() {
185 let remaining = self.remove_not_null_conditions(expr_meta.inner(), &right_col_names);
186
187 let mut result = TransformResult::new();
188
189 if let Some(rem_expr) = remaining {
190 let ctx = filter_condition.context().clone();
191 let meta = linkrs_core::types::expr::ExpressionMeta::new(rem_expr);
192 let id = ctx.register_expression(meta);
193 let new_ctx_expr = ContextualExpression::new(id, ctx);
194
195 let new_filter = FilterNode::new(PlanNodeEnum::InnerJoin(new_join), new_ctx_expr)
196 .map_err(|e| {
197 RewriteError::rewrite_failed(format!("Failed to create FilterNode: {:?}", e))
198 })?;
199
200 result.erase_curr = true;
201 result.add_new_node(PlanNodeEnum::Filter(new_filter));
202 } else {
203 result.erase_curr = true;
204 result.add_new_node(PlanNodeEnum::InnerJoin(new_join));
205 }
206
207 return Ok(Some(result));
208 }
209
210 Ok(None)
211 }
212}
213
214impl Default for LeftJoinToInnerJoinRule {
215 fn default() -> Self {
216 Self::new()
217 }
218}
219
220impl RewriteRule for LeftJoinToInnerJoinRule {
221 fn name(&self) -> &'static str {
222 "LeftJoinToInnerJoinRule"
223 }
224
225 fn pattern(&self) -> Pattern {
226 Pattern::new_with_name("Filter")
227 }
228
229 fn apply(
230 &self,
231 _ctx: &mut RewriteContext,
232 node: &PlanNodeEnum,
233 ) -> RewriteResult<Option<TransformResult>> {
234 let filter = match node {
235 PlanNodeEnum::Filter(n) => n,
236 _ => return Ok(None),
237 };
238
239 let input = filter.input();
240
241 match input {
242 PlanNodeEnum::LeftJoin(join) => self.apply_to_left_join(filter, join),
243 _ => Ok(None),
244 }
245 }
246}
247
248impl PushDownRule for LeftJoinToInnerJoinRule {
249 fn can_push_down(&self, node: &PlanNodeEnum, target: &PlanNodeEnum) -> bool {
250 matches!(node, PlanNodeEnum::Filter(_)) && matches!(target, PlanNodeEnum::LeftJoin(_))
251 }
252
253 fn push_down(
254 &self,
255 ctx: &mut RewriteContext,
256 node: &PlanNodeEnum,
257 _target: &PlanNodeEnum,
258 ) -> RewriteResult<Option<TransformResult>> {
259 self.apply(ctx, node)
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266
267 #[test]
268 fn test_rule_name() {
269 let rule = LeftJoinToInnerJoinRule::new();
270 assert_eq!(rule.name(), "LeftJoinToInnerJoinRule");
271 }
272
273 #[test]
274 fn test_rule_pattern() {
275 let rule = LeftJoinToInnerJoinRule::new();
276 let pattern = rule.pattern();
277 assert!(pattern.node.is_some());
278 }
279
280 #[test]
281 fn test_check_is_not_null_condition() {
282 let rule = LeftJoinToInnerJoinRule::new();
283 let right_cols = vec!["b_col".to_string()];
284
285 let expr = Expression::function("is_not_null", vec![Expression::variable("b_col")]);
286 assert!(rule.check_is_not_null_condition(&expr, &right_cols));
287
288 let expr2 = Expression::function("is_not_null", vec![Expression::variable("a_col")]);
289 assert!(!rule.check_is_not_null_condition(&expr2, &right_cols));
290 }
291}