1use oxisql_core::OxiSqlError;
4use sqlparser::ast::Expr;
5
6use crate::plan::{JoinType, LogicalPlan, SortExpr, WindowFunctionDef};
7use crate::{agg, setops, window};
8
9pub fn plan_statement(stmt: &sqlparser::ast::Statement) -> Result<LogicalPlan, OxiSqlError> {
20 match stmt {
21 sqlparser::ast::Statement::Query(query) => plan_query(query),
22 sqlparser::ast::Statement::Insert(insert) => {
23 let columns: Vec<String> = insert
24 .columns
25 .iter()
26 .filter_map(|col| {
27 col.0
28 .last()
29 .and_then(|p| p.as_ident())
30 .map(|id| id.value.clone())
31 })
32 .collect();
33 let rows = insert
34 .source
35 .as_ref()
36 .and_then(|src| {
37 if let sqlparser::ast::SetExpr::Values(ref vals) = *src.body {
38 Some(vals.rows.len())
39 } else {
40 None
41 }
42 })
43 .unwrap_or(0);
44 Ok(LogicalPlan::Values { columns, rows })
45 }
46 sqlparser::ast::Statement::Update(update) => {
47 let (table, alias) = table_factor_name_alias(&update.table.relation);
48 let base = LogicalPlan::Scan {
49 table,
50 alias,
51 limit: None,
52 };
53 Ok(match &update.selection {
54 Some(pred) => LogicalPlan::Filter {
55 input: Box::new(base),
56 predicate: pred.to_string(),
57 },
58 None => base,
59 })
60 }
61 sqlparser::ast::Statement::Delete(delete) => {
62 let base = match &delete.from {
63 sqlparser::ast::FromTable::WithFromKeyword(twjs) => build_scan_from_twjs(twjs),
64 sqlparser::ast::FromTable::WithoutKeyword(twjs) => build_scan_from_twjs(twjs),
65 };
66 Ok(match &delete.selection {
67 Some(pred) => LogicalPlan::Filter {
68 input: Box::new(base),
69 predicate: pred.to_string(),
70 },
71 None => base,
72 })
73 }
74 _ => Ok(LogicalPlan::Empty),
75 }
76}
77
78pub fn plan_query(query: &sqlparser::ast::Query) -> Result<LogicalPlan, OxiSqlError> {
84 let mut plan = plan_set_expr(&query.body)?;
85
86 if let Some(ref order_by) = query.order_by {
88 if let sqlparser::ast::OrderByKind::Expressions(ref exprs) = order_by.kind {
89 if !exprs.is_empty() {
90 let sort_keys: Vec<SortExpr> = exprs
91 .iter()
92 .map(|e| SortExpr {
93 column: e.expr.to_string(),
94 ascending: e.options.asc.unwrap_or(true),
95 })
96 .collect();
97 plan = LogicalPlan::Sort {
98 input: Box::new(plan),
99 order_by: sort_keys,
100 };
101 }
102 }
103 }
104
105 if let Some(ref limit_clause) = query.limit_clause {
107 let (count, offset) = extract_limit_offset(limit_clause);
108 if count.is_some() || offset.is_some() {
109 plan = LogicalPlan::Limit {
110 input: Box::new(plan),
111 count,
112 offset,
113 };
114 }
115 }
116
117 if let Some(ref with) = query.with {
120 let recursive = with.recursive;
121 for cte in with.cte_tables.iter().rev() {
122 let cte_name = cte.alias.name.value.clone();
123 let inner_plan = plan_query(&cte.query)?;
124 plan = LogicalPlan::Cte {
125 name: cte_name,
126 query: Box::new(inner_plan),
127 recursive,
128 };
129 }
130 }
131
132 Ok(plan)
133}
134
135pub(crate) fn plan_set_expr(body: &sqlparser::ast::SetExpr) -> Result<LogicalPlan, OxiSqlError> {
137 match body {
138 sqlparser::ast::SetExpr::Select(sel) => plan_select(sel),
139 sqlparser::ast::SetExpr::Query(inner) => plan_query(inner),
140 sqlparser::ast::SetExpr::SetOperation {
141 op,
142 set_quantifier,
143 left,
144 right,
145 } => setops::plan_set_operation(op, set_quantifier, left, right, &plan_set_expr),
146 _ => Ok(LogicalPlan::Empty),
147 }
148}
149
150fn extract_subquery_from_select_item(
152 item: &sqlparser::ast::SelectItem,
153) -> Result<Option<LogicalPlan>, OxiSqlError> {
154 let expr = match item {
155 sqlparser::ast::SelectItem::UnnamedExpr(e) => e,
156 sqlparser::ast::SelectItem::ExprWithAlias { expr: e, .. } => e,
157 _ => return Ok(None),
158 };
159 if let Expr::Subquery(query) = expr {
160 Ok(Some(plan_query(query)?))
161 } else {
162 Ok(None)
163 }
164}
165
166pub(crate) fn plan_select(sel: &sqlparser::ast::Select) -> Result<LogicalPlan, OxiSqlError> {
168 let mut plan = build_from_plan(&sel.from);
170
171 if let Some(ref pred) = sel.selection {
173 match pred {
174 Expr::Exists { subquery, negated } => {
175 let inner = plan_query(subquery)?;
176 plan = LogicalPlan::Exists {
177 subquery: Box::new(inner),
178 negated: *negated,
179 };
180 }
181 Expr::InSubquery {
182 expr,
183 subquery,
184 negated,
185 } => {
186 let inner = plan_query(subquery)?;
187 plan = LogicalPlan::InSubquery {
188 expr: expr.to_string(),
189 subquery: Box::new(inner),
190 negated: *negated,
191 };
192 }
193 other => {
194 plan = LogicalPlan::Filter {
195 input: Box::new(plan),
196 predicate: other.to_string(),
197 };
198 }
199 }
200 }
201
202 let group_by_exprs: Vec<String> = match &sel.group_by {
204 sqlparser::ast::GroupByExpr::Expressions(exprs, _) => {
205 exprs.iter().map(|e| e.to_string()).collect()
206 }
207 sqlparser::ast::GroupByExpr::All(_) => vec!["*".to_string()],
208 };
209 let aggregates: Vec<String> = sel
210 .projection
211 .iter()
212 .filter(|item| agg::projection_item_is_aggregate(item))
213 .map(|item| item.to_string())
214 .collect();
215 if !group_by_exprs.is_empty() || !aggregates.is_empty() {
216 plan = LogicalPlan::Aggregate {
217 input: Box::new(plan),
218 group_by: group_by_exprs,
219 aggregates,
220 };
221 if let Some(ref having_expr) = sel.having {
223 plan = LogicalPlan::Filter {
224 input: Box::new(plan),
225 predicate: having_expr.to_string(),
226 };
227 }
228 }
229
230 let all_wildcard = sel.projection.iter().all(|item| {
232 matches!(
233 item,
234 sqlparser::ast::SelectItem::Wildcard(_)
235 | sqlparser::ast::SelectItem::QualifiedWildcard(_, _)
236 )
237 });
238 if !all_wildcard
239 && !matches!(plan, LogicalPlan::Aggregate { .. })
240 && !matches!(plan, LogicalPlan::Filter {
241 input: ref i, ..
242 } if matches!(i.as_ref(), LogicalPlan::Aggregate { .. }))
243 {
244 let subquery_plans: Vec<LogicalPlan> = sel
246 .projection
247 .iter()
248 .filter_map(|item| extract_subquery_from_select_item(item).ok().flatten())
249 .collect();
250 if !subquery_plans.is_empty() {
251 for sub_plan in subquery_plans {
252 let alias = sel.projection.iter().find_map(|it| match it {
253 sqlparser::ast::SelectItem::ExprWithAlias {
254 expr: Expr::Subquery(_),
255 alias,
256 } => Some(alias.value.clone()),
257 _ => None,
258 });
259 plan = LogicalPlan::Subquery {
260 query: Box::new(sub_plan),
261 alias,
262 };
263 }
264 } else {
265 let columns: Vec<String> = sel.projection.iter().map(|item| item.to_string()).collect();
266 plan = LogicalPlan::Project {
267 input: Box::new(plan),
268 columns,
269 };
270 }
271 }
272
273 let window_defs: Vec<WindowFunctionDef> = sel
275 .projection
276 .iter()
277 .filter(|item| window::select_item_is_windowed(item))
278 .filter_map(window::extract_window_def)
279 .collect();
280 if !window_defs.is_empty() {
281 plan = LogicalPlan::Window {
282 input: Box::new(plan),
283 functions: window_defs,
284 };
285 }
286
287 Ok(plan)
288}
289
290pub(crate) fn build_from_plan(from: &[sqlparser::ast::TableWithJoins]) -> LogicalPlan {
294 if from.is_empty() {
295 return LogicalPlan::Empty;
296 }
297
298 let mut plan = build_table_with_joins_plan(&from[0]);
300
301 for twj in from.iter().skip(1) {
303 let right = build_table_with_joins_plan(twj);
304 plan = LogicalPlan::Join {
305 left: Box::new(plan),
306 right: Box::new(right),
307 on: String::new(),
308 join_type: JoinType::Cross,
309 algo_hint: None,
310 };
311 }
312
313 plan
314}
315
316fn build_table_with_joins_plan(twj: &sqlparser::ast::TableWithJoins) -> LogicalPlan {
318 let (table, alias) = table_factor_name_alias(&twj.relation);
319 let mut plan = LogicalPlan::Scan {
320 table,
321 alias,
322 limit: None,
323 };
324
325 for join in &twj.joins {
326 let (jt, alias) = table_factor_name_alias(&join.relation);
327 let right = LogicalPlan::Scan {
328 table: jt,
329 alias,
330 limit: None,
331 };
332
333 let (join_type, on_expr) = join_operator_info(&join.join_operator);
334
335 plan = LogicalPlan::Join {
336 left: Box::new(plan),
337 right: Box::new(right),
338 on: on_expr,
339 join_type,
340 algo_hint: None,
341 };
342 }
343
344 plan
345}
346
347pub(crate) fn table_factor_name_alias(
349 factor: &sqlparser::ast::TableFactor,
350) -> (String, Option<String>) {
351 match factor {
352 sqlparser::ast::TableFactor::Table { name, alias, .. } => {
353 let tname = name.to_string();
354 let talias = alias.as_ref().map(|a| a.name.value.clone());
355 (tname, talias)
356 }
357 other => (other.to_string(), None),
358 }
359}
360
361fn join_operator_info(op: &sqlparser::ast::JoinOperator) -> (JoinType, String) {
363 let constraint_on = |c: &sqlparser::ast::JoinConstraint| -> String {
364 match c {
365 sqlparser::ast::JoinConstraint::On(expr) => expr.to_string(),
366 _ => String::new(),
367 }
368 };
369
370 match op {
371 sqlparser::ast::JoinOperator::Join(c)
372 | sqlparser::ast::JoinOperator::Inner(c)
373 | sqlparser::ast::JoinOperator::Semi(c)
374 | sqlparser::ast::JoinOperator::LeftSemi(c)
375 | sqlparser::ast::JoinOperator::RightSemi(c)
376 | sqlparser::ast::JoinOperator::Anti(c)
377 | sqlparser::ast::JoinOperator::LeftAnti(c)
378 | sqlparser::ast::JoinOperator::RightAnti(c)
379 | sqlparser::ast::JoinOperator::StraightJoin(c) => (JoinType::Inner, constraint_on(c)),
380 sqlparser::ast::JoinOperator::Left(c) | sqlparser::ast::JoinOperator::LeftOuter(c) => {
381 (JoinType::Left, constraint_on(c))
382 }
383 sqlparser::ast::JoinOperator::Right(c) | sqlparser::ast::JoinOperator::RightOuter(c) => {
384 (JoinType::Right, constraint_on(c))
385 }
386 sqlparser::ast::JoinOperator::FullOuter(c) => (JoinType::Full, constraint_on(c)),
387 sqlparser::ast::JoinOperator::CrossJoin(c) => (JoinType::Cross, constraint_on(c)),
388 _ => (JoinType::Inner, String::new()),
389 }
390}
391
392pub(crate) fn build_scan_from_twjs(twjs: &[sqlparser::ast::TableWithJoins]) -> LogicalPlan {
394 build_from_plan(twjs)
395}
396
397pub(crate) fn collect_outer_scope(plan: &LogicalPlan) -> Vec<String> {
402 let mut out = Vec::new();
403 collect_outer_scope_inner(plan, &mut out);
404 out
405}
406
407fn collect_outer_scope_inner(plan: &LogicalPlan, out: &mut Vec<String>) {
408 match plan {
409 LogicalPlan::Scan { table, alias, .. } => {
410 out.push(table.clone());
411 if let Some(a) = alias {
412 out.push(a.clone());
413 }
414 }
415 LogicalPlan::Filter { input, .. }
416 | LogicalPlan::Project { input, .. }
417 | LogicalPlan::Aggregate { input, .. }
418 | LogicalPlan::Sort { input, .. }
419 | LogicalPlan::Limit { input, .. }
420 | LogicalPlan::Window { input, .. }
421 | LogicalPlan::Compute { input, .. } => collect_outer_scope_inner(input, out),
422 LogicalPlan::Join { left, right, .. } | LogicalPlan::SetOp { left, right, .. } => {
423 collect_outer_scope_inner(left, out);
424 collect_outer_scope_inner(right, out);
425 }
426 _ => {}
427 }
428}
429
430pub fn plan_statement_with_opts(
436 stmt: &sqlparser::ast::Statement,
437 opts: &crate::decorrelate::PlannerOptions,
438) -> Result<LogicalPlan, OxiSqlError> {
439 match stmt {
440 sqlparser::ast::Statement::Query(query) => plan_query_with_opts(query, opts),
441 other => plan_statement(other),
442 }
443}
444
445pub(crate) fn plan_query_with_opts(
447 query: &sqlparser::ast::Query,
448 opts: &crate::decorrelate::PlannerOptions,
449) -> Result<LogicalPlan, OxiSqlError> {
450 let mut plan = plan_set_expr_with_opts(&query.body, opts)?;
451
452 if let Some(ref order_by) = query.order_by {
454 if let sqlparser::ast::OrderByKind::Expressions(ref exprs) = order_by.kind {
455 if !exprs.is_empty() {
456 let sort_keys: Vec<SortExpr> = exprs
457 .iter()
458 .map(|e| SortExpr {
459 column: e.expr.to_string(),
460 ascending: e.options.asc.unwrap_or(true),
461 })
462 .collect();
463 plan = LogicalPlan::Sort {
464 input: Box::new(plan),
465 order_by: sort_keys,
466 };
467 }
468 }
469 }
470
471 if let Some(ref limit_clause) = query.limit_clause {
473 let (count, offset) = extract_limit_offset(limit_clause);
474 if count.is_some() || offset.is_some() {
475 plan = LogicalPlan::Limit {
476 input: Box::new(plan),
477 count,
478 offset,
479 };
480 }
481 }
482
483 if let Some(ref with) = query.with {
485 let recursive = with.recursive;
486 for cte in with.cte_tables.iter().rev() {
487 let cte_name = cte.alias.name.value.clone();
488 let inner_plan = plan_query_with_opts(&cte.query, opts)?;
489 plan = LogicalPlan::Cte {
490 name: cte_name,
491 query: Box::new(inner_plan),
492 recursive,
493 };
494 }
495 }
496
497 Ok(plan)
498}
499
500fn plan_set_expr_with_opts(
501 body: &sqlparser::ast::SetExpr,
502 opts: &crate::decorrelate::PlannerOptions,
503) -> Result<LogicalPlan, OxiSqlError> {
504 match body {
505 sqlparser::ast::SetExpr::Select(sel) => plan_select_with_opts(sel, opts),
506 sqlparser::ast::SetExpr::Query(inner) => plan_query_with_opts(inner, opts),
507 sqlparser::ast::SetExpr::SetOperation {
508 op,
509 set_quantifier,
510 left,
511 right,
512 } => setops::plan_set_operation(op, set_quantifier, left, right, &|b| {
513 plan_set_expr_with_opts(b, opts)
514 }),
515 _ => Ok(LogicalPlan::Empty),
516 }
517}
518
519fn plan_select_with_opts(
521 sel: &sqlparser::ast::Select,
522 opts: &crate::decorrelate::PlannerOptions,
523) -> Result<LogicalPlan, OxiSqlError> {
524 let mut plan = build_from_plan(&sel.from);
526
527 if let Some(ref pred) = sel.selection {
529 if opts.decorrelate {
530 let outer_scope = collect_outer_scope(&plan);
531 plan = crate::decorrelate::apply_decorrelated_where(plan, pred, &outer_scope)?;
532 } else {
533 match pred {
534 Expr::Exists { subquery, negated } => {
535 let inner = plan_query_with_opts(subquery, opts)?;
536 plan = LogicalPlan::Exists {
537 subquery: Box::new(inner),
538 negated: *negated,
539 };
540 }
541 Expr::InSubquery {
542 expr,
543 subquery,
544 negated,
545 } => {
546 let inner = plan_query_with_opts(subquery, opts)?;
547 plan = LogicalPlan::InSubquery {
548 expr: expr.to_string(),
549 subquery: Box::new(inner),
550 negated: *negated,
551 };
552 }
553 other => {
554 plan = LogicalPlan::Filter {
555 input: Box::new(plan),
556 predicate: other.to_string(),
557 };
558 }
559 }
560 }
561 }
562
563 let group_by_exprs: Vec<String> = match &sel.group_by {
565 sqlparser::ast::GroupByExpr::Expressions(exprs, _) => {
566 exprs.iter().map(|e| e.to_string()).collect()
567 }
568 sqlparser::ast::GroupByExpr::All(_) => vec!["*".to_string()],
569 };
570 let aggregates: Vec<String> = sel
571 .projection
572 .iter()
573 .filter(|item| agg::projection_item_is_aggregate(item))
574 .map(|item| item.to_string())
575 .collect();
576 if !group_by_exprs.is_empty() || !aggregates.is_empty() {
577 plan = LogicalPlan::Aggregate {
578 input: Box::new(plan),
579 group_by: group_by_exprs,
580 aggregates,
581 };
582 if let Some(ref having_expr) = sel.having {
583 plan = LogicalPlan::Filter {
584 input: Box::new(plan),
585 predicate: having_expr.to_string(),
586 };
587 }
588 }
589
590 let all_wildcard = sel.projection.iter().all(|item| {
592 matches!(
593 item,
594 sqlparser::ast::SelectItem::Wildcard(_)
595 | sqlparser::ast::SelectItem::QualifiedWildcard(_, _)
596 )
597 });
598 if !all_wildcard
599 && !matches!(plan, LogicalPlan::Aggregate { .. })
600 && !matches!(plan, LogicalPlan::Filter {
601 input: ref i, ..
602 } if matches!(i.as_ref(), LogicalPlan::Aggregate { .. }))
603 {
604 let subquery_plans: Vec<LogicalPlan> = sel
605 .projection
606 .iter()
607 .filter_map(|item| extract_subquery_from_select_item(item).ok().flatten())
608 .collect();
609 if !subquery_plans.is_empty() {
610 for sub_plan in subquery_plans {
611 let alias = sel.projection.iter().find_map(|it| match it {
612 sqlparser::ast::SelectItem::ExprWithAlias {
613 expr: Expr::Subquery(_),
614 alias,
615 } => Some(alias.value.clone()),
616 _ => None,
617 });
618 plan = LogicalPlan::Subquery {
619 query: Box::new(sub_plan),
620 alias,
621 };
622 }
623 } else {
624 let columns: Vec<String> = sel.projection.iter().map(|item| item.to_string()).collect();
625 plan = LogicalPlan::Project {
626 input: Box::new(plan),
627 columns,
628 };
629 }
630 }
631
632 let window_defs: Vec<WindowFunctionDef> = sel
634 .projection
635 .iter()
636 .filter(|item| window::select_item_is_windowed(item))
637 .filter_map(window::extract_window_def)
638 .collect();
639 if !window_defs.is_empty() {
640 plan = LogicalPlan::Window {
641 input: Box::new(plan),
642 functions: window_defs,
643 };
644 }
645
646 Ok(plan)
647}
648
649fn extract_limit_offset(clause: &sqlparser::ast::LimitClause) -> (Option<u64>, Option<u64>) {
651 match clause {
652 sqlparser::ast::LimitClause::LimitOffset { limit, offset, .. } => {
653 let count = limit.as_ref().and_then(expr_to_u64);
654 let off = offset.as_ref().and_then(|o| expr_to_u64(&o.value));
655 (count, off)
656 }
657 sqlparser::ast::LimitClause::OffsetCommaLimit { offset, limit } => {
658 let count = expr_to_u64(limit);
659 let off = expr_to_u64(offset);
660 (count, Some(off.unwrap_or(0)))
661 }
662 }
663}
664
665fn expr_to_u64(expr: &Expr) -> Option<u64> {
667 if let Expr::Value(sqlparser::ast::ValueWithSpan {
668 value: sqlparser::ast::Value::Number(ref s, _),
669 ..
670 }) = expr
671 {
672 s.parse::<u64>().ok()
673 } else {
674 None
675 }
676}