1use crate::ast::*;
2use crate::parser::{parse, ParseError};
3use crate::plan::*;
4
5type RangeBound = (String, Option<(Expr, bool)>, Option<(Expr, bool)>);
7
8#[derive(Debug)]
10pub enum PlanError {
11 Parse(ParseError),
13}
14
15impl PlanError {
16 pub fn message(&self) -> String {
18 self.to_string()
19 }
20}
21
22impl std::fmt::Display for PlanError {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 match self {
25 Self::Parse(e) => write!(f, "{e}"),
26 }
27 }
28}
29
30impl std::error::Error for PlanError {
31 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
32 match self {
33 Self::Parse(e) => Some(e),
34 }
35 }
36}
37
38impl From<ParseError> for PlanError {
39 fn from(e: ParseError) -> Self {
40 PlanError::Parse(e)
41 }
42}
43
44pub fn plan(input: &str) -> Result<PlanNode, PlanError> {
45 let stmt = parse(input)?;
46 plan_statement(stmt)
47}
48
49pub fn plan_statement(stmt: Statement) -> Result<PlanNode, PlanError> {
50 match stmt {
51 Statement::Query(q) => plan_query(q),
52 Statement::Insert(ins) => plan_insert(ins),
53 Statement::UpdateQuery(upd) => plan_update(upd),
54 Statement::DeleteQuery(del) => plan_delete(del),
55 Statement::CreateType(ct) => plan_create_type(ct),
56 Statement::AlterTable(at) => Ok(PlanNode::AlterTable {
57 table: at.table,
58 action: at.action,
59 }),
60 Statement::DropTable(dt) => Ok(PlanNode::DropTable {
61 name: dt.table,
62 if_exists: dt.if_exists,
63 }),
64 Statement::CreateView(cv) => Ok(PlanNode::CreateView {
65 name: cv.name,
66 query_text: cv.query_text,
67 }),
68 Statement::RefreshView(rv) => Ok(PlanNode::RefreshView { name: rv.name }),
69 Statement::DropView(dv) => Ok(PlanNode::DropView {
70 name: dv.name,
71 if_exists: dv.if_exists,
72 }),
73 Statement::ListTypes => Ok(PlanNode::ListTypes),
74 Statement::Describe(table) => Ok(PlanNode::Describe { table }),
75 Statement::Union(u) => {
76 let left = plan_statement(*u.left)?;
77 let right = plan_statement(*u.right)?;
78 Ok(PlanNode::Union {
79 left: Box::new(left),
80 right: Box::new(right),
81 all: u.all,
82 })
83 }
84 Statement::Upsert(ups) => plan_upsert(ups),
85 Statement::Begin => Ok(PlanNode::Begin),
86 Statement::Commit => Ok(PlanNode::Commit),
87 Statement::Rollback => Ok(PlanNode::Rollback),
88 Statement::Explain(inner) => {
89 let inner_plan = plan_statement(*inner)?;
90 Ok(PlanNode::Explain {
91 input: Box::new(inner_plan),
92 })
93 }
94 }
95}
96
97fn plan_query(q: QueryExpr) -> Result<PlanNode, PlanError> {
98 if !q.joins.is_empty() {
104 return plan_joined_query(q);
105 }
106 let (source, filter) = match q.filter {
115 Some(pred) => match try_extract_eq_index_key(&q.source, &pred) {
116 Some(index_scan) => (index_scan, None),
117 None => match try_extract_range_index_keys(&q.source, &pred) {
118 Some(range_scan) => (range_scan, None),
119 None => (
120 PlanNode::SeqScan {
121 table: q.source.clone(),
122 },
123 Some(pred),
124 ),
125 },
126 },
127 None => (
128 PlanNode::SeqScan {
129 table: q.source.clone(),
130 },
131 None,
132 ),
133 };
134 let mut node = source;
135
136 if let Some(pred) = filter {
137 node = PlanNode::Filter {
138 input: Box::new(node),
139 predicate: pred,
140 };
141 }
142
143 if let Some(group) = q.group_by {
146 let mut proj_fields: Vec<ProjectField> = q
147 .projection
148 .map(|proj| {
149 proj.into_iter()
150 .map(|pf| ProjectField {
151 alias: pf.alias,
152 expr: pf.expr,
153 })
154 .collect()
155 })
156 .unwrap_or_default();
157 let mut having = group.having;
158 let aggregates = extract_aggregates(&mut proj_fields, &mut having);
159
160 node = PlanNode::GroupBy {
161 input: Box::new(node),
162 keys: group.keys,
163 aggregates,
164 having,
165 };
166
167 if !proj_fields.is_empty() {
168 node = PlanNode::Project {
169 input: Box::new(node),
170 fields: proj_fields,
171 };
172 }
173
174 if let Some(order) = q.order {
175 node = PlanNode::Sort {
176 input: Box::new(node),
177 keys: order
178 .keys
179 .into_iter()
180 .map(|k| SortKey {
181 field: k.field,
182 descending: k.descending,
183 })
184 .collect(),
185 };
186 }
187 if let Some(off) = q.offset {
191 node = PlanNode::Offset {
192 input: Box::new(node),
193 count: off,
194 };
195 }
196 if let Some(lim) = q.limit {
197 node = PlanNode::Limit {
198 input: Box::new(node),
199 count: lim,
200 };
201 }
202 if q.distinct {
203 node = PlanNode::Distinct {
204 input: Box::new(node),
205 };
206 }
207 return Ok(node);
208 }
209
210 if let Some(order) = q.order {
211 node = PlanNode::Sort {
212 input: Box::new(node),
213 keys: order
214 .keys
215 .into_iter()
216 .map(|k| SortKey {
217 field: k.field,
218 descending: k.descending,
219 })
220 .collect(),
221 };
222 }
223
224 if let Some(off) = q.offset {
228 node = PlanNode::Offset {
229 input: Box::new(node),
230 count: off,
231 };
232 }
233
234 if let Some(lim) = q.limit {
235 node = PlanNode::Limit {
236 input: Box::new(node),
237 count: lim,
238 };
239 }
240
241 if let Some(proj) = q.projection {
242 let mut fields: Vec<ProjectField> = proj
243 .into_iter()
244 .map(|pf| ProjectField {
245 alias: pf.alias,
246 expr: pf.expr,
247 })
248 .collect();
249 let windows = extract_windows(&mut fields);
250 if !windows.is_empty() {
251 node = PlanNode::Window {
252 input: Box::new(node),
253 windows,
254 };
255 }
256 node = PlanNode::Project {
257 input: Box::new(node),
258 fields,
259 };
260 }
261
262 if q.distinct {
263 node = PlanNode::Distinct {
264 input: Box::new(node),
265 };
266 }
267
268 if let Some(agg) = q.aggregation {
269 node = PlanNode::Aggregate {
270 input: Box::new(node),
271 function: agg.function,
272 field: agg.field,
273 };
274 }
275
276 Ok(node)
277}
278
279fn plan_joined_query(q: QueryExpr) -> Result<PlanNode, PlanError> {
301 let primary_alias = q.alias.clone().unwrap_or_else(|| q.source.clone());
302 let mut node = PlanNode::AliasScan {
303 table: q.source.clone(),
304 alias: primary_alias,
305 };
306
307 for join in q.joins {
308 let right_alias = join.alias.unwrap_or_else(|| join.source.clone());
309 let right = PlanNode::AliasScan {
310 table: join.source,
311 alias: right_alias,
312 };
313 match join.kind {
314 JoinKind::Inner | JoinKind::LeftOuter | JoinKind::Cross => {
315 node = PlanNode::NestedLoopJoin {
316 left: Box::new(node),
317 right: Box::new(right),
318 on: join.on,
319 kind: join.kind,
320 };
321 }
322 JoinKind::RightOuter => {
323 node = PlanNode::NestedLoopJoin {
325 left: Box::new(right),
326 right: Box::new(node),
327 on: join.on,
328 kind: JoinKind::LeftOuter,
329 };
330 }
331 }
332 }
333
334 if let Some(pred) = q.filter {
335 node = PlanNode::Filter {
336 input: Box::new(node),
337 predicate: pred,
338 };
339 }
340
341 if let Some(order) = q.order {
342 node = PlanNode::Sort {
343 input: Box::new(node),
344 keys: order
345 .keys
346 .into_iter()
347 .map(|k| SortKey {
348 field: k.field,
349 descending: k.descending,
350 })
351 .collect(),
352 };
353 }
354
355 if let Some(off) = q.offset {
359 node = PlanNode::Offset {
360 input: Box::new(node),
361 count: off,
362 };
363 }
364
365 if let Some(lim) = q.limit {
366 node = PlanNode::Limit {
367 input: Box::new(node),
368 count: lim,
369 };
370 }
371
372 if let Some(group) = q.group_by {
374 let mut proj_fields: Vec<ProjectField> = q
375 .projection
376 .map(|proj| {
377 proj.into_iter()
378 .map(|pf| ProjectField {
379 alias: pf.alias,
380 expr: pf.expr,
381 })
382 .collect()
383 })
384 .unwrap_or_default();
385 let mut having = group.having;
386 let aggregates = extract_aggregates(&mut proj_fields, &mut having);
387
388 node = PlanNode::GroupBy {
389 input: Box::new(node),
390 keys: group.keys,
391 aggregates,
392 having,
393 };
394
395 if !proj_fields.is_empty() {
396 node = PlanNode::Project {
397 input: Box::new(node),
398 fields: proj_fields,
399 };
400 }
401 if q.distinct {
402 node = PlanNode::Distinct {
403 input: Box::new(node),
404 };
405 }
406 return Ok(node);
407 }
408
409 if let Some(proj) = q.projection {
410 let mut fields: Vec<ProjectField> = proj
411 .into_iter()
412 .map(|pf| ProjectField {
413 alias: pf.alias,
414 expr: pf.expr,
415 })
416 .collect();
417 let windows = extract_windows(&mut fields);
418 if !windows.is_empty() {
419 node = PlanNode::Window {
420 input: Box::new(node),
421 windows,
422 };
423 }
424 node = PlanNode::Project {
425 input: Box::new(node),
426 fields,
427 };
428 }
429
430 if q.distinct {
431 node = PlanNode::Distinct {
432 input: Box::new(node),
433 };
434 }
435
436 if let Some(agg) = q.aggregation {
437 node = PlanNode::Aggregate {
438 input: Box::new(node),
439 function: agg.function,
440 field: agg.field,
441 };
442 }
443
444 Ok(node)
445}
446
447fn plan_insert(ins: InsertExpr) -> Result<PlanNode, PlanError> {
448 Ok(PlanNode::Insert {
449 table: ins.target,
450 rows: ins.rows,
451 returning: ins.returning,
452 })
453}
454
455fn plan_update(upd: UpdateExpr) -> Result<PlanNode, PlanError> {
456 let source = match upd.filter {
461 Some(pred) => match try_extract_eq_index_key(&upd.source, &pred) {
462 Some(index_scan) => index_scan,
463 None => match try_extract_range_index_keys(&upd.source, &pred) {
464 Some(range_scan) => range_scan,
465 None => PlanNode::Filter {
466 input: Box::new(PlanNode::SeqScan {
467 table: upd.source.clone(),
468 }),
469 predicate: pred,
470 },
471 },
472 },
473 None => PlanNode::SeqScan {
474 table: upd.source.clone(),
475 },
476 };
477 Ok(PlanNode::Update {
478 input: Box::new(source),
479 table: upd.source,
480 assignments: upd.assignments,
481 returning: upd.returning,
482 })
483}
484
485fn plan_delete(del: DeleteExpr) -> Result<PlanNode, PlanError> {
486 let source = match del.filter {
487 Some(pred) => match try_extract_eq_index_key(&del.source, &pred) {
488 Some(index_scan) => index_scan,
489 None => match try_extract_range_index_keys(&del.source, &pred) {
490 Some(range_scan) => range_scan,
491 None => PlanNode::Filter {
492 input: Box::new(PlanNode::SeqScan {
493 table: del.source.clone(),
494 }),
495 predicate: pred,
496 },
497 },
498 },
499 None => PlanNode::SeqScan {
500 table: del.source.clone(),
501 },
502 };
503 Ok(PlanNode::Delete {
504 input: Box::new(source),
505 table: del.source,
506 returning: del.returning,
507 })
508}
509
510fn plan_upsert(ups: UpsertExpr) -> Result<PlanNode, PlanError> {
511 Ok(PlanNode::Upsert {
512 table: ups.target,
513 key_column: ups.key_column,
514 assignments: ups.assignments,
515 on_conflict: ups.on_conflict,
516 })
517}
518
519fn plan_create_type(ct: CreateTypeExpr) -> Result<PlanNode, PlanError> {
520 let fields = ct
521 .fields
522 .into_iter()
523 .map(|f| crate::plan::CreateField {
524 name: f.name,
525 type_name: f.type_name,
526 required: f.required,
527 unique: f.unique,
528 default: f.default,
529 auto: f.auto,
530 })
531 .collect();
532 Ok(PlanNode::CreateTable {
533 name: ct.name,
534 fields,
535 if_not_exists: ct.if_not_exists,
536 })
537}
538
539fn try_extract_eq_index_key(table: &str, pred: &Expr) -> Option<PlanNode> {
549 let (lhs, op, rhs) = match pred {
550 Expr::BinaryOp(lhs, op, rhs) => (lhs.as_ref(), *op, rhs.as_ref()),
551 _ => return None,
552 };
553 if op != BinOp::Eq {
554 return None;
555 }
556 let (column, key) = match (lhs, rhs) {
557 (Expr::Field(name), Expr::Literal(_)) => (name.clone(), rhs.clone()),
558 (Expr::Literal(_), Expr::Field(name)) => (name.clone(), lhs.clone()),
559 _ => return None,
560 };
561 Some(PlanNode::IndexScan {
562 table: table.to_string(),
563 column,
564 key,
565 })
566}
567
568fn extract_single_bound(pred: &Expr) -> Option<RangeBound> {
571 let (lhs, op, rhs) = match pred {
572 Expr::BinaryOp(lhs, op, rhs) => (lhs.as_ref(), *op, rhs.as_ref()),
573 _ => return None,
574 };
575 match op {
576 BinOp::Gt => match (lhs, rhs) {
578 (Expr::Field(name), Expr::Literal(_)) => {
579 Some((name.clone(), Some((rhs.clone(), false)), None))
580 }
581 (Expr::Literal(_), Expr::Field(name)) => {
582 Some((name.clone(), None, Some((lhs.clone(), false))))
584 }
585 _ => None,
586 },
587 BinOp::Gte => match (lhs, rhs) {
589 (Expr::Field(name), Expr::Literal(_)) => {
590 Some((name.clone(), Some((rhs.clone(), true)), None))
591 }
592 (Expr::Literal(_), Expr::Field(name)) => {
593 Some((name.clone(), None, Some((lhs.clone(), true))))
594 }
595 _ => None,
596 },
597 BinOp::Lt => match (lhs, rhs) {
599 (Expr::Field(name), Expr::Literal(_)) => {
600 Some((name.clone(), None, Some((rhs.clone(), false))))
601 }
602 (Expr::Literal(_), Expr::Field(name)) => {
603 Some((name.clone(), Some((lhs.clone(), false)), None))
604 }
605 _ => None,
606 },
607 BinOp::Lte => match (lhs, rhs) {
609 (Expr::Field(name), Expr::Literal(_)) => {
610 Some((name.clone(), None, Some((rhs.clone(), true))))
611 }
612 (Expr::Literal(_), Expr::Field(name)) => {
613 Some((name.clone(), Some((lhs.clone(), true)), None))
614 }
615 _ => None,
616 },
617 _ => None,
618 }
619}
620
621fn try_extract_range_index_keys(table: &str, pred: &Expr) -> Option<PlanNode> {
626 if let Expr::BinaryOp(lhs, BinOp::And, rhs) = pred {
628 if let (Some((col1, s1, e1)), Some((col2, s2, e2))) =
629 (extract_single_bound(lhs), extract_single_bound(rhs))
630 {
631 if col1 == col2 {
632 let start = s1.or(s2);
633 let end = e1.or(e2);
634 if start.is_some() || end.is_some() {
635 return Some(PlanNode::RangeScan {
636 table: table.to_string(),
637 column: col1,
638 start,
639 end,
640 });
641 }
642 }
643 }
644 }
645
646 if let Some((col, start, end)) = extract_single_bound(pred) {
648 return Some(PlanNode::RangeScan {
649 table: table.to_string(),
650 column: col,
651 start,
652 end,
653 });
654 }
655
656 None
657}
658
659fn extract_windows(proj_fields: &mut [ProjectField]) -> Vec<WindowDef> {
664 let mut defs = Vec::new();
665 let mut counter = 0usize;
666 for f in proj_fields.iter_mut() {
667 if let Expr::Window {
668 function,
669 args,
670 partition_by,
671 order_by,
672 } = &f.expr
673 {
674 let output_name = format!("__win_{counter}");
675 defs.push(WindowDef {
676 function: *function,
677 args: args.clone(),
678 partition_by: partition_by.clone(),
679 order_by: order_by
680 .iter()
681 .map(|k| SortKey {
682 field: k.field.clone(),
683 descending: k.descending,
684 })
685 .collect(),
686 output_name: output_name.clone(),
687 });
688 f.expr = Expr::Field(output_name);
689 counter += 1;
690 }
691 }
692 defs
693}
694
695fn extract_aggregates(
701 proj_fields: &mut [ProjectField],
702 having: &mut Option<Expr>,
703) -> Vec<GroupAgg> {
704 let mut aggs: Vec<GroupAgg> = Vec::new();
705 let mut counter = 0usize;
706 for f in proj_fields.iter_mut() {
707 rewrite_agg_expr(&mut f.expr, &mut aggs, &mut counter);
708 }
709 if let Some(h) = having {
710 rewrite_agg_expr(h, &mut aggs, &mut counter);
711 }
712 aggs
713}
714
715fn rewrite_agg_expr(expr: &mut Expr, aggs: &mut Vec<GroupAgg>, counter: &mut usize) {
716 match expr {
717 Expr::FunctionCall(func, inner) => {
718 let field_name = match inner.as_ref() {
729 Expr::Field(name) => Some(name.clone()),
730 Expr::QualifiedField { qualifier, field } => Some(format!("{qualifier}.{field}")),
731 _ => None,
732 };
733 if let Some(name) = field_name {
734 let output = find_or_insert_agg(aggs, *func, &name, counter);
735 *expr = Expr::Field(output);
736 }
737 }
738 Expr::BinaryOp(l, _, r) => {
739 rewrite_agg_expr(l, aggs, counter);
740 rewrite_agg_expr(r, aggs, counter);
741 }
742 Expr::UnaryOp(_, inner) => rewrite_agg_expr(inner, aggs, counter),
743 Expr::Coalesce(l, r) => {
744 rewrite_agg_expr(l, aggs, counter);
745 rewrite_agg_expr(r, aggs, counter);
746 }
747 Expr::InList { expr: e, list, .. } => {
748 rewrite_agg_expr(e, aggs, counter);
749 for item in list {
750 rewrite_agg_expr(item, aggs, counter);
751 }
752 }
753 Expr::InSubquery { expr: e, .. } => {
754 rewrite_agg_expr(e, aggs, counter);
755 }
756 _ => {}
757 }
758}
759
760fn find_or_insert_agg(
761 aggs: &mut Vec<GroupAgg>,
762 func: AggFunc,
763 field: &str,
764 counter: &mut usize,
765) -> String {
766 for existing in aggs.iter() {
767 if existing.function == func && existing.field == field {
768 return existing.output_name.clone();
769 }
770 }
771 let output_name = format!("__agg_{counter}");
772 aggs.push(GroupAgg {
773 function: func,
774 field: field.to_string(),
775 output_name: output_name.clone(),
776 });
777 *counter += 1;
778 output_name
779}
780
781#[cfg(test)]
782mod tests {
783 use super::*;
784 use crate::plan::PlanNode;
785
786 #[test]
787 fn test_plan_simple_scan() {
788 let plan = plan("User").unwrap();
789 assert!(matches!(plan, PlanNode::SeqScan { table } if table == "User"));
790 }
791
792 #[test]
793 fn test_plan_filter() {
794 let plan = plan("User filter .age > 30").unwrap();
795 assert!(matches!(plan, PlanNode::RangeScan { .. }));
796 }
797
798 #[test]
799 fn test_plan_filter_with_projection() {
800 let plan = plan("User filter .age > 30 { name, email }").unwrap();
801 assert!(matches!(plan, PlanNode::Project { .. }));
802 }
803
804 #[test]
805 fn test_plan_insert() {
806 let plan = plan(r#"insert User { name := "Alice", age := 30 }"#).unwrap();
807 assert!(matches!(plan, PlanNode::Insert { .. }));
808 }
809
810 #[test]
811 fn test_plan_order_limit() {
812 let plan = plan("User order .name limit 10").unwrap();
813 match plan {
814 PlanNode::Limit { input, .. } => {
815 assert!(matches!(*input, PlanNode::Sort { .. }));
816 }
817 _ => panic!("expected Limit(Sort(SeqScan))"),
818 }
819 }
820
821 #[test]
822 fn test_plan_count() {
823 let plan = plan("count(User)").unwrap();
824 assert!(matches!(plan, PlanNode::Aggregate { .. }));
825 }
826
827 #[test]
828 fn test_plan_eq_becomes_index_scan() {
829 let plan = plan("User filter .id = 42").unwrap();
832 match plan {
833 PlanNode::IndexScan { table, column, key } => {
834 assert_eq!(table, "User");
835 assert_eq!(column, "id");
836 assert!(matches!(key, Expr::Literal(Literal::Int(42))));
837 }
838 other => panic!("expected IndexScan, got {other:?}"),
839 }
840 }
841
842 #[test]
843 fn test_plan_eq_reversed_becomes_index_scan() {
844 let plan = plan(r#"User filter "NYC" = .city"#).unwrap();
846 assert!(matches!(plan, PlanNode::IndexScan { .. }));
847 }
848
849 #[test]
850 fn test_plan_non_eq_stays_filter() {
851 let plan = plan("User filter .age > 30").unwrap();
853 match plan {
854 PlanNode::RangeScan {
855 column, start, end, ..
856 } => {
857 assert_eq!(column, "age");
858 assert!(start.is_some(), "expected lower bound");
859 assert!(end.is_none(), "expected no upper bound");
860 let (_, inclusive) = start.unwrap();
861 assert!(!inclusive, "expected exclusive lower bound for >");
862 }
863 other => panic!("expected RangeScan, got {other:?}"),
864 }
865 }
866
867 #[test]
868 fn test_plan_index_scan_with_projection() {
869 let plan = plan("User filter .id = 1 { .name }").unwrap();
871 match plan {
872 PlanNode::Project { input, .. } => {
873 assert!(matches!(*input, PlanNode::IndexScan { .. }));
874 }
875 other => panic!("expected Project(IndexScan), got {other:?}"),
876 }
877 }
878
879 #[test]
880 fn test_plan_update_by_pk_becomes_index_scan() {
881 let plan = plan("User filter .id = 42 update { age := 31 }").unwrap();
884 match plan {
885 PlanNode::Update { input, .. } => {
886 assert!(
887 matches!(*input, PlanNode::IndexScan { .. }),
888 "expected Update(IndexScan), got {input:?}"
889 );
890 }
891 other => panic!("expected Update, got {other:?}"),
892 }
893 }
894
895 #[test]
896 fn test_plan_update_range_stays_range_scan() {
897 let plan = plan("User filter .age > 30 update { age := 31 }").unwrap();
898 match plan {
899 PlanNode::Update { input, .. } => {
900 assert!(
901 matches!(*input, PlanNode::RangeScan { .. }),
902 "expected Update(RangeScan), got {input:?}"
903 );
904 }
905 other => panic!("expected Update, got {other:?}"),
906 }
907 }
908
909 #[test]
910 fn test_plan_delete_by_pk_becomes_index_scan() {
911 let plan = plan("User filter .id = 7 delete").unwrap();
912 match plan {
913 PlanNode::Delete { input, .. } => {
914 assert!(matches!(*input, PlanNode::IndexScan { .. }));
915 }
916 other => panic!("expected Delete, got {other:?}"),
917 }
918 }
919
920 #[test]
921 fn test_plan_inner_join_builds_nested_loop() {
922 let plan = plan("User as u join Order as o on u.id = o.user_id").unwrap();
925 match plan {
926 PlanNode::NestedLoopJoin {
927 left,
928 right,
929 on,
930 kind,
931 } => {
932 assert_eq!(kind, JoinKind::Inner);
933 assert!(on.is_some());
934 assert!(matches!(*left, PlanNode::AliasScan { .. }));
935 assert!(matches!(*right, PlanNode::AliasScan { .. }));
936 }
937 other => panic!("expected NestedLoopJoin, got {other:?}"),
938 }
939 }
940
941 #[test]
942 fn test_plan_right_join_rewritten_as_left_with_swapped_inputs() {
943 let plan = plan("User as u right join Order as o on u.id = o.user_id").unwrap();
944 match plan {
945 PlanNode::NestedLoopJoin {
946 left, right, kind, ..
947 } => {
948 assert_eq!(kind, JoinKind::LeftOuter);
949 match *left {
951 PlanNode::AliasScan { table, .. } => assert_eq!(table, "Order"),
952 other => panic!("expected AliasScan(Order), got {other:?}"),
953 }
954 match *right {
955 PlanNode::AliasScan { table, .. } => assert_eq!(table, "User"),
956 other => panic!("expected AliasScan(User), got {other:?}"),
957 }
958 }
959 other => panic!("expected NestedLoopJoin, got {other:?}"),
960 }
961 }
962
963 #[test]
964 fn test_plan_multi_join_is_left_deep() {
965 let plan = plan(
967 "User as u join Order as o on u.id = o.user_id \
968 join Product as p on o.product_id = p.id",
969 )
970 .unwrap();
971 match plan {
972 PlanNode::NestedLoopJoin { left, right, .. } => {
973 match *right {
975 PlanNode::AliasScan { table, .. } => assert_eq!(table, "Product"),
976 other => panic!("expected AliasScan(Product), got {other:?}"),
977 }
978 assert!(matches!(*left, PlanNode::NestedLoopJoin { .. }));
980 }
981 other => panic!("expected NestedLoopJoin, got {other:?}"),
982 }
983 }
984
985 #[test]
986 fn test_plan_join_with_filter_tail_wraps_filter_on_top() {
987 let plan =
988 plan("User as u join Order as o on u.id = o.user_id filter o.total > 100").unwrap();
989 match plan {
990 PlanNode::Filter { input, .. } => {
991 assert!(matches!(*input, PlanNode::NestedLoopJoin { .. }));
992 }
993 other => panic!("expected Filter(NestedLoopJoin), got {other:?}"),
994 }
995 }
996
997 #[test]
998 fn test_plan_group_by_builds_groupby_node() {
999 let plan = plan("User group .status { .status, n: count(.name) }").unwrap();
1000 match plan {
1002 PlanNode::Project { input, fields } => {
1003 assert_eq!(fields.len(), 2);
1004 match *input {
1005 PlanNode::GroupBy {
1006 input: inner,
1007 keys,
1008 aggregates,
1009 having,
1010 } => {
1011 assert!(matches!(*inner, PlanNode::SeqScan { .. }));
1012 assert_eq!(keys, vec![GroupKey::Unqualified("status".into())]);
1013 assert_eq!(aggregates.len(), 1);
1014 assert_eq!(aggregates[0].function, AggFunc::Count);
1015 assert_eq!(aggregates[0].field, "name");
1016 assert!(having.is_none());
1017 }
1018 other => panic!("expected GroupBy, got {other:?}"),
1019 }
1020 }
1021 other => panic!("expected Project, got {other:?}"),
1022 }
1023 }
1024
1025 #[test]
1026 fn test_plan_group_by_having_rewrites_agg_in_having() {
1027 let plan = plan("User group .status having count(.name) > 1 { .status }").unwrap();
1028 match plan {
1029 PlanNode::Project { input, .. } => {
1030 match *input {
1031 PlanNode::GroupBy {
1032 having, aggregates, ..
1033 } => {
1034 assert_eq!(aggregates.len(), 1);
1037 assert_eq!(aggregates[0].output_name, "__agg_0");
1038 let h = having.expect("having should be Some");
1039 match h {
1040 Expr::BinaryOp(l, BinOp::Gt, _) => {
1041 assert!(
1042 matches!(*l, Expr::Field(ref name) if name == "__agg_0"),
1043 "expected Field(__agg_0), got {l:?}"
1044 );
1045 }
1046 other => panic!("expected BinaryOp, got {other:?}"),
1047 }
1048 }
1049 other => panic!("expected GroupBy, got {other:?}"),
1050 }
1051 }
1052 other => panic!("expected Project, got {other:?}"),
1053 }
1054 }
1055
1056 #[test]
1057 fn test_plan_window_inserts_window_node_before_project() {
1058 let plan = plan("User { .name, rn: row_number() over (order .age) }").unwrap();
1059 match plan {
1061 PlanNode::Project { input, fields } => {
1062 assert_eq!(fields.len(), 2);
1063 assert!(
1065 matches!(&fields[1].expr, Expr::Field(name) if name == "__win_0"),
1066 "expected Field(__win_0), got {:?}",
1067 fields[1].expr
1068 );
1069 match *input {
1070 PlanNode::Window {
1071 input: inner,
1072 windows,
1073 } => {
1074 assert_eq!(windows.len(), 1);
1075 assert_eq!(windows[0].output_name, "__win_0");
1076 assert!(matches!(*inner, PlanNode::SeqScan { .. }));
1077 }
1078 other => panic!("expected Window, got {other:?}"),
1079 }
1080 }
1081 other => panic!("expected Project, got {other:?}"),
1082 }
1083 }
1084
1085 #[test]
1086 fn test_plan_multiple_windows() {
1087 let plan = plan(
1088 "User { .name, rn: row_number() over (order .age), s: sum(.salary) over (partition .dept order .salary) }"
1089 ).unwrap();
1090 match plan {
1091 PlanNode::Project { input, fields } => {
1092 assert_eq!(fields.len(), 3);
1093 assert!(matches!(&fields[1].expr, Expr::Field(name) if name == "__win_0"));
1094 assert!(matches!(&fields[2].expr, Expr::Field(name) if name == "__win_1"));
1095 match *input {
1096 PlanNode::Window { windows, .. } => {
1097 assert_eq!(windows.len(), 2);
1098 assert_eq!(windows[0].output_name, "__win_0");
1099 assert_eq!(windows[1].output_name, "__win_1");
1100 }
1101 other => panic!("expected Window, got {other:?}"),
1102 }
1103 }
1104 other => panic!("expected Project, got {other:?}"),
1105 }
1106 }
1107
1108 #[test]
1109 fn test_plan_no_window_without_over() {
1110 let plan = plan("User group .dept { .dept, total: sum(.salary) }").unwrap();
1112 match plan {
1113 PlanNode::Project { input, .. } => {
1114 assert!(
1116 matches!(*input, PlanNode::GroupBy { .. }),
1117 "expected GroupBy under Project, got {:?}",
1118 input
1119 );
1120 }
1121 other => panic!("expected Project, got {other:?}"),
1122 }
1123 }
1124
1125 #[test]
1126 fn test_plan_explain_wraps_inner() {
1127 let plan = plan("explain User filter .age > 30").unwrap();
1128 match plan {
1129 PlanNode::Explain { input } => {
1130 assert!(
1131 matches!(*input, PlanNode::RangeScan { .. }),
1132 "expected Explain(RangeScan), got {:?}",
1133 input
1134 );
1135 }
1136 other => panic!("expected Explain, got {other:?}"),
1137 }
1138 }
1139
1140 #[test]
1141 fn test_plan_explain_simple_scan() {
1142 let plan = plan("explain User").unwrap();
1143 match plan {
1144 PlanNode::Explain { input } => {
1145 assert!(matches!(*input, PlanNode::SeqScan { .. }));
1146 }
1147 other => panic!("expected Explain(SeqScan), got {other:?}"),
1148 }
1149 }
1150
1151 #[test]
1152 fn test_plan_explain_join() {
1153 let plan = plan("explain User as u join Order as o on u.id = o.user_id").unwrap();
1154 match plan {
1155 PlanNode::Explain { input } => {
1156 assert!(matches!(*input, PlanNode::NestedLoopJoin { .. }));
1157 }
1158 other => panic!("expected Explain(NestedLoopJoin), got {other:?}"),
1159 }
1160 }
1161}