1use crate::ast::expr::WindowFrame;
48use crate::catalog::{IndexMetadata, TableMetadata};
49use crate::planner::aggregate_expr::AggregateExpr;
50use crate::planner::typed_expr::{Projection, SortExpr, TypedAssignment, TypedExpr};
51
52#[derive(Debug, Clone)]
54pub enum WindowFunction {
55 RowNumber,
56 Rank,
57 DenseRank,
58 PercentRank,
59 CumeDist,
60 Ntile(TypedExpr),
61 Aggregate(AggregateExpr),
62 Value(ValueWindowFunction),
63 Lag(OffsetWindowFunction),
65 Lead(OffsetWindowFunction),
67}
68
69#[derive(Debug, Clone)]
71#[allow(clippy::large_enum_variant)]
74pub enum ValueWindowFunction {
75 FirstValue(TypedExpr),
76 LastValue(TypedExpr),
77 NthValue { value: TypedExpr, nth: TypedExpr },
78}
79
80#[derive(Debug, Clone)]
87pub struct OffsetWindowFunction {
88 pub value: TypedExpr,
89 pub offset: Option<TypedExpr>,
90 pub default: Option<TypedExpr>,
91}
92
93#[derive(Debug, Clone)]
95pub struct WindowExpr {
96 pub function: WindowFunction,
97 pub partition_by: Vec<TypedExpr>,
98 pub order_by: Vec<SortExpr>,
99 pub frame: Option<WindowFrame>,
100 pub result_type: crate::planner::types::ResolvedType,
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub enum JoinType {
106 Inner,
107 Left,
108 Right,
109 Full,
110 Cross,
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub enum SetOperator {
116 Union,
117 Intersect,
118 Except,
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
127pub struct RecursiveCteLimits {
128 pub max_iterations: usize,
129 pub max_rows: usize,
130}
131
132impl Default for RecursiveCteLimits {
133 fn default() -> Self {
134 Self {
135 max_iterations: 1_000,
136 max_rows: 100_000,
137 }
138 }
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq)]
146pub enum TableFunctionKind {
147 Unnest,
149 GenerateSeries,
151}
152
153impl TableFunctionKind {
154 pub fn from_name(name: &str) -> Option<Self> {
156 match name.to_ascii_uppercase().as_str() {
157 "UNNEST" => Some(Self::Unnest),
158 "GENERATE_SERIES" => Some(Self::GenerateSeries),
159 _ => None,
160 }
161 }
162
163 pub fn name(self) -> &'static str {
165 match self {
166 Self::Unnest => "UNNEST",
167 Self::GenerateSeries => "GENERATE_SERIES",
168 }
169 }
170
171 pub fn default_relation_name(self) -> &'static str {
174 match self {
175 Self::Unnest => "unnest",
176 Self::GenerateSeries => "generate_series",
177 }
178 }
179}
180
181#[derive(Debug, Clone)]
190pub enum LogicalPlan {
191 Pragma {
193 name: String,
195 value: Option<crate::ast::PragmaValue>,
197 },
198
199 Scan {
205 table: String,
207 projection: Projection,
209 },
210
211 Values {
213 rows: Vec<Vec<TypedExpr>>,
215 schema: Vec<crate::catalog::ColumnMetadata>,
217 },
218
219 Filter {
223 input: Box<LogicalPlan>,
225 predicate: TypedExpr,
227 },
228
229 Project {
235 input: Box<LogicalPlan>,
237 projection: Projection,
239 },
240
241 Join {
243 left: Box<LogicalPlan>,
245 right: Box<LogicalPlan>,
247 join_type: JoinType,
249 condition: Option<TypedExpr>,
251 using: Option<Vec<String>>,
253 },
254
255 LateralJoin {
261 left: Box<LogicalPlan>,
263 right: Box<LogicalPlan>,
266 join_type: JoinType,
268 condition: Option<TypedExpr>,
270 right_schema: Vec<crate::catalog::ColumnMetadata>,
273 },
274
275 TableFunction {
277 function: TableFunctionKind,
279 args: Vec<TypedExpr>,
281 schema: Vec<crate::catalog::ColumnMetadata>,
283 },
284
285 Aggregate {
289 input: Box<LogicalPlan>,
291 group_keys: Vec<TypedExpr>,
293 aggregates: Vec<AggregateExpr>,
295 having: Option<TypedExpr>,
297 projection: Projection,
299 grouping_sets: Option<Vec<u64>>,
308 },
309
310 Window {
313 input: Box<LogicalPlan>,
314 windows: Vec<WindowExpr>,
315 },
316
317 SetOperation {
319 left: Box<LogicalPlan>,
320 right: Box<LogicalPlan>,
321 operator: SetOperator,
322 all: bool,
323 },
324
325 RecursiveCte {
328 name: String,
329 anchor: Box<LogicalPlan>,
330 recursive_term: Box<LogicalPlan>,
331 union_all: bool,
332 schema: Vec<crate::catalog::ColumnMetadata>,
333 limits: RecursiveCteLimits,
334 },
335
336 RecursiveReference {
339 name: String,
340 schema: Vec<crate::catalog::ColumnMetadata>,
341 },
342
343 Sort {
347 input: Box<LogicalPlan>,
349 order_by: Vec<SortExpr>,
351 },
352
353 DistinctOn {
369 input: Box<LogicalPlan>,
371 key_count: usize,
373 order_by: Vec<SortExpr>,
375 },
376
377 Limit {
383 input: Box<LogicalPlan>,
385 limit: Option<u64>,
387 offset: Option<u64>,
389 ties: Option<Vec<SortExpr>>,
394 },
395
396 Insert {
403 table: String,
405 columns: Vec<String>,
408 values: Vec<Vec<TypedExpr>>,
410 },
411
412 InsertSelect {
414 table: String,
416 columns: Vec<String>,
418 source: Box<LogicalPlan>,
420 },
421
422 Update {
426 table: String,
428 assignments: Vec<TypedAssignment>,
430 filter: Option<TypedExpr>,
432 },
433
434 Delete {
438 table: String,
440 filter: Option<TypedExpr>,
442 },
443
444 CreateTable {
449 table: TableMetadata,
451 if_not_exists: bool,
453 with_options: Vec<(String, String)>,
455 },
456
457 DropTable {
461 name: String,
463 if_exists: bool,
465 },
466
467 CreateIndex {
471 index: IndexMetadata,
473 if_not_exists: bool,
475 },
476
477 DropIndex {
481 name: String,
483 if_exists: bool,
485 },
486}
487
488impl LogicalPlan {
489 pub fn operation_name(&self) -> &'static str {
490 match self {
491 LogicalPlan::Pragma { .. } => "PRAGMA",
492 LogicalPlan::Scan { .. }
493 | LogicalPlan::Values { .. }
494 | LogicalPlan::Filter { .. }
495 | LogicalPlan::Project { .. }
496 | LogicalPlan::Join { .. }
497 | LogicalPlan::LateralJoin { .. }
498 | LogicalPlan::TableFunction { .. }
499 | LogicalPlan::Aggregate { .. }
500 | LogicalPlan::Window { .. }
501 | LogicalPlan::SetOperation { .. }
502 | LogicalPlan::RecursiveCte { .. }
503 | LogicalPlan::RecursiveReference { .. }
504 | LogicalPlan::Sort { .. }
505 | LogicalPlan::DistinctOn { .. }
506 | LogicalPlan::Limit { .. } => "SELECT",
507 LogicalPlan::Insert { .. } => "INSERT",
508 LogicalPlan::InsertSelect { .. } => "INSERT",
509 LogicalPlan::Update { .. } => "UPDATE",
510 LogicalPlan::Delete { .. } => "DELETE",
511 LogicalPlan::CreateTable { .. } => "CREATE TABLE",
512 LogicalPlan::DropTable { .. } => "DROP TABLE",
513 LogicalPlan::CreateIndex { .. } => "CREATE INDEX",
514 LogicalPlan::DropIndex { .. } => "DROP INDEX",
515 }
516 }
517
518 pub fn scan(table: String, projection: Projection) -> Self {
520 LogicalPlan::Scan { table, projection }
521 }
522
523 pub fn filter(input: LogicalPlan, predicate: TypedExpr) -> Self {
525 LogicalPlan::Filter {
526 input: Box::new(input),
527 predicate,
528 }
529 }
530
531 pub fn project(input: LogicalPlan, projection: Projection) -> Self {
533 LogicalPlan::Project {
534 input: Box::new(input),
535 projection,
536 }
537 }
538
539 pub fn join(
541 left: LogicalPlan,
542 right: LogicalPlan,
543 join_type: JoinType,
544 condition: Option<TypedExpr>,
545 using: Option<Vec<String>>,
546 ) -> Self {
547 LogicalPlan::Join {
548 left: Box::new(left),
549 right: Box::new(right),
550 join_type,
551 condition,
552 using,
553 }
554 }
555
556 pub fn aggregate(
558 input: LogicalPlan,
559 group_keys: Vec<TypedExpr>,
560 aggregates: Vec<AggregateExpr>,
561 having: Option<TypedExpr>,
562 projection: Projection,
563 ) -> Self {
564 LogicalPlan::Aggregate {
565 input: Box::new(input),
566 group_keys,
567 aggregates,
568 having,
569 projection,
570 grouping_sets: None,
571 }
572 }
573
574 pub fn sort(input: LogicalPlan, order_by: Vec<SortExpr>) -> Self {
576 LogicalPlan::Sort {
577 input: Box::new(input),
578 order_by,
579 }
580 }
581
582 pub fn distinct_on(input: LogicalPlan, key_count: usize, order_by: Vec<SortExpr>) -> Self {
584 LogicalPlan::DistinctOn {
585 input: Box::new(input),
586 key_count,
587 order_by,
588 }
589 }
590
591 pub fn limit(input: LogicalPlan, limit: Option<u64>, offset: Option<u64>) -> Self {
593 LogicalPlan::Limit {
594 input: Box::new(input),
595 limit,
596 offset,
597 ties: None,
598 }
599 }
600
601 pub fn insert(table: String, columns: Vec<String>, values: Vec<Vec<TypedExpr>>) -> Self {
603 LogicalPlan::Insert {
604 table,
605 columns,
606 values,
607 }
608 }
609
610 pub fn update(
612 table: String,
613 assignments: Vec<TypedAssignment>,
614 filter: Option<TypedExpr>,
615 ) -> Self {
616 LogicalPlan::Update {
617 table,
618 assignments,
619 filter,
620 }
621 }
622
623 pub fn delete(table: String, filter: Option<TypedExpr>) -> Self {
625 LogicalPlan::Delete { table, filter }
626 }
627
628 pub fn create_table(
630 table: TableMetadata,
631 if_not_exists: bool,
632 with_options: Vec<(String, String)>,
633 ) -> Self {
634 LogicalPlan::CreateTable {
635 table,
636 if_not_exists,
637 with_options,
638 }
639 }
640
641 pub fn drop_table(name: String, if_exists: bool) -> Self {
643 LogicalPlan::DropTable { name, if_exists }
644 }
645
646 pub fn create_index(index: IndexMetadata, if_not_exists: bool) -> Self {
648 LogicalPlan::CreateIndex {
649 index,
650 if_not_exists,
651 }
652 }
653
654 pub fn drop_index(name: String, if_exists: bool) -> Self {
656 LogicalPlan::DropIndex { name, if_exists }
657 }
658
659 pub fn name(&self) -> &'static str {
661 match self {
662 LogicalPlan::Pragma { .. } => "Pragma",
663 LogicalPlan::Scan { .. } => "Scan",
664 LogicalPlan::Values { .. } => "Values",
665 LogicalPlan::Filter { .. } => "Filter",
666 LogicalPlan::Project { .. } => "Project",
667 LogicalPlan::Join { .. } => "Join",
668 LogicalPlan::LateralJoin { .. } => "LateralJoin",
669 LogicalPlan::TableFunction { .. } => "TableFunction",
670 LogicalPlan::Aggregate { .. } => "Aggregate",
671 LogicalPlan::Window { .. } => "Window",
672 LogicalPlan::SetOperation { .. } => "SetOperation",
673 LogicalPlan::RecursiveCte { .. } => "RecursiveCte",
674 LogicalPlan::RecursiveReference { .. } => "RecursiveReference",
675 LogicalPlan::Sort { .. } => "Sort",
676 LogicalPlan::DistinctOn { .. } => "DistinctOn",
677 LogicalPlan::Limit { .. } => "Limit",
678 LogicalPlan::Insert { .. } => "Insert",
679 LogicalPlan::InsertSelect { .. } => "InsertSelect",
680 LogicalPlan::Update { .. } => "Update",
681 LogicalPlan::Delete { .. } => "Delete",
682 LogicalPlan::CreateTable { .. } => "CreateTable",
683 LogicalPlan::DropTable { .. } => "DropTable",
684 LogicalPlan::CreateIndex { .. } => "CreateIndex",
685 LogicalPlan::DropIndex { .. } => "DropIndex",
686 }
687 }
688
689 pub fn is_query(&self) -> bool {
691 matches!(
692 self,
693 LogicalPlan::Scan { .. }
694 | LogicalPlan::Values { .. }
695 | LogicalPlan::Filter { .. }
696 | LogicalPlan::Project { .. }
697 | LogicalPlan::Join { .. }
698 | LogicalPlan::LateralJoin { .. }
699 | LogicalPlan::TableFunction { .. }
700 | LogicalPlan::Aggregate { .. }
701 | LogicalPlan::Window { .. }
702 | LogicalPlan::SetOperation { .. }
703 | LogicalPlan::RecursiveCte { .. }
704 | LogicalPlan::RecursiveReference { .. }
705 | LogicalPlan::Sort { .. }
706 | LogicalPlan::DistinctOn { .. }
707 | LogicalPlan::Limit { .. }
708 )
709 }
710
711 pub fn is_dml(&self) -> bool {
713 matches!(
714 self,
715 LogicalPlan::Insert { .. }
716 | LogicalPlan::InsertSelect { .. }
717 | LogicalPlan::Update { .. }
718 | LogicalPlan::Delete { .. }
719 )
720 }
721
722 pub fn is_ddl(&self) -> bool {
724 matches!(
725 self,
726 LogicalPlan::CreateTable { .. }
727 | LogicalPlan::DropTable { .. }
728 | LogicalPlan::CreateIndex { .. }
729 | LogicalPlan::DropIndex { .. }
730 | LogicalPlan::Pragma { .. }
731 )
732 }
733
734 pub fn input(&self) -> Option<&LogicalPlan> {
736 match self {
737 LogicalPlan::Filter { input, .. }
738 | LogicalPlan::Project { input, .. }
739 | LogicalPlan::Aggregate { input, .. }
740 | LogicalPlan::Window { input, .. }
741 | LogicalPlan::Sort { input, .. }
742 | LogicalPlan::DistinctOn { input, .. }
743 | LogicalPlan::Limit { input, .. } => Some(input),
744 LogicalPlan::Join { .. }
745 | LogicalPlan::LateralJoin { .. }
746 | LogicalPlan::TableFunction { .. }
747 | LogicalPlan::Values { .. } => None,
748 LogicalPlan::SetOperation { .. } => None,
749 LogicalPlan::RecursiveCte { .. } | LogicalPlan::RecursiveReference { .. } => None,
750 _ => None,
751 }
752 }
753
754 pub fn table_name(&self) -> Option<&str> {
756 match self {
757 LogicalPlan::Scan { table, .. }
758 | LogicalPlan::Insert { table, .. }
759 | LogicalPlan::InsertSelect { table, .. }
760 | LogicalPlan::Update { table, .. }
761 | LogicalPlan::Delete { table, .. } => Some(table),
762 LogicalPlan::CreateTable { table, .. } => Some(&table.name),
763 LogicalPlan::DropTable { name, .. } => Some(name),
764 LogicalPlan::CreateIndex { index, .. } => Some(&index.table),
765 LogicalPlan::DropIndex { .. } => None,
766 LogicalPlan::Pragma { .. } => None,
767 LogicalPlan::Values { .. } => None,
768 LogicalPlan::Filter { input, .. }
769 | LogicalPlan::Project { input, .. }
770 | LogicalPlan::Aggregate { input, .. }
771 | LogicalPlan::Window { input, .. }
772 | LogicalPlan::Sort { input, .. }
773 | LogicalPlan::DistinctOn { input, .. }
774 | LogicalPlan::Limit { input, .. } => input.table_name(),
775 LogicalPlan::Join { .. }
776 | LogicalPlan::LateralJoin { .. }
777 | LogicalPlan::TableFunction { .. } => None,
778 LogicalPlan::SetOperation { left, right, .. } => left
779 .table_name()
780 .filter(|name| right.table_name() == Some(*name)),
781 LogicalPlan::RecursiveCte { .. } | LogicalPlan::RecursiveReference { .. } => None,
782 }
783 }
784
785 pub fn contains_join(&self) -> bool {
792 match self {
793 LogicalPlan::Join { .. } | LogicalPlan::LateralJoin { .. } => true,
794 LogicalPlan::SetOperation { left, right, .. } => {
795 left.contains_join() || right.contains_join()
796 }
797 LogicalPlan::RecursiveCte {
798 anchor,
799 recursive_term,
800 ..
801 } => anchor.contains_join() || recursive_term.contains_join(),
802 LogicalPlan::Filter { input, .. }
803 | LogicalPlan::Project { input, .. }
804 | LogicalPlan::Aggregate { input, .. }
805 | LogicalPlan::Window { input, .. }
806 | LogicalPlan::Sort { input, .. }
807 | LogicalPlan::DistinctOn { input, .. }
808 | LogicalPlan::Limit { input, .. } => input.contains_join(),
809 _ => false,
810 }
811 }
812
813 pub fn contains_set_operation(&self) -> bool {
815 match self {
816 LogicalPlan::SetOperation { .. } | LogicalPlan::RecursiveCte { .. } => true,
817 LogicalPlan::Filter { input, .. }
818 | LogicalPlan::Project { input, .. }
819 | LogicalPlan::Aggregate { input, .. }
820 | LogicalPlan::Sort { input, .. }
821 | LogicalPlan::DistinctOn { input, .. }
822 | LogicalPlan::Limit { input, .. } => input.contains_set_operation(),
823 LogicalPlan::Join { left, right, .. }
824 | LogicalPlan::LateralJoin { left, right, .. } => {
825 left.contains_set_operation() || right.contains_set_operation()
826 }
827 _ => false,
828 }
829 }
830}
831
832#[cfg(test)]
833mod tests {
834 use super::*;
835 use crate::ast::expr::Literal;
836 use crate::ast::span::Span;
837 use crate::catalog::ColumnMetadata;
838 use crate::planner::typed_expr::ProjectedColumn;
839 use crate::planner::types::ResolvedType;
840
841 fn create_test_table_metadata() -> TableMetadata {
842 TableMetadata::new(
843 "users",
844 vec![
845 ColumnMetadata::new("id", ResolvedType::Integer)
846 .with_primary_key(true)
847 .with_not_null(true),
848 ColumnMetadata::new("name", ResolvedType::Text).with_not_null(true),
849 ColumnMetadata::new("email", ResolvedType::Text),
850 ],
851 )
852 .with_primary_key(vec!["id".to_string()])
853 }
854
855 #[test]
856 fn test_scan_plan() {
857 let plan = LogicalPlan::scan(
858 "users".to_string(),
859 Projection::All(vec![
860 "id".to_string(),
861 "name".to_string(),
862 "email".to_string(),
863 ]),
864 );
865
866 assert_eq!(plan.name(), "Scan");
867 assert!(plan.is_query());
868 assert!(!plan.is_dml());
869 assert!(!plan.is_ddl());
870 assert_eq!(plan.table_name(), Some("users"));
871 assert!(plan.input().is_none());
872 }
873
874 #[test]
875 fn test_filter_plan() {
876 let scan = LogicalPlan::scan("users".to_string(), Projection::All(vec![]));
877 let predicate = TypedExpr::column_ref(
878 "users".to_string(),
879 "id".to_string(),
880 0,
881 ResolvedType::Integer,
882 Span::default(),
883 );
884
885 let plan = LogicalPlan::filter(scan, predicate);
886
887 assert_eq!(plan.name(), "Filter");
888 assert!(plan.is_query());
889 assert!(plan.input().is_some());
890 assert_eq!(plan.table_name(), Some("users"));
891 }
892
893 #[test]
894 fn test_sort_plan() {
895 let scan = LogicalPlan::scan("users".to_string(), Projection::All(vec![]));
896 let sort_expr = SortExpr::asc(TypedExpr::column_ref(
897 "users".to_string(),
898 "name".to_string(),
899 1,
900 ResolvedType::Text,
901 Span::default(),
902 ));
903
904 let plan = LogicalPlan::sort(scan, vec![sort_expr]);
905
906 assert_eq!(plan.name(), "Sort");
907 assert!(plan.is_query());
908 }
909
910 #[test]
911 fn test_limit_plan() {
912 let scan = LogicalPlan::scan("users".to_string(), Projection::All(vec![]));
913 let plan = LogicalPlan::limit(scan, Some(10), Some(5));
914
915 assert_eq!(plan.name(), "Limit");
916 assert!(plan.is_query());
917
918 if let LogicalPlan::Limit { limit, offset, .. } = &plan {
919 assert_eq!(*limit, Some(10));
920 assert_eq!(*offset, Some(5));
921 } else {
922 panic!("Expected Limit plan");
923 }
924 }
925
926 #[test]
927 fn test_nested_query_plan() {
928 let scan = LogicalPlan::scan(
930 "users".to_string(),
931 Projection::All(vec!["id".to_string(), "name".to_string()]),
932 );
933
934 let predicate = TypedExpr::literal(
935 Literal::Boolean(true),
936 ResolvedType::Boolean,
937 Span::default(),
938 );
939 let filter = LogicalPlan::filter(scan, predicate);
940
941 let sort_expr = SortExpr::asc(TypedExpr::column_ref(
942 "users".to_string(),
943 "name".to_string(),
944 1,
945 ResolvedType::Text,
946 Span::default(),
947 ));
948 let sort = LogicalPlan::sort(filter, vec![sort_expr]);
949
950 let limit = LogicalPlan::limit(sort, Some(10), None);
951
952 assert_eq!(limit.name(), "Limit");
954 assert_eq!(limit.table_name(), Some("users"));
955
956 let sort_plan = limit.input().unwrap();
957 assert_eq!(sort_plan.name(), "Sort");
958
959 let filter_plan = sort_plan.input().unwrap();
960 assert_eq!(filter_plan.name(), "Filter");
961
962 let scan_plan = filter_plan.input().unwrap();
963 assert_eq!(scan_plan.name(), "Scan");
964 assert!(scan_plan.input().is_none());
965 }
966
967 #[test]
968 fn test_insert_plan() {
969 let value1 = TypedExpr::literal(
970 Literal::Number("1".to_string()),
971 ResolvedType::Integer,
972 Span::default(),
973 );
974 let value2 = TypedExpr::literal(
975 Literal::String("Alice".to_string()),
976 ResolvedType::Text,
977 Span::default(),
978 );
979
980 let plan = LogicalPlan::insert(
981 "users".to_string(),
982 vec!["id".to_string(), "name".to_string()],
983 vec![vec![value1, value2]],
984 );
985
986 assert_eq!(plan.name(), "Insert");
987 assert!(plan.is_dml());
988 assert!(!plan.is_query());
989 assert!(!plan.is_ddl());
990 assert_eq!(plan.table_name(), Some("users"));
991
992 if let LogicalPlan::Insert {
993 table,
994 columns,
995 values,
996 } = &plan
997 {
998 assert_eq!(table, "users");
999 assert_eq!(columns, &vec!["id".to_string(), "name".to_string()]);
1000 assert_eq!(values.len(), 1);
1001 assert_eq!(values[0].len(), 2);
1002 } else {
1003 panic!("Expected Insert plan");
1004 }
1005 }
1006
1007 #[test]
1008 fn test_update_plan() {
1009 let assignment = TypedAssignment::new(
1010 "name".to_string(),
1011 1,
1012 TypedExpr::literal(
1013 Literal::String("Bob".to_string()),
1014 ResolvedType::Text,
1015 Span::default(),
1016 ),
1017 );
1018
1019 let filter = TypedExpr::literal(
1020 Literal::Boolean(true),
1021 ResolvedType::Boolean,
1022 Span::default(),
1023 );
1024
1025 let plan = LogicalPlan::update("users".to_string(), vec![assignment], Some(filter));
1026
1027 assert_eq!(plan.name(), "Update");
1028 assert!(plan.is_dml());
1029 assert_eq!(plan.table_name(), Some("users"));
1030 }
1031
1032 #[test]
1033 fn test_delete_plan() {
1034 let filter = TypedExpr::column_ref(
1035 "users".to_string(),
1036 "id".to_string(),
1037 0,
1038 ResolvedType::Integer,
1039 Span::default(),
1040 );
1041
1042 let plan = LogicalPlan::delete("users".to_string(), Some(filter));
1043
1044 assert_eq!(plan.name(), "Delete");
1045 assert!(plan.is_dml());
1046 assert_eq!(plan.table_name(), Some("users"));
1047 }
1048
1049 #[test]
1050 fn test_create_table_plan() {
1051 let table = create_test_table_metadata();
1052 let plan = LogicalPlan::create_table(table, false, vec![]);
1053
1054 assert_eq!(plan.name(), "CreateTable");
1055 assert!(plan.is_ddl());
1056 assert!(!plan.is_dml());
1057 assert!(!plan.is_query());
1058 assert_eq!(plan.table_name(), Some("users"));
1059 }
1060
1061 #[test]
1062 fn test_drop_table_plan() {
1063 let plan = LogicalPlan::drop_table("users".to_string(), true);
1064
1065 assert_eq!(plan.name(), "DropTable");
1066 assert!(plan.is_ddl());
1067 assert_eq!(plan.table_name(), Some("users"));
1068
1069 if let LogicalPlan::DropTable { name, if_exists } = &plan {
1070 assert_eq!(name, "users");
1071 assert!(*if_exists);
1072 } else {
1073 panic!("Expected DropTable plan");
1074 }
1075 }
1076
1077 #[test]
1078 fn test_create_index_plan() {
1079 let index = IndexMetadata::new(0, "idx_users_name", "users", vec!["name".into()]);
1080 let plan = LogicalPlan::create_index(index, false);
1081
1082 assert_eq!(plan.name(), "CreateIndex");
1083 assert!(plan.is_ddl());
1084 assert_eq!(plan.table_name(), Some("users"));
1085 }
1086
1087 #[test]
1088 fn test_drop_index_plan() {
1089 let plan = LogicalPlan::drop_index("idx_users_name".to_string(), false);
1090
1091 assert_eq!(plan.name(), "DropIndex");
1092 assert!(plan.is_ddl());
1093 assert!(plan.table_name().is_none());
1095 }
1096
1097 #[test]
1098 fn test_projection_columns() {
1099 let col1 = ProjectedColumn::new(TypedExpr::column_ref(
1100 "users".to_string(),
1101 "id".to_string(),
1102 0,
1103 ResolvedType::Integer,
1104 Span::default(),
1105 ));
1106 let col2 = ProjectedColumn::with_alias(
1107 TypedExpr::column_ref(
1108 "users".to_string(),
1109 "name".to_string(),
1110 1,
1111 ResolvedType::Text,
1112 Span::default(),
1113 ),
1114 "user_name".to_string(),
1115 );
1116
1117 let plan = LogicalPlan::scan("users".to_string(), Projection::Columns(vec![col1, col2]));
1118
1119 if let LogicalPlan::Scan { projection, .. } = &plan {
1120 assert_eq!(projection.len(), 2);
1121 } else {
1122 panic!("Expected Scan plan");
1123 }
1124 }
1125}