1use crate::ast::expr::WindowFrame;
47use crate::catalog::{IndexMetadata, TableMetadata};
48use crate::planner::aggregate_expr::AggregateExpr;
49use crate::planner::typed_expr::{Projection, SortExpr, TypedAssignment, TypedExpr};
50
51#[derive(Debug, Clone)]
53pub enum WindowFunction {
54 RowNumber,
55 Rank,
56 DenseRank,
57 Aggregate(AggregateExpr),
58 Lag(OffsetWindowFunction),
60 Lead(OffsetWindowFunction),
62}
63
64#[derive(Debug, Clone)]
71pub struct OffsetWindowFunction {
72 pub value: TypedExpr,
73 pub offset: Option<TypedExpr>,
74 pub default: Option<TypedExpr>,
75}
76
77#[derive(Debug, Clone)]
79pub struct WindowExpr {
80 pub function: WindowFunction,
81 pub partition_by: Vec<TypedExpr>,
82 pub order_by: Vec<SortExpr>,
83 pub frame: Option<WindowFrame>,
84 pub result_type: crate::planner::types::ResolvedType,
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89pub enum JoinType {
90 Inner,
91 Left,
92 Right,
93 Full,
94 Cross,
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99pub enum SetOperator {
100 Union,
101 Intersect,
102 Except,
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub struct RecursiveCteLimits {
112 pub max_iterations: usize,
113 pub max_rows: usize,
114}
115
116impl Default for RecursiveCteLimits {
117 fn default() -> Self {
118 Self {
119 max_iterations: 1_000,
120 max_rows: 100_000,
121 }
122 }
123}
124
125#[derive(Debug, Clone)]
134pub enum LogicalPlan {
135 Pragma {
137 name: String,
139 value: Option<crate::ast::PragmaValue>,
141 },
142
143 Scan {
149 table: String,
151 projection: Projection,
153 },
154
155 Filter {
159 input: Box<LogicalPlan>,
161 predicate: TypedExpr,
163 },
164
165 Project {
171 input: Box<LogicalPlan>,
173 projection: Projection,
175 },
176
177 Join {
179 left: Box<LogicalPlan>,
181 right: Box<LogicalPlan>,
183 join_type: JoinType,
185 condition: Option<TypedExpr>,
187 using: Option<Vec<String>>,
189 },
190
191 Aggregate {
195 input: Box<LogicalPlan>,
197 group_keys: Vec<TypedExpr>,
199 aggregates: Vec<AggregateExpr>,
201 having: Option<TypedExpr>,
203 projection: Projection,
205 },
206
207 Window {
210 input: Box<LogicalPlan>,
211 windows: Vec<WindowExpr>,
212 },
213
214 SetOperation {
216 left: Box<LogicalPlan>,
217 right: Box<LogicalPlan>,
218 operator: SetOperator,
219 all: bool,
220 },
221
222 RecursiveCte {
225 name: String,
226 anchor: Box<LogicalPlan>,
227 recursive_term: Box<LogicalPlan>,
228 union_all: bool,
229 schema: Vec<crate::catalog::ColumnMetadata>,
230 limits: RecursiveCteLimits,
231 },
232
233 RecursiveReference {
236 name: String,
237 schema: Vec<crate::catalog::ColumnMetadata>,
238 },
239
240 Sort {
244 input: Box<LogicalPlan>,
246 order_by: Vec<SortExpr>,
248 },
249
250 Limit {
254 input: Box<LogicalPlan>,
256 limit: Option<u64>,
258 offset: Option<u64>,
260 },
261
262 Insert {
269 table: String,
271 columns: Vec<String>,
274 values: Vec<Vec<TypedExpr>>,
276 },
277
278 InsertSelect {
280 table: String,
282 columns: Vec<String>,
284 source: Box<LogicalPlan>,
286 },
287
288 Update {
292 table: String,
294 assignments: Vec<TypedAssignment>,
296 filter: Option<TypedExpr>,
298 },
299
300 Delete {
304 table: String,
306 filter: Option<TypedExpr>,
308 },
309
310 CreateTable {
315 table: TableMetadata,
317 if_not_exists: bool,
319 with_options: Vec<(String, String)>,
321 },
322
323 DropTable {
327 name: String,
329 if_exists: bool,
331 },
332
333 CreateIndex {
337 index: IndexMetadata,
339 if_not_exists: bool,
341 },
342
343 DropIndex {
347 name: String,
349 if_exists: bool,
351 },
352}
353
354impl LogicalPlan {
355 pub fn operation_name(&self) -> &'static str {
356 match self {
357 LogicalPlan::Pragma { .. } => "PRAGMA",
358 LogicalPlan::Scan { .. }
359 | LogicalPlan::Filter { .. }
360 | LogicalPlan::Project { .. }
361 | LogicalPlan::Join { .. }
362 | LogicalPlan::Aggregate { .. }
363 | LogicalPlan::Window { .. }
364 | LogicalPlan::SetOperation { .. }
365 | LogicalPlan::RecursiveCte { .. }
366 | LogicalPlan::RecursiveReference { .. }
367 | LogicalPlan::Sort { .. }
368 | LogicalPlan::Limit { .. } => "SELECT",
369 LogicalPlan::Insert { .. } => "INSERT",
370 LogicalPlan::InsertSelect { .. } => "INSERT",
371 LogicalPlan::Update { .. } => "UPDATE",
372 LogicalPlan::Delete { .. } => "DELETE",
373 LogicalPlan::CreateTable { .. } => "CREATE TABLE",
374 LogicalPlan::DropTable { .. } => "DROP TABLE",
375 LogicalPlan::CreateIndex { .. } => "CREATE INDEX",
376 LogicalPlan::DropIndex { .. } => "DROP INDEX",
377 }
378 }
379
380 pub fn scan(table: String, projection: Projection) -> Self {
382 LogicalPlan::Scan { table, projection }
383 }
384
385 pub fn filter(input: LogicalPlan, predicate: TypedExpr) -> Self {
387 LogicalPlan::Filter {
388 input: Box::new(input),
389 predicate,
390 }
391 }
392
393 pub fn project(input: LogicalPlan, projection: Projection) -> Self {
395 LogicalPlan::Project {
396 input: Box::new(input),
397 projection,
398 }
399 }
400
401 pub fn join(
403 left: LogicalPlan,
404 right: LogicalPlan,
405 join_type: JoinType,
406 condition: Option<TypedExpr>,
407 using: Option<Vec<String>>,
408 ) -> Self {
409 LogicalPlan::Join {
410 left: Box::new(left),
411 right: Box::new(right),
412 join_type,
413 condition,
414 using,
415 }
416 }
417
418 pub fn aggregate(
420 input: LogicalPlan,
421 group_keys: Vec<TypedExpr>,
422 aggregates: Vec<AggregateExpr>,
423 having: Option<TypedExpr>,
424 projection: Projection,
425 ) -> Self {
426 LogicalPlan::Aggregate {
427 input: Box::new(input),
428 group_keys,
429 aggregates,
430 having,
431 projection,
432 }
433 }
434
435 pub fn sort(input: LogicalPlan, order_by: Vec<SortExpr>) -> Self {
437 LogicalPlan::Sort {
438 input: Box::new(input),
439 order_by,
440 }
441 }
442
443 pub fn limit(input: LogicalPlan, limit: Option<u64>, offset: Option<u64>) -> Self {
445 LogicalPlan::Limit {
446 input: Box::new(input),
447 limit,
448 offset,
449 }
450 }
451
452 pub fn insert(table: String, columns: Vec<String>, values: Vec<Vec<TypedExpr>>) -> Self {
454 LogicalPlan::Insert {
455 table,
456 columns,
457 values,
458 }
459 }
460
461 pub fn update(
463 table: String,
464 assignments: Vec<TypedAssignment>,
465 filter: Option<TypedExpr>,
466 ) -> Self {
467 LogicalPlan::Update {
468 table,
469 assignments,
470 filter,
471 }
472 }
473
474 pub fn delete(table: String, filter: Option<TypedExpr>) -> Self {
476 LogicalPlan::Delete { table, filter }
477 }
478
479 pub fn create_table(
481 table: TableMetadata,
482 if_not_exists: bool,
483 with_options: Vec<(String, String)>,
484 ) -> Self {
485 LogicalPlan::CreateTable {
486 table,
487 if_not_exists,
488 with_options,
489 }
490 }
491
492 pub fn drop_table(name: String, if_exists: bool) -> Self {
494 LogicalPlan::DropTable { name, if_exists }
495 }
496
497 pub fn create_index(index: IndexMetadata, if_not_exists: bool) -> Self {
499 LogicalPlan::CreateIndex {
500 index,
501 if_not_exists,
502 }
503 }
504
505 pub fn drop_index(name: String, if_exists: bool) -> Self {
507 LogicalPlan::DropIndex { name, if_exists }
508 }
509
510 pub fn name(&self) -> &'static str {
512 match self {
513 LogicalPlan::Pragma { .. } => "Pragma",
514 LogicalPlan::Scan { .. } => "Scan",
515 LogicalPlan::Filter { .. } => "Filter",
516 LogicalPlan::Project { .. } => "Project",
517 LogicalPlan::Join { .. } => "Join",
518 LogicalPlan::Aggregate { .. } => "Aggregate",
519 LogicalPlan::Window { .. } => "Window",
520 LogicalPlan::SetOperation { .. } => "SetOperation",
521 LogicalPlan::RecursiveCte { .. } => "RecursiveCte",
522 LogicalPlan::RecursiveReference { .. } => "RecursiveReference",
523 LogicalPlan::Sort { .. } => "Sort",
524 LogicalPlan::Limit { .. } => "Limit",
525 LogicalPlan::Insert { .. } => "Insert",
526 LogicalPlan::InsertSelect { .. } => "InsertSelect",
527 LogicalPlan::Update { .. } => "Update",
528 LogicalPlan::Delete { .. } => "Delete",
529 LogicalPlan::CreateTable { .. } => "CreateTable",
530 LogicalPlan::DropTable { .. } => "DropTable",
531 LogicalPlan::CreateIndex { .. } => "CreateIndex",
532 LogicalPlan::DropIndex { .. } => "DropIndex",
533 }
534 }
535
536 pub fn is_query(&self) -> bool {
538 matches!(
539 self,
540 LogicalPlan::Scan { .. }
541 | LogicalPlan::Filter { .. }
542 | LogicalPlan::Project { .. }
543 | LogicalPlan::Join { .. }
544 | LogicalPlan::Aggregate { .. }
545 | LogicalPlan::Window { .. }
546 | LogicalPlan::SetOperation { .. }
547 | LogicalPlan::RecursiveCte { .. }
548 | LogicalPlan::RecursiveReference { .. }
549 | LogicalPlan::Sort { .. }
550 | LogicalPlan::Limit { .. }
551 )
552 }
553
554 pub fn is_dml(&self) -> bool {
556 matches!(
557 self,
558 LogicalPlan::Insert { .. }
559 | LogicalPlan::InsertSelect { .. }
560 | LogicalPlan::Update { .. }
561 | LogicalPlan::Delete { .. }
562 )
563 }
564
565 pub fn is_ddl(&self) -> bool {
567 matches!(
568 self,
569 LogicalPlan::CreateTable { .. }
570 | LogicalPlan::DropTable { .. }
571 | LogicalPlan::CreateIndex { .. }
572 | LogicalPlan::DropIndex { .. }
573 | LogicalPlan::Pragma { .. }
574 )
575 }
576
577 pub fn input(&self) -> Option<&LogicalPlan> {
579 match self {
580 LogicalPlan::Filter { input, .. }
581 | LogicalPlan::Project { input, .. }
582 | LogicalPlan::Aggregate { input, .. }
583 | LogicalPlan::Window { input, .. }
584 | LogicalPlan::Sort { input, .. }
585 | LogicalPlan::Limit { input, .. } => Some(input),
586 LogicalPlan::Join { .. } => None,
587 LogicalPlan::SetOperation { .. } => None,
588 LogicalPlan::RecursiveCte { .. } | LogicalPlan::RecursiveReference { .. } => None,
589 _ => None,
590 }
591 }
592
593 pub fn table_name(&self) -> Option<&str> {
595 match self {
596 LogicalPlan::Scan { table, .. }
597 | LogicalPlan::Insert { table, .. }
598 | LogicalPlan::InsertSelect { table, .. }
599 | LogicalPlan::Update { table, .. }
600 | LogicalPlan::Delete { table, .. } => Some(table),
601 LogicalPlan::CreateTable { table, .. } => Some(&table.name),
602 LogicalPlan::DropTable { name, .. } => Some(name),
603 LogicalPlan::CreateIndex { index, .. } => Some(&index.table),
604 LogicalPlan::DropIndex { .. } => None,
605 LogicalPlan::Pragma { .. } => None,
606 LogicalPlan::Filter { input, .. }
607 | LogicalPlan::Project { input, .. }
608 | LogicalPlan::Aggregate { input, .. }
609 | LogicalPlan::Window { input, .. }
610 | LogicalPlan::Sort { input, .. }
611 | LogicalPlan::Limit { input, .. } => input.table_name(),
612 LogicalPlan::Join { .. } => None,
613 LogicalPlan::SetOperation { left, right, .. } => left
614 .table_name()
615 .filter(|name| right.table_name() == Some(*name)),
616 LogicalPlan::RecursiveCte { .. } | LogicalPlan::RecursiveReference { .. } => None,
617 }
618 }
619
620 pub fn contains_join(&self) -> bool {
627 match self {
628 LogicalPlan::Join { .. } => true,
629 LogicalPlan::SetOperation { left, right, .. } => {
630 left.contains_join() || right.contains_join()
631 }
632 LogicalPlan::RecursiveCte {
633 anchor,
634 recursive_term,
635 ..
636 } => anchor.contains_join() || recursive_term.contains_join(),
637 LogicalPlan::Filter { input, .. }
638 | LogicalPlan::Project { input, .. }
639 | LogicalPlan::Aggregate { input, .. }
640 | LogicalPlan::Window { input, .. }
641 | LogicalPlan::Sort { input, .. }
642 | LogicalPlan::Limit { input, .. } => input.contains_join(),
643 _ => false,
644 }
645 }
646
647 pub fn contains_set_operation(&self) -> bool {
649 match self {
650 LogicalPlan::SetOperation { .. } | LogicalPlan::RecursiveCte { .. } => true,
651 LogicalPlan::Filter { input, .. }
652 | LogicalPlan::Project { input, .. }
653 | LogicalPlan::Aggregate { input, .. }
654 | LogicalPlan::Sort { input, .. }
655 | LogicalPlan::Limit { input, .. } => input.contains_set_operation(),
656 LogicalPlan::Join { left, right, .. } => {
657 left.contains_set_operation() || right.contains_set_operation()
658 }
659 _ => false,
660 }
661 }
662}
663
664#[cfg(test)]
665mod tests {
666 use super::*;
667 use crate::ast::expr::Literal;
668 use crate::ast::span::Span;
669 use crate::catalog::ColumnMetadata;
670 use crate::planner::typed_expr::ProjectedColumn;
671 use crate::planner::types::ResolvedType;
672
673 fn create_test_table_metadata() -> TableMetadata {
674 TableMetadata::new(
675 "users",
676 vec![
677 ColumnMetadata::new("id", ResolvedType::Integer)
678 .with_primary_key(true)
679 .with_not_null(true),
680 ColumnMetadata::new("name", ResolvedType::Text).with_not_null(true),
681 ColumnMetadata::new("email", ResolvedType::Text),
682 ],
683 )
684 .with_primary_key(vec!["id".to_string()])
685 }
686
687 #[test]
688 fn test_scan_plan() {
689 let plan = LogicalPlan::scan(
690 "users".to_string(),
691 Projection::All(vec![
692 "id".to_string(),
693 "name".to_string(),
694 "email".to_string(),
695 ]),
696 );
697
698 assert_eq!(plan.name(), "Scan");
699 assert!(plan.is_query());
700 assert!(!plan.is_dml());
701 assert!(!plan.is_ddl());
702 assert_eq!(plan.table_name(), Some("users"));
703 assert!(plan.input().is_none());
704 }
705
706 #[test]
707 fn test_filter_plan() {
708 let scan = LogicalPlan::scan("users".to_string(), Projection::All(vec![]));
709 let predicate = TypedExpr::column_ref(
710 "users".to_string(),
711 "id".to_string(),
712 0,
713 ResolvedType::Integer,
714 Span::default(),
715 );
716
717 let plan = LogicalPlan::filter(scan, predicate);
718
719 assert_eq!(plan.name(), "Filter");
720 assert!(plan.is_query());
721 assert!(plan.input().is_some());
722 assert_eq!(plan.table_name(), Some("users"));
723 }
724
725 #[test]
726 fn test_sort_plan() {
727 let scan = LogicalPlan::scan("users".to_string(), Projection::All(vec![]));
728 let sort_expr = SortExpr::asc(TypedExpr::column_ref(
729 "users".to_string(),
730 "name".to_string(),
731 1,
732 ResolvedType::Text,
733 Span::default(),
734 ));
735
736 let plan = LogicalPlan::sort(scan, vec![sort_expr]);
737
738 assert_eq!(plan.name(), "Sort");
739 assert!(plan.is_query());
740 }
741
742 #[test]
743 fn test_limit_plan() {
744 let scan = LogicalPlan::scan("users".to_string(), Projection::All(vec![]));
745 let plan = LogicalPlan::limit(scan, Some(10), Some(5));
746
747 assert_eq!(plan.name(), "Limit");
748 assert!(plan.is_query());
749
750 if let LogicalPlan::Limit { limit, offset, .. } = &plan {
751 assert_eq!(*limit, Some(10));
752 assert_eq!(*offset, Some(5));
753 } else {
754 panic!("Expected Limit plan");
755 }
756 }
757
758 #[test]
759 fn test_nested_query_plan() {
760 let scan = LogicalPlan::scan(
762 "users".to_string(),
763 Projection::All(vec!["id".to_string(), "name".to_string()]),
764 );
765
766 let predicate = TypedExpr::literal(
767 Literal::Boolean(true),
768 ResolvedType::Boolean,
769 Span::default(),
770 );
771 let filter = LogicalPlan::filter(scan, predicate);
772
773 let sort_expr = SortExpr::asc(TypedExpr::column_ref(
774 "users".to_string(),
775 "name".to_string(),
776 1,
777 ResolvedType::Text,
778 Span::default(),
779 ));
780 let sort = LogicalPlan::sort(filter, vec![sort_expr]);
781
782 let limit = LogicalPlan::limit(sort, Some(10), None);
783
784 assert_eq!(limit.name(), "Limit");
786 assert_eq!(limit.table_name(), Some("users"));
787
788 let sort_plan = limit.input().unwrap();
789 assert_eq!(sort_plan.name(), "Sort");
790
791 let filter_plan = sort_plan.input().unwrap();
792 assert_eq!(filter_plan.name(), "Filter");
793
794 let scan_plan = filter_plan.input().unwrap();
795 assert_eq!(scan_plan.name(), "Scan");
796 assert!(scan_plan.input().is_none());
797 }
798
799 #[test]
800 fn test_insert_plan() {
801 let value1 = TypedExpr::literal(
802 Literal::Number("1".to_string()),
803 ResolvedType::Integer,
804 Span::default(),
805 );
806 let value2 = TypedExpr::literal(
807 Literal::String("Alice".to_string()),
808 ResolvedType::Text,
809 Span::default(),
810 );
811
812 let plan = LogicalPlan::insert(
813 "users".to_string(),
814 vec!["id".to_string(), "name".to_string()],
815 vec![vec![value1, value2]],
816 );
817
818 assert_eq!(plan.name(), "Insert");
819 assert!(plan.is_dml());
820 assert!(!plan.is_query());
821 assert!(!plan.is_ddl());
822 assert_eq!(plan.table_name(), Some("users"));
823
824 if let LogicalPlan::Insert {
825 table,
826 columns,
827 values,
828 } = &plan
829 {
830 assert_eq!(table, "users");
831 assert_eq!(columns, &vec!["id".to_string(), "name".to_string()]);
832 assert_eq!(values.len(), 1);
833 assert_eq!(values[0].len(), 2);
834 } else {
835 panic!("Expected Insert plan");
836 }
837 }
838
839 #[test]
840 fn test_update_plan() {
841 let assignment = TypedAssignment::new(
842 "name".to_string(),
843 1,
844 TypedExpr::literal(
845 Literal::String("Bob".to_string()),
846 ResolvedType::Text,
847 Span::default(),
848 ),
849 );
850
851 let filter = TypedExpr::literal(
852 Literal::Boolean(true),
853 ResolvedType::Boolean,
854 Span::default(),
855 );
856
857 let plan = LogicalPlan::update("users".to_string(), vec![assignment], Some(filter));
858
859 assert_eq!(plan.name(), "Update");
860 assert!(plan.is_dml());
861 assert_eq!(plan.table_name(), Some("users"));
862 }
863
864 #[test]
865 fn test_delete_plan() {
866 let filter = TypedExpr::column_ref(
867 "users".to_string(),
868 "id".to_string(),
869 0,
870 ResolvedType::Integer,
871 Span::default(),
872 );
873
874 let plan = LogicalPlan::delete("users".to_string(), Some(filter));
875
876 assert_eq!(plan.name(), "Delete");
877 assert!(plan.is_dml());
878 assert_eq!(plan.table_name(), Some("users"));
879 }
880
881 #[test]
882 fn test_create_table_plan() {
883 let table = create_test_table_metadata();
884 let plan = LogicalPlan::create_table(table, false, vec![]);
885
886 assert_eq!(plan.name(), "CreateTable");
887 assert!(plan.is_ddl());
888 assert!(!plan.is_dml());
889 assert!(!plan.is_query());
890 assert_eq!(plan.table_name(), Some("users"));
891 }
892
893 #[test]
894 fn test_drop_table_plan() {
895 let plan = LogicalPlan::drop_table("users".to_string(), true);
896
897 assert_eq!(plan.name(), "DropTable");
898 assert!(plan.is_ddl());
899 assert_eq!(plan.table_name(), Some("users"));
900
901 if let LogicalPlan::DropTable { name, if_exists } = &plan {
902 assert_eq!(name, "users");
903 assert!(*if_exists);
904 } else {
905 panic!("Expected DropTable plan");
906 }
907 }
908
909 #[test]
910 fn test_create_index_plan() {
911 let index = IndexMetadata::new(0, "idx_users_name", "users", vec!["name".into()]);
912 let plan = LogicalPlan::create_index(index, false);
913
914 assert_eq!(plan.name(), "CreateIndex");
915 assert!(plan.is_ddl());
916 assert_eq!(plan.table_name(), Some("users"));
917 }
918
919 #[test]
920 fn test_drop_index_plan() {
921 let plan = LogicalPlan::drop_index("idx_users_name".to_string(), false);
922
923 assert_eq!(plan.name(), "DropIndex");
924 assert!(plan.is_ddl());
925 assert!(plan.table_name().is_none());
927 }
928
929 #[test]
930 fn test_projection_columns() {
931 let col1 = ProjectedColumn::new(TypedExpr::column_ref(
932 "users".to_string(),
933 "id".to_string(),
934 0,
935 ResolvedType::Integer,
936 Span::default(),
937 ));
938 let col2 = ProjectedColumn::with_alias(
939 TypedExpr::column_ref(
940 "users".to_string(),
941 "name".to_string(),
942 1,
943 ResolvedType::Text,
944 Span::default(),
945 ),
946 "user_name".to_string(),
947 );
948
949 let plan = LogicalPlan::scan("users".to_string(), Projection::Columns(vec![col1, col2]));
950
951 if let LogicalPlan::Scan { projection, .. } = &plan {
952 assert_eq!(projection.len(), 2);
953 } else {
954 panic!("Expected Scan plan");
955 }
956 }
957}