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