1use oxisql_core::OxiSqlError;
9use sqlparser::ast::Expr;
10
11use crate::optimizer::expr_util::{collect_colrefs, join_conjuncts, render, split_conjuncts};
12use crate::plan::{JoinType, LogicalPlan};
13
14#[derive(Debug, Clone)]
18pub struct PlannerOptions {
19 pub decorrelate: bool,
22}
23
24impl Default for PlannerOptions {
25 fn default() -> Self {
26 Self { decorrelate: true }
27 }
28}
29
30pub fn decorrelate_plan(plan: LogicalPlan, outer_scope: &[String]) -> LogicalPlan {
43 match plan {
44 LogicalPlan::Filter { input, predicate } => {
45 let input = decorrelate_plan(*input, outer_scope);
46 let mut scope: Vec<String> = collect_plan_table_names(&input);
48 scope.extend_from_slice(outer_scope);
49
50 match apply_decorrelated_filter(input, &predicate, &scope) {
51 Ok(new_plan) => new_plan,
52 Err(_) => LogicalPlan::Filter {
53 input: Box::new(decorrelate_plan(
54 LogicalPlan::Scan {
55 table: "__err__".to_string(),
56 alias: None,
57 limit: None,
58 },
59 outer_scope,
60 )),
61 predicate,
62 },
63 }
64 }
65
66 LogicalPlan::Project { input, columns } => LogicalPlan::Project {
68 input: Box::new(decorrelate_plan(*input, outer_scope)),
69 columns,
70 },
71 LogicalPlan::Aggregate {
72 input,
73 group_by,
74 aggregates,
75 } => LogicalPlan::Aggregate {
76 input: Box::new(decorrelate_plan(*input, outer_scope)),
77 group_by,
78 aggregates,
79 },
80 LogicalPlan::Sort { input, order_by } => LogicalPlan::Sort {
81 input: Box::new(decorrelate_plan(*input, outer_scope)),
82 order_by,
83 },
84 LogicalPlan::Limit {
85 input,
86 count,
87 offset,
88 } => LogicalPlan::Limit {
89 input: Box::new(decorrelate_plan(*input, outer_scope)),
90 count,
91 offset,
92 },
93 LogicalPlan::Window { input, functions } => LogicalPlan::Window {
94 input: Box::new(decorrelate_plan(*input, outer_scope)),
95 functions,
96 },
97 LogicalPlan::Compute { input, bindings } => LogicalPlan::Compute {
98 input: Box::new(decorrelate_plan(*input, outer_scope)),
99 bindings,
100 },
101 LogicalPlan::Join {
102 left,
103 right,
104 on,
105 join_type,
106 algo_hint,
107 } => LogicalPlan::Join {
108 left: Box::new(decorrelate_plan(*left, outer_scope)),
109 right: Box::new(decorrelate_plan(*right, outer_scope)),
110 on,
111 join_type,
112 algo_hint,
113 },
114 LogicalPlan::SetOp {
115 op,
116 all,
117 left,
118 right,
119 } => LogicalPlan::SetOp {
120 op,
121 all,
122 left: Box::new(decorrelate_plan(*left, outer_scope)),
123 right: Box::new(decorrelate_plan(*right, outer_scope)),
124 },
125 LogicalPlan::Cte {
126 name,
127 query,
128 recursive,
129 } => LogicalPlan::Cte {
130 name,
131 query: Box::new(decorrelate_plan(*query, outer_scope)),
132 recursive,
133 },
134 LogicalPlan::Subquery { query, alias } => LogicalPlan::Subquery {
135 query: Box::new(decorrelate_plan(*query, outer_scope)),
136 alias,
137 },
138 LogicalPlan::Exists { subquery, negated } => LogicalPlan::Exists {
139 subquery: Box::new(decorrelate_plan(*subquery, outer_scope)),
140 negated,
141 },
142 LogicalPlan::InSubquery {
143 expr,
144 subquery,
145 negated,
146 } => LogicalPlan::InSubquery {
147 expr,
148 subquery: Box::new(decorrelate_plan(*subquery, outer_scope)),
149 negated,
150 },
151 other => other,
153 }
154}
155
156fn apply_decorrelated_filter(
162 outer_plan: LogicalPlan,
163 predicate: &str,
164 outer_scope: &[String],
165) -> Result<LogicalPlan, OxiSqlError> {
166 let Some(pred_expr) = crate::parse_predicate(predicate) else {
167 return Ok(LogicalPlan::Filter {
169 input: Box::new(outer_plan),
170 predicate: predicate.to_string(),
171 });
172 };
173
174 let conjuncts = split_conjuncts(pred_expr);
175 let mut plan = outer_plan;
176 let mut remaining: Vec<Expr> = Vec::new();
177
178 for conjunct in conjuncts {
179 match conjunct {
180 Expr::Exists {
181 ref subquery,
182 negated,
183 } => {
184 let inner = crate::planner::plan_query(subquery)?;
185 let (non_corr_plan, corr_preds) = extract_correlated_filter(inner, outer_scope);
186 if corr_preds.is_empty() {
187 remaining.push(conjunct);
189 } else {
190 let join_on = corr_preds.join(" AND ");
191 let join_type = if negated {
192 JoinType::LeftAnti
193 } else {
194 JoinType::LeftSemi
195 };
196 plan = LogicalPlan::Join {
197 left: Box::new(plan),
198 right: Box::new(non_corr_plan),
199 on: join_on,
200 join_type,
201 algo_hint: None,
202 };
203 }
204 }
205
206 Expr::InSubquery {
207 ref expr,
208 ref subquery,
209 negated,
210 } => {
211 let inner = crate::planner::plan_query(subquery)?;
212 let proj_cols = extract_projection_cols(&inner);
213 let (non_corr_plan, mut corr_preds) = extract_correlated_filter(inner, outer_scope);
214
215 if corr_preds.is_empty() {
216 remaining.push(conjunct);
218 } else {
219 let expr_str = expr.to_string();
220 if let Some(proj_col) = proj_cols.first() {
222 corr_preds.insert(0, format!("{expr_str} = {proj_col}"));
223 }
224 let join_on = corr_preds.join(" AND ");
225 let join_type = if negated {
226 JoinType::LeftAnti
227 } else {
228 JoinType::LeftSemi
229 };
230 plan = LogicalPlan::Join {
231 left: Box::new(plan),
232 right: Box::new(non_corr_plan),
233 on: join_on,
234 join_type,
235 algo_hint: None,
236 };
237 }
238 }
239
240 other => remaining.push(other),
241 }
242 }
243
244 if let Some(e) = join_conjuncts(remaining) {
246 plan = LogicalPlan::Filter {
247 input: Box::new(plan),
248 predicate: render(&e),
249 };
250 }
251
252 Ok(plan)
253}
254
255pub(crate) fn apply_decorrelated_where(
262 outer_plan: LogicalPlan,
263 pred: &Expr,
264 outer_scope: &[String],
265) -> Result<LogicalPlan, OxiSqlError> {
266 let conjuncts = split_conjuncts(pred.clone());
267 let mut plan = outer_plan;
268 let mut remaining: Vec<Expr> = Vec::new();
269
270 for conjunct in conjuncts {
271 match conjunct {
272 Expr::Exists {
273 ref subquery,
274 negated,
275 } => {
276 let opts_no_decorr = PlannerOptions { decorrelate: false };
277 let inner = crate::planner::plan_query_with_opts(subquery, &opts_no_decorr)?;
278 let (non_corr_plan, corr_preds) = extract_correlated_filter(inner, outer_scope);
279
280 if corr_preds.is_empty() {
281 remaining.push(conjunct);
282 } else {
283 let join_on = corr_preds.join(" AND ");
284 let join_type = if negated {
285 JoinType::LeftAnti
286 } else {
287 JoinType::LeftSemi
288 };
289 plan = LogicalPlan::Join {
290 left: Box::new(plan),
291 right: Box::new(non_corr_plan),
292 on: join_on,
293 join_type,
294 algo_hint: None,
295 };
296 }
297 }
298
299 Expr::InSubquery {
300 ref expr,
301 ref subquery,
302 negated,
303 } => {
304 let opts_no_decorr = PlannerOptions { decorrelate: false };
305 let inner = crate::planner::plan_query_with_opts(subquery, &opts_no_decorr)?;
306 let proj_cols = extract_projection_cols(&inner);
307 let (non_corr_plan, mut corr_preds) = extract_correlated_filter(inner, outer_scope);
308
309 if corr_preds.is_empty() {
310 remaining.push(conjunct);
311 } else {
312 let expr_str = expr.to_string();
313 if let Some(proj_col) = proj_cols.first() {
314 corr_preds.insert(0, format!("{expr_str} = {proj_col}"));
315 }
316 let join_on = corr_preds.join(" AND ");
317 let join_type = if negated {
318 JoinType::LeftAnti
319 } else {
320 JoinType::LeftSemi
321 };
322 plan = LogicalPlan::Join {
323 left: Box::new(plan),
324 right: Box::new(non_corr_plan),
325 on: join_on,
326 join_type,
327 algo_hint: None,
328 };
329 }
330 }
331
332 other => remaining.push(other),
333 }
334 }
335
336 if let Some(e) = join_conjuncts(remaining) {
337 plan = LogicalPlan::Filter {
338 input: Box::new(plan),
339 predicate: render(&e),
340 };
341 }
342
343 Ok(plan)
344}
345
346fn extract_correlated_filter(
351 plan: LogicalPlan,
352 outer_scope: &[String],
353) -> (LogicalPlan, Vec<String>) {
354 match plan {
355 LogicalPlan::Filter { input, predicate } => {
356 let Some(e) = crate::parse_predicate(&predicate) else {
357 return (LogicalPlan::Filter { input, predicate }, vec![]);
358 };
359 let conjuncts = split_conjuncts(e);
360 let mut corr: Vec<String> = Vec::new();
361 let mut non_corr: Vec<Expr> = Vec::new();
362
363 for c in conjuncts {
364 let refs = collect_colrefs(&c);
365 let is_corr = refs.iter().any(|r| {
366 r.qualifier
367 .as_deref()
368 .map(|q| outer_scope.iter().any(|s| s == q))
369 .unwrap_or(false)
370 });
371 if is_corr {
372 corr.push(render(&c));
373 } else {
374 non_corr.push(c);
375 }
376 }
377
378 let new_plan = if let Some(e) = join_conjuncts(non_corr) {
379 LogicalPlan::Filter {
380 input,
381 predicate: render(&e),
382 }
383 } else {
384 *input
385 };
386 (new_plan, corr)
387 }
388
389 LogicalPlan::Project { input, columns } => {
390 let (new_input, corr) = extract_correlated_filter(*input, outer_scope);
391 (
392 LogicalPlan::Project {
393 input: Box::new(new_input),
394 columns,
395 },
396 corr,
397 )
398 }
399
400 other => (other, vec![]),
402 }
403}
404
405fn extract_projection_cols(plan: &LogicalPlan) -> Vec<String> {
407 match plan {
408 LogicalPlan::Project { columns, .. } => columns.clone(),
409 LogicalPlan::Filter { input, .. } => extract_projection_cols(input),
410 _ => vec![],
411 }
412}
413
414fn collect_plan_table_names(plan: &LogicalPlan) -> Vec<String> {
416 let mut out = Vec::new();
417 collect_names_inner(plan, &mut out);
418 out
419}
420
421fn collect_names_inner(plan: &LogicalPlan, out: &mut Vec<String>) {
422 match plan {
423 LogicalPlan::Scan { table, alias, .. } => {
424 out.push(table.clone());
425 if let Some(a) = alias {
426 out.push(a.clone());
427 }
428 }
429 LogicalPlan::Filter { input, .. }
430 | LogicalPlan::Project { input, .. }
431 | LogicalPlan::Aggregate { input, .. }
432 | LogicalPlan::Sort { input, .. }
433 | LogicalPlan::Limit { input, .. }
434 | LogicalPlan::Window { input, .. }
435 | LogicalPlan::Compute { input, .. } => collect_names_inner(input, out),
436 LogicalPlan::Join { left, right, .. } | LogicalPlan::SetOp { left, right, .. } => {
437 collect_names_inner(left, out);
438 collect_names_inner(right, out);
439 }
440 _ => {}
441 }
442}