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, Copy, PartialEq, Eq)]
52pub enum JoinType {
53 Inner,
54 Left,
55 Right,
56 Full,
57 Cross,
58}
59
60#[derive(Debug, Clone)]
69pub enum LogicalPlan {
70 Pragma {
72 name: String,
74 value: Option<crate::ast::PragmaValue>,
76 },
77
78 Scan {
84 table: String,
86 projection: Projection,
88 },
89
90 Filter {
94 input: Box<LogicalPlan>,
96 predicate: TypedExpr,
98 },
99
100 Project {
106 input: Box<LogicalPlan>,
108 projection: Projection,
110 },
111
112 Join {
114 left: Box<LogicalPlan>,
116 right: Box<LogicalPlan>,
118 join_type: JoinType,
120 condition: Option<TypedExpr>,
122 using: Option<Vec<String>>,
124 },
125
126 Aggregate {
130 input: Box<LogicalPlan>,
132 group_keys: Vec<TypedExpr>,
134 aggregates: Vec<AggregateExpr>,
136 having: Option<TypedExpr>,
138 projection: Projection,
140 },
141
142 Sort {
146 input: Box<LogicalPlan>,
148 order_by: Vec<SortExpr>,
150 },
151
152 Limit {
156 input: Box<LogicalPlan>,
158 limit: Option<u64>,
160 offset: Option<u64>,
162 },
163
164 Insert {
171 table: String,
173 columns: Vec<String>,
176 values: Vec<Vec<TypedExpr>>,
178 },
179
180 InsertSelect {
182 table: String,
184 columns: Vec<String>,
186 source: Box<LogicalPlan>,
188 },
189
190 Update {
194 table: String,
196 assignments: Vec<TypedAssignment>,
198 filter: Option<TypedExpr>,
200 },
201
202 Delete {
206 table: String,
208 filter: Option<TypedExpr>,
210 },
211
212 CreateTable {
217 table: TableMetadata,
219 if_not_exists: bool,
221 with_options: Vec<(String, String)>,
223 },
224
225 DropTable {
229 name: String,
231 if_exists: bool,
233 },
234
235 CreateIndex {
239 index: IndexMetadata,
241 if_not_exists: bool,
243 },
244
245 DropIndex {
249 name: String,
251 if_exists: bool,
253 },
254}
255
256impl LogicalPlan {
257 pub fn operation_name(&self) -> &'static str {
258 match self {
259 LogicalPlan::Pragma { .. } => "PRAGMA",
260 LogicalPlan::Scan { .. }
261 | LogicalPlan::Filter { .. }
262 | LogicalPlan::Project { .. }
263 | LogicalPlan::Join { .. }
264 | LogicalPlan::Aggregate { .. }
265 | LogicalPlan::Sort { .. }
266 | LogicalPlan::Limit { .. } => "SELECT",
267 LogicalPlan::Insert { .. } => "INSERT",
268 LogicalPlan::InsertSelect { .. } => "INSERT",
269 LogicalPlan::Update { .. } => "UPDATE",
270 LogicalPlan::Delete { .. } => "DELETE",
271 LogicalPlan::CreateTable { .. } => "CREATE TABLE",
272 LogicalPlan::DropTable { .. } => "DROP TABLE",
273 LogicalPlan::CreateIndex { .. } => "CREATE INDEX",
274 LogicalPlan::DropIndex { .. } => "DROP INDEX",
275 }
276 }
277
278 pub fn scan(table: String, projection: Projection) -> Self {
280 LogicalPlan::Scan { table, projection }
281 }
282
283 pub fn filter(input: LogicalPlan, predicate: TypedExpr) -> Self {
285 LogicalPlan::Filter {
286 input: Box::new(input),
287 predicate,
288 }
289 }
290
291 pub fn project(input: LogicalPlan, projection: Projection) -> Self {
293 LogicalPlan::Project {
294 input: Box::new(input),
295 projection,
296 }
297 }
298
299 pub fn join(
301 left: LogicalPlan,
302 right: LogicalPlan,
303 join_type: JoinType,
304 condition: Option<TypedExpr>,
305 using: Option<Vec<String>>,
306 ) -> Self {
307 LogicalPlan::Join {
308 left: Box::new(left),
309 right: Box::new(right),
310 join_type,
311 condition,
312 using,
313 }
314 }
315
316 pub fn aggregate(
318 input: LogicalPlan,
319 group_keys: Vec<TypedExpr>,
320 aggregates: Vec<AggregateExpr>,
321 having: Option<TypedExpr>,
322 projection: Projection,
323 ) -> Self {
324 LogicalPlan::Aggregate {
325 input: Box::new(input),
326 group_keys,
327 aggregates,
328 having,
329 projection,
330 }
331 }
332
333 pub fn sort(input: LogicalPlan, order_by: Vec<SortExpr>) -> Self {
335 LogicalPlan::Sort {
336 input: Box::new(input),
337 order_by,
338 }
339 }
340
341 pub fn limit(input: LogicalPlan, limit: Option<u64>, offset: Option<u64>) -> Self {
343 LogicalPlan::Limit {
344 input: Box::new(input),
345 limit,
346 offset,
347 }
348 }
349
350 pub fn insert(table: String, columns: Vec<String>, values: Vec<Vec<TypedExpr>>) -> Self {
352 LogicalPlan::Insert {
353 table,
354 columns,
355 values,
356 }
357 }
358
359 pub fn update(
361 table: String,
362 assignments: Vec<TypedAssignment>,
363 filter: Option<TypedExpr>,
364 ) -> Self {
365 LogicalPlan::Update {
366 table,
367 assignments,
368 filter,
369 }
370 }
371
372 pub fn delete(table: String, filter: Option<TypedExpr>) -> Self {
374 LogicalPlan::Delete { table, filter }
375 }
376
377 pub fn create_table(
379 table: TableMetadata,
380 if_not_exists: bool,
381 with_options: Vec<(String, String)>,
382 ) -> Self {
383 LogicalPlan::CreateTable {
384 table,
385 if_not_exists,
386 with_options,
387 }
388 }
389
390 pub fn drop_table(name: String, if_exists: bool) -> Self {
392 LogicalPlan::DropTable { name, if_exists }
393 }
394
395 pub fn create_index(index: IndexMetadata, if_not_exists: bool) -> Self {
397 LogicalPlan::CreateIndex {
398 index,
399 if_not_exists,
400 }
401 }
402
403 pub fn drop_index(name: String, if_exists: bool) -> Self {
405 LogicalPlan::DropIndex { name, if_exists }
406 }
407
408 pub fn name(&self) -> &'static str {
410 match self {
411 LogicalPlan::Pragma { .. } => "Pragma",
412 LogicalPlan::Scan { .. } => "Scan",
413 LogicalPlan::Filter { .. } => "Filter",
414 LogicalPlan::Project { .. } => "Project",
415 LogicalPlan::Join { .. } => "Join",
416 LogicalPlan::Aggregate { .. } => "Aggregate",
417 LogicalPlan::Sort { .. } => "Sort",
418 LogicalPlan::Limit { .. } => "Limit",
419 LogicalPlan::Insert { .. } => "Insert",
420 LogicalPlan::InsertSelect { .. } => "InsertSelect",
421 LogicalPlan::Update { .. } => "Update",
422 LogicalPlan::Delete { .. } => "Delete",
423 LogicalPlan::CreateTable { .. } => "CreateTable",
424 LogicalPlan::DropTable { .. } => "DropTable",
425 LogicalPlan::CreateIndex { .. } => "CreateIndex",
426 LogicalPlan::DropIndex { .. } => "DropIndex",
427 }
428 }
429
430 pub fn is_query(&self) -> bool {
432 matches!(
433 self,
434 LogicalPlan::Scan { .. }
435 | LogicalPlan::Filter { .. }
436 | LogicalPlan::Project { .. }
437 | LogicalPlan::Join { .. }
438 | LogicalPlan::Aggregate { .. }
439 | LogicalPlan::Sort { .. }
440 | LogicalPlan::Limit { .. }
441 )
442 }
443
444 pub fn is_dml(&self) -> bool {
446 matches!(
447 self,
448 LogicalPlan::Insert { .. }
449 | LogicalPlan::InsertSelect { .. }
450 | LogicalPlan::Update { .. }
451 | LogicalPlan::Delete { .. }
452 )
453 }
454
455 pub fn is_ddl(&self) -> bool {
457 matches!(
458 self,
459 LogicalPlan::CreateTable { .. }
460 | LogicalPlan::DropTable { .. }
461 | LogicalPlan::CreateIndex { .. }
462 | LogicalPlan::DropIndex { .. }
463 | LogicalPlan::Pragma { .. }
464 )
465 }
466
467 pub fn input(&self) -> Option<&LogicalPlan> {
469 match self {
470 LogicalPlan::Filter { input, .. }
471 | LogicalPlan::Project { input, .. }
472 | LogicalPlan::Aggregate { input, .. }
473 | LogicalPlan::Sort { input, .. }
474 | LogicalPlan::Limit { input, .. } => Some(input),
475 LogicalPlan::Join { .. } => None,
476 _ => None,
477 }
478 }
479
480 pub fn table_name(&self) -> Option<&str> {
482 match self {
483 LogicalPlan::Scan { table, .. }
484 | LogicalPlan::Insert { table, .. }
485 | LogicalPlan::InsertSelect { table, .. }
486 | LogicalPlan::Update { table, .. }
487 | LogicalPlan::Delete { table, .. } => Some(table),
488 LogicalPlan::CreateTable { table, .. } => Some(&table.name),
489 LogicalPlan::DropTable { name, .. } => Some(name),
490 LogicalPlan::CreateIndex { index, .. } => Some(&index.table),
491 LogicalPlan::DropIndex { .. } => None,
492 LogicalPlan::Pragma { .. } => None,
493 LogicalPlan::Filter { input, .. }
494 | LogicalPlan::Project { input, .. }
495 | LogicalPlan::Aggregate { input, .. }
496 | LogicalPlan::Sort { input, .. }
497 | LogicalPlan::Limit { input, .. } => input.table_name(),
498 LogicalPlan::Join { .. } => None,
499 }
500 }
501
502 pub fn contains_join(&self) -> bool {
509 match self {
510 LogicalPlan::Join { .. } => true,
511 LogicalPlan::Filter { input, .. }
512 | LogicalPlan::Project { input, .. }
513 | LogicalPlan::Aggregate { input, .. }
514 | LogicalPlan::Sort { input, .. }
515 | LogicalPlan::Limit { input, .. } => input.contains_join(),
516 _ => false,
517 }
518 }
519}
520
521#[cfg(test)]
522mod tests {
523 use super::*;
524 use crate::ast::expr::Literal;
525 use crate::ast::span::Span;
526 use crate::catalog::ColumnMetadata;
527 use crate::planner::typed_expr::ProjectedColumn;
528 use crate::planner::types::ResolvedType;
529
530 fn create_test_table_metadata() -> TableMetadata {
531 TableMetadata::new(
532 "users",
533 vec![
534 ColumnMetadata::new("id", ResolvedType::Integer)
535 .with_primary_key(true)
536 .with_not_null(true),
537 ColumnMetadata::new("name", ResolvedType::Text).with_not_null(true),
538 ColumnMetadata::new("email", ResolvedType::Text),
539 ],
540 )
541 .with_primary_key(vec!["id".to_string()])
542 }
543
544 #[test]
545 fn test_scan_plan() {
546 let plan = LogicalPlan::scan(
547 "users".to_string(),
548 Projection::All(vec![
549 "id".to_string(),
550 "name".to_string(),
551 "email".to_string(),
552 ]),
553 );
554
555 assert_eq!(plan.name(), "Scan");
556 assert!(plan.is_query());
557 assert!(!plan.is_dml());
558 assert!(!plan.is_ddl());
559 assert_eq!(plan.table_name(), Some("users"));
560 assert!(plan.input().is_none());
561 }
562
563 #[test]
564 fn test_filter_plan() {
565 let scan = LogicalPlan::scan("users".to_string(), Projection::All(vec![]));
566 let predicate = TypedExpr::column_ref(
567 "users".to_string(),
568 "id".to_string(),
569 0,
570 ResolvedType::Integer,
571 Span::default(),
572 );
573
574 let plan = LogicalPlan::filter(scan, predicate);
575
576 assert_eq!(plan.name(), "Filter");
577 assert!(plan.is_query());
578 assert!(plan.input().is_some());
579 assert_eq!(plan.table_name(), Some("users"));
580 }
581
582 #[test]
583 fn test_sort_plan() {
584 let scan = LogicalPlan::scan("users".to_string(), Projection::All(vec![]));
585 let sort_expr = SortExpr::asc(TypedExpr::column_ref(
586 "users".to_string(),
587 "name".to_string(),
588 1,
589 ResolvedType::Text,
590 Span::default(),
591 ));
592
593 let plan = LogicalPlan::sort(scan, vec![sort_expr]);
594
595 assert_eq!(plan.name(), "Sort");
596 assert!(plan.is_query());
597 }
598
599 #[test]
600 fn test_limit_plan() {
601 let scan = LogicalPlan::scan("users".to_string(), Projection::All(vec![]));
602 let plan = LogicalPlan::limit(scan, Some(10), Some(5));
603
604 assert_eq!(plan.name(), "Limit");
605 assert!(plan.is_query());
606
607 if let LogicalPlan::Limit { limit, offset, .. } = &plan {
608 assert_eq!(*limit, Some(10));
609 assert_eq!(*offset, Some(5));
610 } else {
611 panic!("Expected Limit plan");
612 }
613 }
614
615 #[test]
616 fn test_nested_query_plan() {
617 let scan = LogicalPlan::scan(
619 "users".to_string(),
620 Projection::All(vec!["id".to_string(), "name".to_string()]),
621 );
622
623 let predicate = TypedExpr::literal(
624 Literal::Boolean(true),
625 ResolvedType::Boolean,
626 Span::default(),
627 );
628 let filter = LogicalPlan::filter(scan, predicate);
629
630 let sort_expr = SortExpr::asc(TypedExpr::column_ref(
631 "users".to_string(),
632 "name".to_string(),
633 1,
634 ResolvedType::Text,
635 Span::default(),
636 ));
637 let sort = LogicalPlan::sort(filter, vec![sort_expr]);
638
639 let limit = LogicalPlan::limit(sort, Some(10), None);
640
641 assert_eq!(limit.name(), "Limit");
643 assert_eq!(limit.table_name(), Some("users"));
644
645 let sort_plan = limit.input().unwrap();
646 assert_eq!(sort_plan.name(), "Sort");
647
648 let filter_plan = sort_plan.input().unwrap();
649 assert_eq!(filter_plan.name(), "Filter");
650
651 let scan_plan = filter_plan.input().unwrap();
652 assert_eq!(scan_plan.name(), "Scan");
653 assert!(scan_plan.input().is_none());
654 }
655
656 #[test]
657 fn test_insert_plan() {
658 let value1 = TypedExpr::literal(
659 Literal::Number("1".to_string()),
660 ResolvedType::Integer,
661 Span::default(),
662 );
663 let value2 = TypedExpr::literal(
664 Literal::String("Alice".to_string()),
665 ResolvedType::Text,
666 Span::default(),
667 );
668
669 let plan = LogicalPlan::insert(
670 "users".to_string(),
671 vec!["id".to_string(), "name".to_string()],
672 vec![vec![value1, value2]],
673 );
674
675 assert_eq!(plan.name(), "Insert");
676 assert!(plan.is_dml());
677 assert!(!plan.is_query());
678 assert!(!plan.is_ddl());
679 assert_eq!(plan.table_name(), Some("users"));
680
681 if let LogicalPlan::Insert {
682 table,
683 columns,
684 values,
685 } = &plan
686 {
687 assert_eq!(table, "users");
688 assert_eq!(columns, &vec!["id".to_string(), "name".to_string()]);
689 assert_eq!(values.len(), 1);
690 assert_eq!(values[0].len(), 2);
691 } else {
692 panic!("Expected Insert plan");
693 }
694 }
695
696 #[test]
697 fn test_update_plan() {
698 let assignment = TypedAssignment::new(
699 "name".to_string(),
700 1,
701 TypedExpr::literal(
702 Literal::String("Bob".to_string()),
703 ResolvedType::Text,
704 Span::default(),
705 ),
706 );
707
708 let filter = TypedExpr::literal(
709 Literal::Boolean(true),
710 ResolvedType::Boolean,
711 Span::default(),
712 );
713
714 let plan = LogicalPlan::update("users".to_string(), vec![assignment], Some(filter));
715
716 assert_eq!(plan.name(), "Update");
717 assert!(plan.is_dml());
718 assert_eq!(plan.table_name(), Some("users"));
719 }
720
721 #[test]
722 fn test_delete_plan() {
723 let filter = TypedExpr::column_ref(
724 "users".to_string(),
725 "id".to_string(),
726 0,
727 ResolvedType::Integer,
728 Span::default(),
729 );
730
731 let plan = LogicalPlan::delete("users".to_string(), Some(filter));
732
733 assert_eq!(plan.name(), "Delete");
734 assert!(plan.is_dml());
735 assert_eq!(plan.table_name(), Some("users"));
736 }
737
738 #[test]
739 fn test_create_table_plan() {
740 let table = create_test_table_metadata();
741 let plan = LogicalPlan::create_table(table, false, vec![]);
742
743 assert_eq!(plan.name(), "CreateTable");
744 assert!(plan.is_ddl());
745 assert!(!plan.is_dml());
746 assert!(!plan.is_query());
747 assert_eq!(plan.table_name(), Some("users"));
748 }
749
750 #[test]
751 fn test_drop_table_plan() {
752 let plan = LogicalPlan::drop_table("users".to_string(), true);
753
754 assert_eq!(plan.name(), "DropTable");
755 assert!(plan.is_ddl());
756 assert_eq!(plan.table_name(), Some("users"));
757
758 if let LogicalPlan::DropTable { name, if_exists } = &plan {
759 assert_eq!(name, "users");
760 assert!(*if_exists);
761 } else {
762 panic!("Expected DropTable plan");
763 }
764 }
765
766 #[test]
767 fn test_create_index_plan() {
768 let index = IndexMetadata::new(0, "idx_users_name", "users", vec!["name".into()]);
769 let plan = LogicalPlan::create_index(index, false);
770
771 assert_eq!(plan.name(), "CreateIndex");
772 assert!(plan.is_ddl());
773 assert_eq!(plan.table_name(), Some("users"));
774 }
775
776 #[test]
777 fn test_drop_index_plan() {
778 let plan = LogicalPlan::drop_index("idx_users_name".to_string(), false);
779
780 assert_eq!(plan.name(), "DropIndex");
781 assert!(plan.is_ddl());
782 assert!(plan.table_name().is_none());
784 }
785
786 #[test]
787 fn test_projection_columns() {
788 let col1 = ProjectedColumn::new(TypedExpr::column_ref(
789 "users".to_string(),
790 "id".to_string(),
791 0,
792 ResolvedType::Integer,
793 Span::default(),
794 ));
795 let col2 = ProjectedColumn::with_alias(
796 TypedExpr::column_ref(
797 "users".to_string(),
798 "name".to_string(),
799 1,
800 ResolvedType::Text,
801 Span::default(),
802 ),
803 "user_name".to_string(),
804 );
805
806 let plan = LogicalPlan::scan("users".to_string(), Projection::Columns(vec![col1, col2]));
807
808 if let LogicalPlan::Scan { projection, .. } = &plan {
809 assert_eq!(projection.len(), 2);
810 } else {
811 panic!("Expected Scan plan");
812 }
813 }
814}