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() {
732 Expr::Field(name) => Some(name.clone()),
733 Expr::QualifiedField { qualifier, field } => Some(format!("{qualifier}.{field}")),
734 _ => None,
735 };
736 if let Some(name) = field_name {
737 let output = find_or_insert_agg(aggs, *func, &name, counter);
738 *expr = Expr::Field(output);
739 }
740 }
741 Expr::BinaryOp(l, _, r) => {
742 rewrite_agg_expr(l, aggs, counter);
743 rewrite_agg_expr(r, aggs, counter);
744 }
745 Expr::UnaryOp(_, inner) => rewrite_agg_expr(inner, aggs, counter),
746 Expr::Coalesce(l, r) => {
747 rewrite_agg_expr(l, aggs, counter);
748 rewrite_agg_expr(r, aggs, counter);
749 }
750 Expr::InList { expr: e, list, .. } => {
751 rewrite_agg_expr(e, aggs, counter);
752 for item in list {
753 rewrite_agg_expr(item, aggs, counter);
754 }
755 }
756 Expr::InSubquery { expr: e, .. } => {
757 rewrite_agg_expr(e, aggs, counter);
758 }
759 _ => {}
760 }
761}
762
763fn find_or_insert_agg(
764 aggs: &mut Vec<GroupAgg>,
765 func: AggFunc,
766 field: &str,
767 counter: &mut usize,
768) -> String {
769 for existing in aggs.iter() {
770 if existing.function == func && existing.field == field {
771 return existing.output_name.clone();
772 }
773 }
774 let output_name = format!("__agg_{counter}");
775 aggs.push(GroupAgg {
776 function: func,
777 field: field.to_string(),
778 output_name: output_name.clone(),
779 });
780 *counter += 1;
781 output_name
782}
783
784#[cfg(test)]
785mod tests {
786 use super::*;
787 use crate::plan::PlanNode;
788
789 #[test]
790 fn test_plan_simple_scan() {
791 let plan = plan("User").unwrap();
792 assert!(matches!(plan, PlanNode::SeqScan { table } if table == "User"));
793 }
794
795 #[test]
796 fn test_plan_filter() {
797 let plan = plan("User filter .age > 30").unwrap();
798 assert!(matches!(plan, PlanNode::RangeScan { .. }));
799 }
800
801 #[test]
802 fn test_plan_filter_with_projection() {
803 let plan = plan("User filter .age > 30 { name, email }").unwrap();
804 assert!(matches!(plan, PlanNode::Project { .. }));
805 }
806
807 #[test]
808 fn test_plan_insert() {
809 let plan = plan(r#"insert User { name := "Alice", age := 30 }"#).unwrap();
810 assert!(matches!(plan, PlanNode::Insert { .. }));
811 }
812
813 #[test]
814 fn test_plan_order_limit() {
815 let plan = plan("User order .name limit 10").unwrap();
816 match plan {
817 PlanNode::Limit { input, .. } => {
818 assert!(matches!(*input, PlanNode::Sort { .. }));
819 }
820 _ => panic!("expected Limit(Sort(SeqScan))"),
821 }
822 }
823
824 #[test]
825 fn test_plan_count() {
826 let plan = plan("count(User)").unwrap();
827 assert!(matches!(plan, PlanNode::Aggregate { .. }));
828 }
829
830 #[test]
831 fn test_plan_eq_becomes_index_scan() {
832 let plan = plan("User filter .id = 42").unwrap();
835 match plan {
836 PlanNode::IndexScan { table, column, key } => {
837 assert_eq!(table, "User");
838 assert_eq!(column, "id");
839 assert!(matches!(key, Expr::Literal(Literal::Int(42))));
840 }
841 other => panic!("expected IndexScan, got {other:?}"),
842 }
843 }
844
845 #[test]
846 fn test_plan_eq_reversed_becomes_index_scan() {
847 let plan = plan(r#"User filter "NYC" = .city"#).unwrap();
849 assert!(matches!(plan, PlanNode::IndexScan { .. }));
850 }
851
852 #[test]
853 fn test_plan_non_eq_stays_filter() {
854 let plan = plan("User filter .age > 30").unwrap();
856 match plan {
857 PlanNode::RangeScan {
858 column, start, end, ..
859 } => {
860 assert_eq!(column, "age");
861 assert!(start.is_some(), "expected lower bound");
862 assert!(end.is_none(), "expected no upper bound");
863 let (_, inclusive) = start.unwrap();
864 assert!(!inclusive, "expected exclusive lower bound for >");
865 }
866 other => panic!("expected RangeScan, got {other:?}"),
867 }
868 }
869
870 #[test]
871 fn test_plan_index_scan_with_projection() {
872 let plan = plan("User filter .id = 1 { .name }").unwrap();
874 match plan {
875 PlanNode::Project { input, .. } => {
876 assert!(matches!(*input, PlanNode::IndexScan { .. }));
877 }
878 other => panic!("expected Project(IndexScan), got {other:?}"),
879 }
880 }
881
882 #[test]
883 fn test_plan_update_by_pk_becomes_index_scan() {
884 let plan = plan("User filter .id = 42 update { age := 31 }").unwrap();
887 match plan {
888 PlanNode::Update { input, .. } => {
889 assert!(
890 matches!(*input, PlanNode::IndexScan { .. }),
891 "expected Update(IndexScan), got {input:?}"
892 );
893 }
894 other => panic!("expected Update, got {other:?}"),
895 }
896 }
897
898 #[test]
899 fn test_plan_update_range_stays_range_scan() {
900 let plan = plan("User filter .age > 30 update { age := 31 }").unwrap();
901 match plan {
902 PlanNode::Update { input, .. } => {
903 assert!(
904 matches!(*input, PlanNode::RangeScan { .. }),
905 "expected Update(RangeScan), got {input:?}"
906 );
907 }
908 other => panic!("expected Update, got {other:?}"),
909 }
910 }
911
912 #[test]
913 fn test_plan_delete_by_pk_becomes_index_scan() {
914 let plan = plan("User filter .id = 7 delete").unwrap();
915 match plan {
916 PlanNode::Delete { input, .. } => {
917 assert!(matches!(*input, PlanNode::IndexScan { .. }));
918 }
919 other => panic!("expected Delete, got {other:?}"),
920 }
921 }
922
923 #[test]
924 fn test_plan_inner_join_builds_nested_loop() {
925 let plan = plan("User as u join Order as o on u.id = o.user_id").unwrap();
928 match plan {
929 PlanNode::NestedLoopJoin {
930 left,
931 right,
932 on,
933 kind,
934 } => {
935 assert_eq!(kind, JoinKind::Inner);
936 assert!(on.is_some());
937 assert!(matches!(*left, PlanNode::AliasScan { .. }));
938 assert!(matches!(*right, PlanNode::AliasScan { .. }));
939 }
940 other => panic!("expected NestedLoopJoin, got {other:?}"),
941 }
942 }
943
944 #[test]
945 fn test_plan_right_join_rewritten_as_left_with_swapped_inputs() {
946 let plan = plan("User as u right join Order as o on u.id = o.user_id").unwrap();
947 match plan {
948 PlanNode::NestedLoopJoin {
949 left, right, kind, ..
950 } => {
951 assert_eq!(kind, JoinKind::LeftOuter);
952 match *left {
954 PlanNode::AliasScan { table, .. } => assert_eq!(table, "Order"),
955 other => panic!("expected AliasScan(Order), got {other:?}"),
956 }
957 match *right {
958 PlanNode::AliasScan { table, .. } => assert_eq!(table, "User"),
959 other => panic!("expected AliasScan(User), got {other:?}"),
960 }
961 }
962 other => panic!("expected NestedLoopJoin, got {other:?}"),
963 }
964 }
965
966 #[test]
967 fn test_plan_multi_join_is_left_deep() {
968 let plan = plan(
970 "User as u join Order as o on u.id = o.user_id \
971 join Product as p on o.product_id = p.id",
972 )
973 .unwrap();
974 match plan {
975 PlanNode::NestedLoopJoin { left, right, .. } => {
976 match *right {
978 PlanNode::AliasScan { table, .. } => assert_eq!(table, "Product"),
979 other => panic!("expected AliasScan(Product), got {other:?}"),
980 }
981 assert!(matches!(*left, PlanNode::NestedLoopJoin { .. }));
983 }
984 other => panic!("expected NestedLoopJoin, got {other:?}"),
985 }
986 }
987
988 #[test]
989 fn test_plan_join_with_filter_tail_wraps_filter_on_top() {
990 let plan =
991 plan("User as u join Order as o on u.id = o.user_id filter o.total > 100").unwrap();
992 match plan {
993 PlanNode::Filter { input, .. } => {
994 assert!(matches!(*input, PlanNode::NestedLoopJoin { .. }));
995 }
996 other => panic!("expected Filter(NestedLoopJoin), got {other:?}"),
997 }
998 }
999
1000 #[test]
1001 fn test_plan_group_by_builds_groupby_node() {
1002 let plan = plan("User group .status { .status, n: count(.name) }").unwrap();
1003 match plan {
1005 PlanNode::Project { input, fields } => {
1006 assert_eq!(fields.len(), 2);
1007 match *input {
1008 PlanNode::GroupBy {
1009 input: inner,
1010 keys,
1011 aggregates,
1012 having,
1013 } => {
1014 assert!(matches!(*inner, PlanNode::SeqScan { .. }));
1015 assert_eq!(keys, vec![GroupKey::Unqualified("status".into())]);
1016 assert_eq!(aggregates.len(), 1);
1017 assert_eq!(aggregates[0].function, AggFunc::Count);
1018 assert_eq!(aggregates[0].field, "name");
1019 assert!(having.is_none());
1020 }
1021 other => panic!("expected GroupBy, got {other:?}"),
1022 }
1023 }
1024 other => panic!("expected Project, got {other:?}"),
1025 }
1026 }
1027
1028 #[test]
1029 fn test_plan_group_by_having_rewrites_agg_in_having() {
1030 let plan = plan("User group .status having count(.name) > 1 { .status }").unwrap();
1031 match plan {
1032 PlanNode::Project { input, .. } => {
1033 match *input {
1034 PlanNode::GroupBy {
1035 having, aggregates, ..
1036 } => {
1037 assert_eq!(aggregates.len(), 1);
1040 assert_eq!(aggregates[0].output_name, "__agg_0");
1041 let h = having.expect("having should be Some");
1042 match h {
1043 Expr::BinaryOp(l, BinOp::Gt, _) => {
1044 assert!(
1045 matches!(*l, Expr::Field(ref name) if name == "__agg_0"),
1046 "expected Field(__agg_0), got {l:?}"
1047 );
1048 }
1049 other => panic!("expected BinaryOp, got {other:?}"),
1050 }
1051 }
1052 other => panic!("expected GroupBy, got {other:?}"),
1053 }
1054 }
1055 other => panic!("expected Project, got {other:?}"),
1056 }
1057 }
1058
1059 #[test]
1060 fn test_plan_window_inserts_window_node_before_project() {
1061 let plan = plan("User { .name, rn: row_number() over (order .age) }").unwrap();
1062 match plan {
1064 PlanNode::Project { input, fields } => {
1065 assert_eq!(fields.len(), 2);
1066 assert!(
1068 matches!(&fields[1].expr, Expr::Field(name) if name == "__win_0"),
1069 "expected Field(__win_0), got {:?}",
1070 fields[1].expr
1071 );
1072 match *input {
1073 PlanNode::Window {
1074 input: inner,
1075 windows,
1076 } => {
1077 assert_eq!(windows.len(), 1);
1078 assert_eq!(windows[0].output_name, "__win_0");
1079 assert!(matches!(*inner, PlanNode::SeqScan { .. }));
1080 }
1081 other => panic!("expected Window, got {other:?}"),
1082 }
1083 }
1084 other => panic!("expected Project, got {other:?}"),
1085 }
1086 }
1087
1088 #[test]
1089 fn test_plan_multiple_windows() {
1090 let plan = plan(
1091 "User { .name, rn: row_number() over (order .age), s: sum(.salary) over (partition .dept order .salary) }"
1092 ).unwrap();
1093 match plan {
1094 PlanNode::Project { input, fields } => {
1095 assert_eq!(fields.len(), 3);
1096 assert!(matches!(&fields[1].expr, Expr::Field(name) if name == "__win_0"));
1097 assert!(matches!(&fields[2].expr, Expr::Field(name) if name == "__win_1"));
1098 match *input {
1099 PlanNode::Window { windows, .. } => {
1100 assert_eq!(windows.len(), 2);
1101 assert_eq!(windows[0].output_name, "__win_0");
1102 assert_eq!(windows[1].output_name, "__win_1");
1103 }
1104 other => panic!("expected Window, got {other:?}"),
1105 }
1106 }
1107 other => panic!("expected Project, got {other:?}"),
1108 }
1109 }
1110
1111 #[test]
1112 fn test_plan_no_window_without_over() {
1113 let plan = plan("User group .dept { .dept, total: sum(.salary) }").unwrap();
1115 match plan {
1116 PlanNode::Project { input, .. } => {
1117 assert!(
1119 matches!(*input, PlanNode::GroupBy { .. }),
1120 "expected GroupBy under Project, got {:?}",
1121 input
1122 );
1123 }
1124 other => panic!("expected Project, got {other:?}"),
1125 }
1126 }
1127
1128 #[test]
1129 fn test_plan_explain_wraps_inner() {
1130 let plan = plan("explain User filter .age > 30").unwrap();
1131 match plan {
1132 PlanNode::Explain { input } => {
1133 assert!(
1134 matches!(*input, PlanNode::RangeScan { .. }),
1135 "expected Explain(RangeScan), got {:?}",
1136 input
1137 );
1138 }
1139 other => panic!("expected Explain, got {other:?}"),
1140 }
1141 }
1142
1143 #[test]
1144 fn test_plan_explain_simple_scan() {
1145 let plan = plan("explain User").unwrap();
1146 match plan {
1147 PlanNode::Explain { input } => {
1148 assert!(matches!(*input, PlanNode::SeqScan { .. }));
1149 }
1150 other => panic!("expected Explain(SeqScan), got {other:?}"),
1151 }
1152 }
1153
1154 #[test]
1155 fn test_plan_explain_join() {
1156 let plan = plan("explain User as u join Order as o on u.id = o.user_id").unwrap();
1157 match plan {
1158 PlanNode::Explain { input } => {
1159 assert!(matches!(*input, PlanNode::NestedLoopJoin { .. }));
1160 }
1161 other => panic!("expected Explain(NestedLoopJoin), got {other:?}"),
1162 }
1163 }
1164}