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 UnnestWithOrdinality,
151 GenerateSeries,
153 JsonEach,
155 JsonTree,
157 FtsSearch,
159}
160
161impl TableFunctionKind {
162 pub fn from_name(name: &str) -> Option<Self> {
164 match name.to_ascii_uppercase().as_str() {
165 "UNNEST" => Some(Self::Unnest),
166 "GENERATE_SERIES" => Some(Self::GenerateSeries),
167 "JSON_EACH" => Some(Self::JsonEach),
168 "JSON_TREE" => Some(Self::JsonTree),
169 "FTS_SEARCH" => Some(Self::FtsSearch),
170 _ => None,
171 }
172 }
173
174 pub fn name(self) -> &'static str {
176 match self {
177 Self::Unnest | Self::UnnestWithOrdinality => "UNNEST",
178 Self::GenerateSeries => "GENERATE_SERIES",
179 Self::JsonEach => "JSON_EACH",
180 Self::JsonTree => "JSON_TREE",
181 Self::FtsSearch => "FTS_SEARCH",
182 }
183 }
184
185 pub fn default_relation_name(self) -> &'static str {
188 match self {
189 Self::Unnest | Self::UnnestWithOrdinality => "unnest",
190 Self::GenerateSeries => "generate_series",
191 Self::JsonEach => "json_each",
192 Self::JsonTree => "json_tree",
193 Self::FtsSearch => "fts_search",
194 }
195 }
196}
197
198#[derive(Debug, Clone)]
207pub enum LogicalPlan {
208 Pragma {
210 name: String,
212 value: Option<crate::ast::PragmaValue>,
214 },
215
216 Scan {
222 table: String,
224 projection: Projection,
226 },
227
228 Values {
230 rows: Vec<Vec<TypedExpr>>,
232 schema: Vec<crate::catalog::ColumnMetadata>,
234 },
235
236 Filter {
240 input: Box<LogicalPlan>,
242 predicate: TypedExpr,
244 },
245
246 Project {
252 input: Box<LogicalPlan>,
254 projection: Projection,
256 },
257
258 Join {
260 left: Box<LogicalPlan>,
262 right: Box<LogicalPlan>,
264 join_type: JoinType,
266 condition: Option<TypedExpr>,
268 using: Option<Vec<String>>,
270 },
271
272 LateralJoin {
278 left: Box<LogicalPlan>,
280 right: Box<LogicalPlan>,
283 join_type: JoinType,
285 condition: Option<TypedExpr>,
287 right_schema: Vec<crate::catalog::ColumnMetadata>,
290 },
291
292 TableFunction {
294 function: TableFunctionKind,
296 args: Vec<TypedExpr>,
298 schema: Vec<crate::catalog::ColumnMetadata>,
300 },
301
302 Aggregate {
306 input: Box<LogicalPlan>,
308 group_keys: Vec<TypedExpr>,
310 aggregates: Vec<AggregateExpr>,
312 having: Option<TypedExpr>,
314 projection: Projection,
316 grouping_sets: Option<Vec<u64>>,
325 },
326
327 Window {
330 input: Box<LogicalPlan>,
331 windows: Vec<WindowExpr>,
332 },
333
334 SetOperation {
336 left: Box<LogicalPlan>,
337 right: Box<LogicalPlan>,
338 operator: SetOperator,
339 all: bool,
340 },
341
342 RecursiveCte {
345 name: String,
346 anchor: Box<LogicalPlan>,
347 recursive_term: Box<LogicalPlan>,
348 union_all: bool,
349 schema: Vec<crate::catalog::ColumnMetadata>,
350 limits: RecursiveCteLimits,
351 },
352
353 RecursiveReference {
356 name: String,
357 schema: Vec<crate::catalog::ColumnMetadata>,
358 },
359
360 Sort {
364 input: Box<LogicalPlan>,
366 order_by: Vec<SortExpr>,
368 },
369
370 DistinctOn {
386 input: Box<LogicalPlan>,
388 key_count: usize,
390 order_by: Vec<SortExpr>,
392 },
393
394 Limit {
400 input: Box<LogicalPlan>,
402 limit: Option<u64>,
404 offset: Option<u64>,
406 ties: Option<Vec<SortExpr>>,
411 },
412
413 Insert {
420 table: String,
422 columns: Vec<String>,
425 values: Vec<Vec<TypedExpr>>,
427 },
428
429 InsertSelect {
431 table: String,
433 columns: Vec<String>,
435 source: Box<LogicalPlan>,
437 },
438
439 Update {
443 table: String,
445 assignments: Vec<TypedAssignment>,
447 filter: Option<TypedExpr>,
449 },
450
451 Delete {
455 table: String,
457 filter: Option<TypedExpr>,
459 },
460
461 CreateTable {
466 table: TableMetadata,
468 if_not_exists: bool,
470 with_options: Vec<(String, String)>,
472 },
473
474 DropTable {
478 name: String,
480 if_exists: bool,
482 },
483
484 CreateIndex {
488 index: IndexMetadata,
490 if_not_exists: bool,
492 },
493
494 DropIndex {
498 name: String,
500 if_exists: bool,
502 },
503}
504
505impl LogicalPlan {
506 pub fn operation_name(&self) -> &'static str {
507 match self {
508 LogicalPlan::Pragma { .. } => "PRAGMA",
509 LogicalPlan::Scan { .. }
510 | LogicalPlan::Values { .. }
511 | LogicalPlan::Filter { .. }
512 | LogicalPlan::Project { .. }
513 | LogicalPlan::Join { .. }
514 | LogicalPlan::LateralJoin { .. }
515 | LogicalPlan::TableFunction { .. }
516 | LogicalPlan::Aggregate { .. }
517 | LogicalPlan::Window { .. }
518 | LogicalPlan::SetOperation { .. }
519 | LogicalPlan::RecursiveCte { .. }
520 | LogicalPlan::RecursiveReference { .. }
521 | LogicalPlan::Sort { .. }
522 | LogicalPlan::DistinctOn { .. }
523 | LogicalPlan::Limit { .. } => "SELECT",
524 LogicalPlan::Insert { .. } => "INSERT",
525 LogicalPlan::InsertSelect { .. } => "INSERT",
526 LogicalPlan::Update { .. } => "UPDATE",
527 LogicalPlan::Delete { .. } => "DELETE",
528 LogicalPlan::CreateTable { .. } => "CREATE TABLE",
529 LogicalPlan::DropTable { .. } => "DROP TABLE",
530 LogicalPlan::CreateIndex { .. } => "CREATE INDEX",
531 LogicalPlan::DropIndex { .. } => "DROP INDEX",
532 }
533 }
534
535 pub fn scan(table: String, projection: Projection) -> Self {
537 LogicalPlan::Scan { table, projection }
538 }
539
540 pub fn filter(input: LogicalPlan, predicate: TypedExpr) -> Self {
542 LogicalPlan::Filter {
543 input: Box::new(input),
544 predicate,
545 }
546 }
547
548 pub fn project(input: LogicalPlan, projection: Projection) -> Self {
550 LogicalPlan::Project {
551 input: Box::new(input),
552 projection,
553 }
554 }
555
556 pub fn join(
558 left: LogicalPlan,
559 right: LogicalPlan,
560 join_type: JoinType,
561 condition: Option<TypedExpr>,
562 using: Option<Vec<String>>,
563 ) -> Self {
564 LogicalPlan::Join {
565 left: Box::new(left),
566 right: Box::new(right),
567 join_type,
568 condition,
569 using,
570 }
571 }
572
573 pub fn aggregate(
575 input: LogicalPlan,
576 group_keys: Vec<TypedExpr>,
577 aggregates: Vec<AggregateExpr>,
578 having: Option<TypedExpr>,
579 projection: Projection,
580 ) -> Self {
581 LogicalPlan::Aggregate {
582 input: Box::new(input),
583 group_keys,
584 aggregates,
585 having,
586 projection,
587 grouping_sets: None,
588 }
589 }
590
591 pub fn sort(input: LogicalPlan, order_by: Vec<SortExpr>) -> Self {
593 LogicalPlan::Sort {
594 input: Box::new(input),
595 order_by,
596 }
597 }
598
599 pub fn distinct_on(input: LogicalPlan, key_count: usize, order_by: Vec<SortExpr>) -> Self {
601 LogicalPlan::DistinctOn {
602 input: Box::new(input),
603 key_count,
604 order_by,
605 }
606 }
607
608 pub fn limit(input: LogicalPlan, limit: Option<u64>, offset: Option<u64>) -> Self {
610 LogicalPlan::Limit {
611 input: Box::new(input),
612 limit,
613 offset,
614 ties: None,
615 }
616 }
617
618 pub fn insert(table: String, columns: Vec<String>, values: Vec<Vec<TypedExpr>>) -> Self {
620 LogicalPlan::Insert {
621 table,
622 columns,
623 values,
624 }
625 }
626
627 pub fn update(
629 table: String,
630 assignments: Vec<TypedAssignment>,
631 filter: Option<TypedExpr>,
632 ) -> Self {
633 LogicalPlan::Update {
634 table,
635 assignments,
636 filter,
637 }
638 }
639
640 pub fn delete(table: String, filter: Option<TypedExpr>) -> Self {
642 LogicalPlan::Delete { table, filter }
643 }
644
645 pub fn create_table(
647 table: TableMetadata,
648 if_not_exists: bool,
649 with_options: Vec<(String, String)>,
650 ) -> Self {
651 LogicalPlan::CreateTable {
652 table,
653 if_not_exists,
654 with_options,
655 }
656 }
657
658 pub fn drop_table(name: String, if_exists: bool) -> Self {
660 LogicalPlan::DropTable { name, if_exists }
661 }
662
663 pub fn create_index(index: IndexMetadata, if_not_exists: bool) -> Self {
665 LogicalPlan::CreateIndex {
666 index,
667 if_not_exists,
668 }
669 }
670
671 pub fn drop_index(name: String, if_exists: bool) -> Self {
673 LogicalPlan::DropIndex { name, if_exists }
674 }
675
676 pub fn name(&self) -> &'static str {
678 match self {
679 LogicalPlan::Pragma { .. } => "Pragma",
680 LogicalPlan::Scan { .. } => "Scan",
681 LogicalPlan::Values { .. } => "Values",
682 LogicalPlan::Filter { .. } => "Filter",
683 LogicalPlan::Project { .. } => "Project",
684 LogicalPlan::Join { .. } => "Join",
685 LogicalPlan::LateralJoin { .. } => "LateralJoin",
686 LogicalPlan::TableFunction { .. } => "TableFunction",
687 LogicalPlan::Aggregate { .. } => "Aggregate",
688 LogicalPlan::Window { .. } => "Window",
689 LogicalPlan::SetOperation { .. } => "SetOperation",
690 LogicalPlan::RecursiveCte { .. } => "RecursiveCte",
691 LogicalPlan::RecursiveReference { .. } => "RecursiveReference",
692 LogicalPlan::Sort { .. } => "Sort",
693 LogicalPlan::DistinctOn { .. } => "DistinctOn",
694 LogicalPlan::Limit { .. } => "Limit",
695 LogicalPlan::Insert { .. } => "Insert",
696 LogicalPlan::InsertSelect { .. } => "InsertSelect",
697 LogicalPlan::Update { .. } => "Update",
698 LogicalPlan::Delete { .. } => "Delete",
699 LogicalPlan::CreateTable { .. } => "CreateTable",
700 LogicalPlan::DropTable { .. } => "DropTable",
701 LogicalPlan::CreateIndex { .. } => "CreateIndex",
702 LogicalPlan::DropIndex { .. } => "DropIndex",
703 }
704 }
705
706 pub fn is_query(&self) -> bool {
708 matches!(
709 self,
710 LogicalPlan::Scan { .. }
711 | LogicalPlan::Values { .. }
712 | LogicalPlan::Filter { .. }
713 | LogicalPlan::Project { .. }
714 | LogicalPlan::Join { .. }
715 | LogicalPlan::LateralJoin { .. }
716 | LogicalPlan::TableFunction { .. }
717 | LogicalPlan::Aggregate { .. }
718 | LogicalPlan::Window { .. }
719 | LogicalPlan::SetOperation { .. }
720 | LogicalPlan::RecursiveCte { .. }
721 | LogicalPlan::RecursiveReference { .. }
722 | LogicalPlan::Sort { .. }
723 | LogicalPlan::DistinctOn { .. }
724 | LogicalPlan::Limit { .. }
725 )
726 }
727
728 pub fn is_dml(&self) -> bool {
730 matches!(
731 self,
732 LogicalPlan::Insert { .. }
733 | LogicalPlan::InsertSelect { .. }
734 | LogicalPlan::Update { .. }
735 | LogicalPlan::Delete { .. }
736 )
737 }
738
739 pub fn is_ddl(&self) -> bool {
741 matches!(
742 self,
743 LogicalPlan::CreateTable { .. }
744 | LogicalPlan::DropTable { .. }
745 | LogicalPlan::CreateIndex { .. }
746 | LogicalPlan::DropIndex { .. }
747 | LogicalPlan::Pragma { .. }
748 )
749 }
750
751 pub fn input(&self) -> Option<&LogicalPlan> {
753 match self {
754 LogicalPlan::Filter { input, .. }
755 | LogicalPlan::Project { input, .. }
756 | LogicalPlan::Aggregate { input, .. }
757 | LogicalPlan::Window { input, .. }
758 | LogicalPlan::Sort { input, .. }
759 | LogicalPlan::DistinctOn { input, .. }
760 | LogicalPlan::Limit { input, .. } => Some(input),
761 LogicalPlan::Join { .. }
762 | LogicalPlan::LateralJoin { .. }
763 | LogicalPlan::TableFunction { .. }
764 | LogicalPlan::Values { .. } => None,
765 LogicalPlan::SetOperation { .. } => None,
766 LogicalPlan::RecursiveCte { .. } | LogicalPlan::RecursiveReference { .. } => None,
767 _ => None,
768 }
769 }
770
771 pub fn table_name(&self) -> Option<&str> {
773 match self {
774 LogicalPlan::Scan { table, .. }
775 | LogicalPlan::Insert { table, .. }
776 | LogicalPlan::InsertSelect { table, .. }
777 | LogicalPlan::Update { table, .. }
778 | LogicalPlan::Delete { table, .. } => Some(table),
779 LogicalPlan::CreateTable { table, .. } => Some(&table.name),
780 LogicalPlan::DropTable { name, .. } => Some(name),
781 LogicalPlan::CreateIndex { index, .. } => Some(&index.table),
782 LogicalPlan::DropIndex { .. } => None,
783 LogicalPlan::Pragma { .. } => None,
784 LogicalPlan::Values { .. } => None,
785 LogicalPlan::Filter { input, .. }
786 | LogicalPlan::Project { input, .. }
787 | LogicalPlan::Aggregate { input, .. }
788 | LogicalPlan::Window { input, .. }
789 | LogicalPlan::Sort { input, .. }
790 | LogicalPlan::DistinctOn { input, .. }
791 | LogicalPlan::Limit { input, .. } => input.table_name(),
792 LogicalPlan::Join { .. }
793 | LogicalPlan::LateralJoin { .. }
794 | LogicalPlan::TableFunction { .. } => None,
795 LogicalPlan::SetOperation { left, right, .. } => left
796 .table_name()
797 .filter(|name| right.table_name() == Some(*name)),
798 LogicalPlan::RecursiveCte { .. } | LogicalPlan::RecursiveReference { .. } => None,
799 }
800 }
801
802 pub fn contains_join(&self) -> bool {
809 match self {
810 LogicalPlan::Join { .. } | LogicalPlan::LateralJoin { .. } => true,
811 LogicalPlan::SetOperation { left, right, .. } => {
812 left.contains_join() || right.contains_join()
813 }
814 LogicalPlan::RecursiveCte {
815 anchor,
816 recursive_term,
817 ..
818 } => anchor.contains_join() || recursive_term.contains_join(),
819 LogicalPlan::Filter { input, .. }
820 | LogicalPlan::Project { input, .. }
821 | LogicalPlan::Aggregate { input, .. }
822 | LogicalPlan::Window { input, .. }
823 | LogicalPlan::Sort { input, .. }
824 | LogicalPlan::DistinctOn { input, .. }
825 | LogicalPlan::Limit { input, .. } => input.contains_join(),
826 _ => false,
827 }
828 }
829
830 pub fn contains_set_operation(&self) -> bool {
832 match self {
833 LogicalPlan::SetOperation { .. } | LogicalPlan::RecursiveCte { .. } => true,
834 LogicalPlan::Filter { input, .. }
835 | LogicalPlan::Project { input, .. }
836 | LogicalPlan::Aggregate { input, .. }
837 | LogicalPlan::Sort { input, .. }
838 | LogicalPlan::DistinctOn { input, .. }
839 | LogicalPlan::Limit { input, .. } => input.contains_set_operation(),
840 LogicalPlan::Join { left, right, .. }
841 | LogicalPlan::LateralJoin { left, right, .. } => {
842 left.contains_set_operation() || right.contains_set_operation()
843 }
844 _ => false,
845 }
846 }
847}
848
849#[cfg(test)]
850mod tests {
851 use super::*;
852 use crate::ast::expr::Literal;
853 use crate::ast::span::Span;
854 use crate::catalog::ColumnMetadata;
855 use crate::planner::typed_expr::ProjectedColumn;
856 use crate::planner::types::ResolvedType;
857
858 fn create_test_table_metadata() -> TableMetadata {
859 TableMetadata::new(
860 "users",
861 vec![
862 ColumnMetadata::new("id", ResolvedType::Integer)
863 .with_primary_key(true)
864 .with_not_null(true),
865 ColumnMetadata::new("name", ResolvedType::Text).with_not_null(true),
866 ColumnMetadata::new("email", ResolvedType::Text),
867 ],
868 )
869 .with_primary_key(vec!["id".to_string()])
870 }
871
872 #[test]
873 fn test_scan_plan() {
874 let plan = LogicalPlan::scan(
875 "users".to_string(),
876 Projection::All(vec![
877 "id".to_string(),
878 "name".to_string(),
879 "email".to_string(),
880 ]),
881 );
882
883 assert_eq!(plan.name(), "Scan");
884 assert!(plan.is_query());
885 assert!(!plan.is_dml());
886 assert!(!plan.is_ddl());
887 assert_eq!(plan.table_name(), Some("users"));
888 assert!(plan.input().is_none());
889 }
890
891 #[test]
892 fn test_filter_plan() {
893 let scan = LogicalPlan::scan("users".to_string(), Projection::All(vec![]));
894 let predicate = TypedExpr::column_ref(
895 "users".to_string(),
896 "id".to_string(),
897 0,
898 ResolvedType::Integer,
899 Span::default(),
900 );
901
902 let plan = LogicalPlan::filter(scan, predicate);
903
904 assert_eq!(plan.name(), "Filter");
905 assert!(plan.is_query());
906 assert!(plan.input().is_some());
907 assert_eq!(plan.table_name(), Some("users"));
908 }
909
910 #[test]
911 fn test_sort_plan() {
912 let scan = LogicalPlan::scan("users".to_string(), Projection::All(vec![]));
913 let sort_expr = SortExpr::asc(TypedExpr::column_ref(
914 "users".to_string(),
915 "name".to_string(),
916 1,
917 ResolvedType::Text,
918 Span::default(),
919 ));
920
921 let plan = LogicalPlan::sort(scan, vec![sort_expr]);
922
923 assert_eq!(plan.name(), "Sort");
924 assert!(plan.is_query());
925 }
926
927 #[test]
928 fn test_limit_plan() {
929 let scan = LogicalPlan::scan("users".to_string(), Projection::All(vec![]));
930 let plan = LogicalPlan::limit(scan, Some(10), Some(5));
931
932 assert_eq!(plan.name(), "Limit");
933 assert!(plan.is_query());
934
935 if let LogicalPlan::Limit { limit, offset, .. } = &plan {
936 assert_eq!(*limit, Some(10));
937 assert_eq!(*offset, Some(5));
938 } else {
939 panic!("Expected Limit plan");
940 }
941 }
942
943 #[test]
944 fn test_nested_query_plan() {
945 let scan = LogicalPlan::scan(
947 "users".to_string(),
948 Projection::All(vec!["id".to_string(), "name".to_string()]),
949 );
950
951 let predicate = TypedExpr::literal(
952 Literal::Boolean(true),
953 ResolvedType::Boolean,
954 Span::default(),
955 );
956 let filter = LogicalPlan::filter(scan, predicate);
957
958 let sort_expr = SortExpr::asc(TypedExpr::column_ref(
959 "users".to_string(),
960 "name".to_string(),
961 1,
962 ResolvedType::Text,
963 Span::default(),
964 ));
965 let sort = LogicalPlan::sort(filter, vec![sort_expr]);
966
967 let limit = LogicalPlan::limit(sort, Some(10), None);
968
969 assert_eq!(limit.name(), "Limit");
971 assert_eq!(limit.table_name(), Some("users"));
972
973 let sort_plan = limit.input().unwrap();
974 assert_eq!(sort_plan.name(), "Sort");
975
976 let filter_plan = sort_plan.input().unwrap();
977 assert_eq!(filter_plan.name(), "Filter");
978
979 let scan_plan = filter_plan.input().unwrap();
980 assert_eq!(scan_plan.name(), "Scan");
981 assert!(scan_plan.input().is_none());
982 }
983
984 #[test]
985 fn test_insert_plan() {
986 let value1 = TypedExpr::literal(
987 Literal::Number("1".to_string()),
988 ResolvedType::Integer,
989 Span::default(),
990 );
991 let value2 = TypedExpr::literal(
992 Literal::String("Alice".to_string()),
993 ResolvedType::Text,
994 Span::default(),
995 );
996
997 let plan = LogicalPlan::insert(
998 "users".to_string(),
999 vec!["id".to_string(), "name".to_string()],
1000 vec![vec![value1, value2]],
1001 );
1002
1003 assert_eq!(plan.name(), "Insert");
1004 assert!(plan.is_dml());
1005 assert!(!plan.is_query());
1006 assert!(!plan.is_ddl());
1007 assert_eq!(plan.table_name(), Some("users"));
1008
1009 if let LogicalPlan::Insert {
1010 table,
1011 columns,
1012 values,
1013 } = &plan
1014 {
1015 assert_eq!(table, "users");
1016 assert_eq!(columns, &vec!["id".to_string(), "name".to_string()]);
1017 assert_eq!(values.len(), 1);
1018 assert_eq!(values[0].len(), 2);
1019 } else {
1020 panic!("Expected Insert plan");
1021 }
1022 }
1023
1024 #[test]
1025 fn test_update_plan() {
1026 let assignment = TypedAssignment::new(
1027 "name".to_string(),
1028 1,
1029 TypedExpr::literal(
1030 Literal::String("Bob".to_string()),
1031 ResolvedType::Text,
1032 Span::default(),
1033 ),
1034 );
1035
1036 let filter = TypedExpr::literal(
1037 Literal::Boolean(true),
1038 ResolvedType::Boolean,
1039 Span::default(),
1040 );
1041
1042 let plan = LogicalPlan::update("users".to_string(), vec![assignment], Some(filter));
1043
1044 assert_eq!(plan.name(), "Update");
1045 assert!(plan.is_dml());
1046 assert_eq!(plan.table_name(), Some("users"));
1047 }
1048
1049 #[test]
1050 fn test_delete_plan() {
1051 let filter = TypedExpr::column_ref(
1052 "users".to_string(),
1053 "id".to_string(),
1054 0,
1055 ResolvedType::Integer,
1056 Span::default(),
1057 );
1058
1059 let plan = LogicalPlan::delete("users".to_string(), Some(filter));
1060
1061 assert_eq!(plan.name(), "Delete");
1062 assert!(plan.is_dml());
1063 assert_eq!(plan.table_name(), Some("users"));
1064 }
1065
1066 #[test]
1067 fn test_create_table_plan() {
1068 let table = create_test_table_metadata();
1069 let plan = LogicalPlan::create_table(table, false, vec![]);
1070
1071 assert_eq!(plan.name(), "CreateTable");
1072 assert!(plan.is_ddl());
1073 assert!(!plan.is_dml());
1074 assert!(!plan.is_query());
1075 assert_eq!(plan.table_name(), Some("users"));
1076 }
1077
1078 #[test]
1079 fn test_drop_table_plan() {
1080 let plan = LogicalPlan::drop_table("users".to_string(), true);
1081
1082 assert_eq!(plan.name(), "DropTable");
1083 assert!(plan.is_ddl());
1084 assert_eq!(plan.table_name(), Some("users"));
1085
1086 if let LogicalPlan::DropTable { name, if_exists } = &plan {
1087 assert_eq!(name, "users");
1088 assert!(*if_exists);
1089 } else {
1090 panic!("Expected DropTable plan");
1091 }
1092 }
1093
1094 #[test]
1095 fn test_create_index_plan() {
1096 let index = IndexMetadata::new(0, "idx_users_name", "users", vec!["name".into()]);
1097 let plan = LogicalPlan::create_index(index, false);
1098
1099 assert_eq!(plan.name(), "CreateIndex");
1100 assert!(plan.is_ddl());
1101 assert_eq!(plan.table_name(), Some("users"));
1102 }
1103
1104 #[test]
1105 fn test_drop_index_plan() {
1106 let plan = LogicalPlan::drop_index("idx_users_name".to_string(), false);
1107
1108 assert_eq!(plan.name(), "DropIndex");
1109 assert!(plan.is_ddl());
1110 assert!(plan.table_name().is_none());
1112 }
1113
1114 #[test]
1115 fn test_projection_columns() {
1116 let col1 = ProjectedColumn::new(TypedExpr::column_ref(
1117 "users".to_string(),
1118 "id".to_string(),
1119 0,
1120 ResolvedType::Integer,
1121 Span::default(),
1122 ));
1123 let col2 = ProjectedColumn::with_alias(
1124 TypedExpr::column_ref(
1125 "users".to_string(),
1126 "name".to_string(),
1127 1,
1128 ResolvedType::Text,
1129 Span::default(),
1130 ),
1131 "user_name".to_string(),
1132 );
1133
1134 let plan = LogicalPlan::scan("users".to_string(), Projection::Columns(vec![col1, col2]));
1135
1136 if let LogicalPlan::Scan { projection, .. } = &plan {
1137 assert_eq!(projection.len(), 2);
1138 } else {
1139 panic!("Expected Scan plan");
1140 }
1141 }
1142}