1use crate::catalog::{IndexMetadata, TableMetadata};
47use crate::planner::aggregate_expr::AggregateExpr;
48use crate::planner::typed_expr::{Projection, SortExpr, TypedAssignment, TypedExpr};
49
50#[derive(Debug, Clone)]
52pub enum WindowFunction {
53 RowNumber,
54 Rank,
55 DenseRank,
56 Aggregate(AggregateExpr),
57}
58
59#[derive(Debug, Clone)]
61pub struct WindowExpr {
62 pub function: WindowFunction,
63 pub partition_by: Vec<TypedExpr>,
64 pub order_by: Vec<SortExpr>,
65 pub result_type: crate::planner::types::ResolvedType,
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub enum JoinType {
71 Inner,
72 Left,
73 Right,
74 Full,
75 Cross,
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub enum SetOperator {
81 Union,
82 Intersect,
83 Except,
84}
85
86#[derive(Debug, Clone)]
95pub enum LogicalPlan {
96 Pragma {
98 name: String,
100 value: Option<crate::ast::PragmaValue>,
102 },
103
104 Scan {
110 table: String,
112 projection: Projection,
114 },
115
116 Filter {
120 input: Box<LogicalPlan>,
122 predicate: TypedExpr,
124 },
125
126 Project {
132 input: Box<LogicalPlan>,
134 projection: Projection,
136 },
137
138 Join {
140 left: Box<LogicalPlan>,
142 right: Box<LogicalPlan>,
144 join_type: JoinType,
146 condition: Option<TypedExpr>,
148 using: Option<Vec<String>>,
150 },
151
152 Aggregate {
156 input: Box<LogicalPlan>,
158 group_keys: Vec<TypedExpr>,
160 aggregates: Vec<AggregateExpr>,
162 having: Option<TypedExpr>,
164 projection: Projection,
166 },
167
168 Window {
171 input: Box<LogicalPlan>,
172 windows: Vec<WindowExpr>,
173 },
174
175 SetOperation {
177 left: Box<LogicalPlan>,
178 right: Box<LogicalPlan>,
179 operator: SetOperator,
180 all: bool,
181 },
182
183 Sort {
187 input: Box<LogicalPlan>,
189 order_by: Vec<SortExpr>,
191 },
192
193 Limit {
197 input: Box<LogicalPlan>,
199 limit: Option<u64>,
201 offset: Option<u64>,
203 },
204
205 Insert {
212 table: String,
214 columns: Vec<String>,
217 values: Vec<Vec<TypedExpr>>,
219 },
220
221 InsertSelect {
223 table: String,
225 columns: Vec<String>,
227 source: Box<LogicalPlan>,
229 },
230
231 Update {
235 table: String,
237 assignments: Vec<TypedAssignment>,
239 filter: Option<TypedExpr>,
241 },
242
243 Delete {
247 table: String,
249 filter: Option<TypedExpr>,
251 },
252
253 CreateTable {
258 table: TableMetadata,
260 if_not_exists: bool,
262 with_options: Vec<(String, String)>,
264 },
265
266 DropTable {
270 name: String,
272 if_exists: bool,
274 },
275
276 CreateIndex {
280 index: IndexMetadata,
282 if_not_exists: bool,
284 },
285
286 DropIndex {
290 name: String,
292 if_exists: bool,
294 },
295}
296
297impl LogicalPlan {
298 pub fn operation_name(&self) -> &'static str {
299 match self {
300 LogicalPlan::Pragma { .. } => "PRAGMA",
301 LogicalPlan::Scan { .. }
302 | LogicalPlan::Filter { .. }
303 | LogicalPlan::Project { .. }
304 | LogicalPlan::Join { .. }
305 | LogicalPlan::Aggregate { .. }
306 | LogicalPlan::Window { .. }
307 | LogicalPlan::SetOperation { .. }
308 | LogicalPlan::Sort { .. }
309 | LogicalPlan::Limit { .. } => "SELECT",
310 LogicalPlan::Insert { .. } => "INSERT",
311 LogicalPlan::InsertSelect { .. } => "INSERT",
312 LogicalPlan::Update { .. } => "UPDATE",
313 LogicalPlan::Delete { .. } => "DELETE",
314 LogicalPlan::CreateTable { .. } => "CREATE TABLE",
315 LogicalPlan::DropTable { .. } => "DROP TABLE",
316 LogicalPlan::CreateIndex { .. } => "CREATE INDEX",
317 LogicalPlan::DropIndex { .. } => "DROP INDEX",
318 }
319 }
320
321 pub fn scan(table: String, projection: Projection) -> Self {
323 LogicalPlan::Scan { table, projection }
324 }
325
326 pub fn filter(input: LogicalPlan, predicate: TypedExpr) -> Self {
328 LogicalPlan::Filter {
329 input: Box::new(input),
330 predicate,
331 }
332 }
333
334 pub fn project(input: LogicalPlan, projection: Projection) -> Self {
336 LogicalPlan::Project {
337 input: Box::new(input),
338 projection,
339 }
340 }
341
342 pub fn join(
344 left: LogicalPlan,
345 right: LogicalPlan,
346 join_type: JoinType,
347 condition: Option<TypedExpr>,
348 using: Option<Vec<String>>,
349 ) -> Self {
350 LogicalPlan::Join {
351 left: Box::new(left),
352 right: Box::new(right),
353 join_type,
354 condition,
355 using,
356 }
357 }
358
359 pub fn aggregate(
361 input: LogicalPlan,
362 group_keys: Vec<TypedExpr>,
363 aggregates: Vec<AggregateExpr>,
364 having: Option<TypedExpr>,
365 projection: Projection,
366 ) -> Self {
367 LogicalPlan::Aggregate {
368 input: Box::new(input),
369 group_keys,
370 aggregates,
371 having,
372 projection,
373 }
374 }
375
376 pub fn sort(input: LogicalPlan, order_by: Vec<SortExpr>) -> Self {
378 LogicalPlan::Sort {
379 input: Box::new(input),
380 order_by,
381 }
382 }
383
384 pub fn limit(input: LogicalPlan, limit: Option<u64>, offset: Option<u64>) -> Self {
386 LogicalPlan::Limit {
387 input: Box::new(input),
388 limit,
389 offset,
390 }
391 }
392
393 pub fn insert(table: String, columns: Vec<String>, values: Vec<Vec<TypedExpr>>) -> Self {
395 LogicalPlan::Insert {
396 table,
397 columns,
398 values,
399 }
400 }
401
402 pub fn update(
404 table: String,
405 assignments: Vec<TypedAssignment>,
406 filter: Option<TypedExpr>,
407 ) -> Self {
408 LogicalPlan::Update {
409 table,
410 assignments,
411 filter,
412 }
413 }
414
415 pub fn delete(table: String, filter: Option<TypedExpr>) -> Self {
417 LogicalPlan::Delete { table, filter }
418 }
419
420 pub fn create_table(
422 table: TableMetadata,
423 if_not_exists: bool,
424 with_options: Vec<(String, String)>,
425 ) -> Self {
426 LogicalPlan::CreateTable {
427 table,
428 if_not_exists,
429 with_options,
430 }
431 }
432
433 pub fn drop_table(name: String, if_exists: bool) -> Self {
435 LogicalPlan::DropTable { name, if_exists }
436 }
437
438 pub fn create_index(index: IndexMetadata, if_not_exists: bool) -> Self {
440 LogicalPlan::CreateIndex {
441 index,
442 if_not_exists,
443 }
444 }
445
446 pub fn drop_index(name: String, if_exists: bool) -> Self {
448 LogicalPlan::DropIndex { name, if_exists }
449 }
450
451 pub fn name(&self) -> &'static str {
453 match self {
454 LogicalPlan::Pragma { .. } => "Pragma",
455 LogicalPlan::Scan { .. } => "Scan",
456 LogicalPlan::Filter { .. } => "Filter",
457 LogicalPlan::Project { .. } => "Project",
458 LogicalPlan::Join { .. } => "Join",
459 LogicalPlan::Aggregate { .. } => "Aggregate",
460 LogicalPlan::Window { .. } => "Window",
461 LogicalPlan::SetOperation { .. } => "SetOperation",
462 LogicalPlan::Sort { .. } => "Sort",
463 LogicalPlan::Limit { .. } => "Limit",
464 LogicalPlan::Insert { .. } => "Insert",
465 LogicalPlan::InsertSelect { .. } => "InsertSelect",
466 LogicalPlan::Update { .. } => "Update",
467 LogicalPlan::Delete { .. } => "Delete",
468 LogicalPlan::CreateTable { .. } => "CreateTable",
469 LogicalPlan::DropTable { .. } => "DropTable",
470 LogicalPlan::CreateIndex { .. } => "CreateIndex",
471 LogicalPlan::DropIndex { .. } => "DropIndex",
472 }
473 }
474
475 pub fn is_query(&self) -> bool {
477 matches!(
478 self,
479 LogicalPlan::Scan { .. }
480 | LogicalPlan::Filter { .. }
481 | LogicalPlan::Project { .. }
482 | LogicalPlan::Join { .. }
483 | LogicalPlan::Aggregate { .. }
484 | LogicalPlan::Window { .. }
485 | LogicalPlan::SetOperation { .. }
486 | LogicalPlan::Sort { .. }
487 | LogicalPlan::Limit { .. }
488 )
489 }
490
491 pub fn is_dml(&self) -> bool {
493 matches!(
494 self,
495 LogicalPlan::Insert { .. }
496 | LogicalPlan::InsertSelect { .. }
497 | LogicalPlan::Update { .. }
498 | LogicalPlan::Delete { .. }
499 )
500 }
501
502 pub fn is_ddl(&self) -> bool {
504 matches!(
505 self,
506 LogicalPlan::CreateTable { .. }
507 | LogicalPlan::DropTable { .. }
508 | LogicalPlan::CreateIndex { .. }
509 | LogicalPlan::DropIndex { .. }
510 | LogicalPlan::Pragma { .. }
511 )
512 }
513
514 pub fn input(&self) -> Option<&LogicalPlan> {
516 match self {
517 LogicalPlan::Filter { input, .. }
518 | LogicalPlan::Project { input, .. }
519 | LogicalPlan::Aggregate { input, .. }
520 | LogicalPlan::Window { input, .. }
521 | LogicalPlan::Sort { input, .. }
522 | LogicalPlan::Limit { input, .. } => Some(input),
523 LogicalPlan::Join { .. } => None,
524 LogicalPlan::SetOperation { .. } => None,
525 _ => None,
526 }
527 }
528
529 pub fn table_name(&self) -> Option<&str> {
531 match self {
532 LogicalPlan::Scan { table, .. }
533 | LogicalPlan::Insert { table, .. }
534 | LogicalPlan::InsertSelect { table, .. }
535 | LogicalPlan::Update { table, .. }
536 | LogicalPlan::Delete { table, .. } => Some(table),
537 LogicalPlan::CreateTable { table, .. } => Some(&table.name),
538 LogicalPlan::DropTable { name, .. } => Some(name),
539 LogicalPlan::CreateIndex { index, .. } => Some(&index.table),
540 LogicalPlan::DropIndex { .. } => None,
541 LogicalPlan::Pragma { .. } => None,
542 LogicalPlan::Filter { input, .. }
543 | LogicalPlan::Project { input, .. }
544 | LogicalPlan::Aggregate { input, .. }
545 | LogicalPlan::Window { input, .. }
546 | LogicalPlan::Sort { input, .. }
547 | LogicalPlan::Limit { input, .. } => input.table_name(),
548 LogicalPlan::Join { .. } => None,
549 LogicalPlan::SetOperation { left, right, .. } => left
550 .table_name()
551 .filter(|name| right.table_name() == Some(*name)),
552 }
553 }
554
555 pub fn contains_join(&self) -> bool {
562 match self {
563 LogicalPlan::Join { .. } => true,
564 LogicalPlan::SetOperation { left, right, .. } => {
565 left.contains_join() || right.contains_join()
566 }
567 LogicalPlan::Filter { input, .. }
568 | LogicalPlan::Project { input, .. }
569 | LogicalPlan::Aggregate { input, .. }
570 | LogicalPlan::Window { input, .. }
571 | LogicalPlan::Sort { input, .. }
572 | LogicalPlan::Limit { input, .. } => input.contains_join(),
573 _ => false,
574 }
575 }
576
577 pub fn contains_set_operation(&self) -> bool {
579 match self {
580 LogicalPlan::SetOperation { .. } => true,
581 LogicalPlan::Filter { input, .. }
582 | LogicalPlan::Project { input, .. }
583 | LogicalPlan::Aggregate { input, .. }
584 | LogicalPlan::Sort { input, .. }
585 | LogicalPlan::Limit { input, .. } => input.contains_set_operation(),
586 LogicalPlan::Join { left, right, .. } => {
587 left.contains_set_operation() || right.contains_set_operation()
588 }
589 _ => false,
590 }
591 }
592}
593
594#[cfg(test)]
595mod tests {
596 use super::*;
597 use crate::ast::expr::Literal;
598 use crate::ast::span::Span;
599 use crate::catalog::ColumnMetadata;
600 use crate::planner::typed_expr::ProjectedColumn;
601 use crate::planner::types::ResolvedType;
602
603 fn create_test_table_metadata() -> TableMetadata {
604 TableMetadata::new(
605 "users",
606 vec![
607 ColumnMetadata::new("id", ResolvedType::Integer)
608 .with_primary_key(true)
609 .with_not_null(true),
610 ColumnMetadata::new("name", ResolvedType::Text).with_not_null(true),
611 ColumnMetadata::new("email", ResolvedType::Text),
612 ],
613 )
614 .with_primary_key(vec!["id".to_string()])
615 }
616
617 #[test]
618 fn test_scan_plan() {
619 let plan = LogicalPlan::scan(
620 "users".to_string(),
621 Projection::All(vec![
622 "id".to_string(),
623 "name".to_string(),
624 "email".to_string(),
625 ]),
626 );
627
628 assert_eq!(plan.name(), "Scan");
629 assert!(plan.is_query());
630 assert!(!plan.is_dml());
631 assert!(!plan.is_ddl());
632 assert_eq!(plan.table_name(), Some("users"));
633 assert!(plan.input().is_none());
634 }
635
636 #[test]
637 fn test_filter_plan() {
638 let scan = LogicalPlan::scan("users".to_string(), Projection::All(vec![]));
639 let predicate = TypedExpr::column_ref(
640 "users".to_string(),
641 "id".to_string(),
642 0,
643 ResolvedType::Integer,
644 Span::default(),
645 );
646
647 let plan = LogicalPlan::filter(scan, predicate);
648
649 assert_eq!(plan.name(), "Filter");
650 assert!(plan.is_query());
651 assert!(plan.input().is_some());
652 assert_eq!(plan.table_name(), Some("users"));
653 }
654
655 #[test]
656 fn test_sort_plan() {
657 let scan = LogicalPlan::scan("users".to_string(), Projection::All(vec![]));
658 let sort_expr = SortExpr::asc(TypedExpr::column_ref(
659 "users".to_string(),
660 "name".to_string(),
661 1,
662 ResolvedType::Text,
663 Span::default(),
664 ));
665
666 let plan = LogicalPlan::sort(scan, vec![sort_expr]);
667
668 assert_eq!(plan.name(), "Sort");
669 assert!(plan.is_query());
670 }
671
672 #[test]
673 fn test_limit_plan() {
674 let scan = LogicalPlan::scan("users".to_string(), Projection::All(vec![]));
675 let plan = LogicalPlan::limit(scan, Some(10), Some(5));
676
677 assert_eq!(plan.name(), "Limit");
678 assert!(plan.is_query());
679
680 if let LogicalPlan::Limit { limit, offset, .. } = &plan {
681 assert_eq!(*limit, Some(10));
682 assert_eq!(*offset, Some(5));
683 } else {
684 panic!("Expected Limit plan");
685 }
686 }
687
688 #[test]
689 fn test_nested_query_plan() {
690 let scan = LogicalPlan::scan(
692 "users".to_string(),
693 Projection::All(vec!["id".to_string(), "name".to_string()]),
694 );
695
696 let predicate = TypedExpr::literal(
697 Literal::Boolean(true),
698 ResolvedType::Boolean,
699 Span::default(),
700 );
701 let filter = LogicalPlan::filter(scan, predicate);
702
703 let sort_expr = SortExpr::asc(TypedExpr::column_ref(
704 "users".to_string(),
705 "name".to_string(),
706 1,
707 ResolvedType::Text,
708 Span::default(),
709 ));
710 let sort = LogicalPlan::sort(filter, vec![sort_expr]);
711
712 let limit = LogicalPlan::limit(sort, Some(10), None);
713
714 assert_eq!(limit.name(), "Limit");
716 assert_eq!(limit.table_name(), Some("users"));
717
718 let sort_plan = limit.input().unwrap();
719 assert_eq!(sort_plan.name(), "Sort");
720
721 let filter_plan = sort_plan.input().unwrap();
722 assert_eq!(filter_plan.name(), "Filter");
723
724 let scan_plan = filter_plan.input().unwrap();
725 assert_eq!(scan_plan.name(), "Scan");
726 assert!(scan_plan.input().is_none());
727 }
728
729 #[test]
730 fn test_insert_plan() {
731 let value1 = TypedExpr::literal(
732 Literal::Number("1".to_string()),
733 ResolvedType::Integer,
734 Span::default(),
735 );
736 let value2 = TypedExpr::literal(
737 Literal::String("Alice".to_string()),
738 ResolvedType::Text,
739 Span::default(),
740 );
741
742 let plan = LogicalPlan::insert(
743 "users".to_string(),
744 vec!["id".to_string(), "name".to_string()],
745 vec![vec![value1, value2]],
746 );
747
748 assert_eq!(plan.name(), "Insert");
749 assert!(plan.is_dml());
750 assert!(!plan.is_query());
751 assert!(!plan.is_ddl());
752 assert_eq!(plan.table_name(), Some("users"));
753
754 if let LogicalPlan::Insert {
755 table,
756 columns,
757 values,
758 } = &plan
759 {
760 assert_eq!(table, "users");
761 assert_eq!(columns, &vec!["id".to_string(), "name".to_string()]);
762 assert_eq!(values.len(), 1);
763 assert_eq!(values[0].len(), 2);
764 } else {
765 panic!("Expected Insert plan");
766 }
767 }
768
769 #[test]
770 fn test_update_plan() {
771 let assignment = TypedAssignment::new(
772 "name".to_string(),
773 1,
774 TypedExpr::literal(
775 Literal::String("Bob".to_string()),
776 ResolvedType::Text,
777 Span::default(),
778 ),
779 );
780
781 let filter = TypedExpr::literal(
782 Literal::Boolean(true),
783 ResolvedType::Boolean,
784 Span::default(),
785 );
786
787 let plan = LogicalPlan::update("users".to_string(), vec![assignment], Some(filter));
788
789 assert_eq!(plan.name(), "Update");
790 assert!(plan.is_dml());
791 assert_eq!(plan.table_name(), Some("users"));
792 }
793
794 #[test]
795 fn test_delete_plan() {
796 let filter = TypedExpr::column_ref(
797 "users".to_string(),
798 "id".to_string(),
799 0,
800 ResolvedType::Integer,
801 Span::default(),
802 );
803
804 let plan = LogicalPlan::delete("users".to_string(), Some(filter));
805
806 assert_eq!(plan.name(), "Delete");
807 assert!(plan.is_dml());
808 assert_eq!(plan.table_name(), Some("users"));
809 }
810
811 #[test]
812 fn test_create_table_plan() {
813 let table = create_test_table_metadata();
814 let plan = LogicalPlan::create_table(table, false, vec![]);
815
816 assert_eq!(plan.name(), "CreateTable");
817 assert!(plan.is_ddl());
818 assert!(!plan.is_dml());
819 assert!(!plan.is_query());
820 assert_eq!(plan.table_name(), Some("users"));
821 }
822
823 #[test]
824 fn test_drop_table_plan() {
825 let plan = LogicalPlan::drop_table("users".to_string(), true);
826
827 assert_eq!(plan.name(), "DropTable");
828 assert!(plan.is_ddl());
829 assert_eq!(plan.table_name(), Some("users"));
830
831 if let LogicalPlan::DropTable { name, if_exists } = &plan {
832 assert_eq!(name, "users");
833 assert!(*if_exists);
834 } else {
835 panic!("Expected DropTable plan");
836 }
837 }
838
839 #[test]
840 fn test_create_index_plan() {
841 let index = IndexMetadata::new(0, "idx_users_name", "users", vec!["name".into()]);
842 let plan = LogicalPlan::create_index(index, false);
843
844 assert_eq!(plan.name(), "CreateIndex");
845 assert!(plan.is_ddl());
846 assert_eq!(plan.table_name(), Some("users"));
847 }
848
849 #[test]
850 fn test_drop_index_plan() {
851 let plan = LogicalPlan::drop_index("idx_users_name".to_string(), false);
852
853 assert_eq!(plan.name(), "DropIndex");
854 assert!(plan.is_ddl());
855 assert!(plan.table_name().is_none());
857 }
858
859 #[test]
860 fn test_projection_columns() {
861 let col1 = ProjectedColumn::new(TypedExpr::column_ref(
862 "users".to_string(),
863 "id".to_string(),
864 0,
865 ResolvedType::Integer,
866 Span::default(),
867 ));
868 let col2 = ProjectedColumn::with_alias(
869 TypedExpr::column_ref(
870 "users".to_string(),
871 "name".to_string(),
872 1,
873 ResolvedType::Text,
874 Span::default(),
875 ),
876 "user_name".to_string(),
877 );
878
879 let plan = LogicalPlan::scan("users".to_string(), Projection::Columns(vec![col1, col2]));
880
881 if let LogicalPlan::Scan { projection, .. } = &plan {
882 assert_eq!(projection.len(), 2);
883 } else {
884 panic!("Expected Scan plan");
885 }
886 }
887}