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