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 Scan {
76 table: String,
78 projection: Projection,
80 },
81
82 Filter {
86 input: Box<LogicalPlan>,
88 predicate: TypedExpr,
90 },
91
92 Project {
98 input: Box<LogicalPlan>,
100 projection: Projection,
102 },
103
104 Join {
106 left: Box<LogicalPlan>,
108 right: Box<LogicalPlan>,
110 join_type: JoinType,
112 condition: Option<TypedExpr>,
114 using: Option<Vec<String>>,
116 },
117
118 Aggregate {
122 input: Box<LogicalPlan>,
124 group_keys: Vec<TypedExpr>,
126 aggregates: Vec<AggregateExpr>,
128 having: Option<TypedExpr>,
130 projection: Projection,
132 },
133
134 Sort {
138 input: Box<LogicalPlan>,
140 order_by: Vec<SortExpr>,
142 },
143
144 Limit {
148 input: Box<LogicalPlan>,
150 limit: Option<u64>,
152 offset: Option<u64>,
154 },
155
156 Insert {
163 table: String,
165 columns: Vec<String>,
168 values: Vec<Vec<TypedExpr>>,
170 },
171
172 Update {
176 table: String,
178 assignments: Vec<TypedAssignment>,
180 filter: Option<TypedExpr>,
182 },
183
184 Delete {
188 table: String,
190 filter: Option<TypedExpr>,
192 },
193
194 CreateTable {
199 table: TableMetadata,
201 if_not_exists: bool,
203 with_options: Vec<(String, String)>,
205 },
206
207 DropTable {
211 name: String,
213 if_exists: bool,
215 },
216
217 CreateIndex {
221 index: IndexMetadata,
223 if_not_exists: bool,
225 },
226
227 DropIndex {
231 name: String,
233 if_exists: bool,
235 },
236}
237
238impl LogicalPlan {
239 pub fn operation_name(&self) -> &'static str {
240 match self {
241 LogicalPlan::Scan { .. }
242 | LogicalPlan::Filter { .. }
243 | LogicalPlan::Project { .. }
244 | LogicalPlan::Join { .. }
245 | LogicalPlan::Aggregate { .. }
246 | LogicalPlan::Sort { .. }
247 | LogicalPlan::Limit { .. } => "SELECT",
248 LogicalPlan::Insert { .. } => "INSERT",
249 LogicalPlan::Update { .. } => "UPDATE",
250 LogicalPlan::Delete { .. } => "DELETE",
251 LogicalPlan::CreateTable { .. } => "CREATE TABLE",
252 LogicalPlan::DropTable { .. } => "DROP TABLE",
253 LogicalPlan::CreateIndex { .. } => "CREATE INDEX",
254 LogicalPlan::DropIndex { .. } => "DROP INDEX",
255 }
256 }
257
258 pub fn scan(table: String, projection: Projection) -> Self {
260 LogicalPlan::Scan { table, projection }
261 }
262
263 pub fn filter(input: LogicalPlan, predicate: TypedExpr) -> Self {
265 LogicalPlan::Filter {
266 input: Box::new(input),
267 predicate,
268 }
269 }
270
271 pub fn project(input: LogicalPlan, projection: Projection) -> Self {
273 LogicalPlan::Project {
274 input: Box::new(input),
275 projection,
276 }
277 }
278
279 pub fn join(
281 left: LogicalPlan,
282 right: LogicalPlan,
283 join_type: JoinType,
284 condition: Option<TypedExpr>,
285 using: Option<Vec<String>>,
286 ) -> Self {
287 LogicalPlan::Join {
288 left: Box::new(left),
289 right: Box::new(right),
290 join_type,
291 condition,
292 using,
293 }
294 }
295
296 pub fn aggregate(
298 input: LogicalPlan,
299 group_keys: Vec<TypedExpr>,
300 aggregates: Vec<AggregateExpr>,
301 having: Option<TypedExpr>,
302 projection: Projection,
303 ) -> Self {
304 LogicalPlan::Aggregate {
305 input: Box::new(input),
306 group_keys,
307 aggregates,
308 having,
309 projection,
310 }
311 }
312
313 pub fn sort(input: LogicalPlan, order_by: Vec<SortExpr>) -> Self {
315 LogicalPlan::Sort {
316 input: Box::new(input),
317 order_by,
318 }
319 }
320
321 pub fn limit(input: LogicalPlan, limit: Option<u64>, offset: Option<u64>) -> Self {
323 LogicalPlan::Limit {
324 input: Box::new(input),
325 limit,
326 offset,
327 }
328 }
329
330 pub fn insert(table: String, columns: Vec<String>, values: Vec<Vec<TypedExpr>>) -> Self {
332 LogicalPlan::Insert {
333 table,
334 columns,
335 values,
336 }
337 }
338
339 pub fn update(
341 table: String,
342 assignments: Vec<TypedAssignment>,
343 filter: Option<TypedExpr>,
344 ) -> Self {
345 LogicalPlan::Update {
346 table,
347 assignments,
348 filter,
349 }
350 }
351
352 pub fn delete(table: String, filter: Option<TypedExpr>) -> Self {
354 LogicalPlan::Delete { table, filter }
355 }
356
357 pub fn create_table(
359 table: TableMetadata,
360 if_not_exists: bool,
361 with_options: Vec<(String, String)>,
362 ) -> Self {
363 LogicalPlan::CreateTable {
364 table,
365 if_not_exists,
366 with_options,
367 }
368 }
369
370 pub fn drop_table(name: String, if_exists: bool) -> Self {
372 LogicalPlan::DropTable { name, if_exists }
373 }
374
375 pub fn create_index(index: IndexMetadata, if_not_exists: bool) -> Self {
377 LogicalPlan::CreateIndex {
378 index,
379 if_not_exists,
380 }
381 }
382
383 pub fn drop_index(name: String, if_exists: bool) -> Self {
385 LogicalPlan::DropIndex { name, if_exists }
386 }
387
388 pub fn name(&self) -> &'static str {
390 match self {
391 LogicalPlan::Scan { .. } => "Scan",
392 LogicalPlan::Filter { .. } => "Filter",
393 LogicalPlan::Project { .. } => "Project",
394 LogicalPlan::Join { .. } => "Join",
395 LogicalPlan::Aggregate { .. } => "Aggregate",
396 LogicalPlan::Sort { .. } => "Sort",
397 LogicalPlan::Limit { .. } => "Limit",
398 LogicalPlan::Insert { .. } => "Insert",
399 LogicalPlan::Update { .. } => "Update",
400 LogicalPlan::Delete { .. } => "Delete",
401 LogicalPlan::CreateTable { .. } => "CreateTable",
402 LogicalPlan::DropTable { .. } => "DropTable",
403 LogicalPlan::CreateIndex { .. } => "CreateIndex",
404 LogicalPlan::DropIndex { .. } => "DropIndex",
405 }
406 }
407
408 pub fn is_query(&self) -> bool {
410 matches!(
411 self,
412 LogicalPlan::Scan { .. }
413 | LogicalPlan::Filter { .. }
414 | LogicalPlan::Project { .. }
415 | LogicalPlan::Join { .. }
416 | LogicalPlan::Aggregate { .. }
417 | LogicalPlan::Sort { .. }
418 | LogicalPlan::Limit { .. }
419 )
420 }
421
422 pub fn is_dml(&self) -> bool {
424 matches!(
425 self,
426 LogicalPlan::Insert { .. } | LogicalPlan::Update { .. } | LogicalPlan::Delete { .. }
427 )
428 }
429
430 pub fn is_ddl(&self) -> bool {
432 matches!(
433 self,
434 LogicalPlan::CreateTable { .. }
435 | LogicalPlan::DropTable { .. }
436 | LogicalPlan::CreateIndex { .. }
437 | LogicalPlan::DropIndex { .. }
438 )
439 }
440
441 pub fn input(&self) -> Option<&LogicalPlan> {
443 match self {
444 LogicalPlan::Filter { input, .. }
445 | LogicalPlan::Project { input, .. }
446 | LogicalPlan::Aggregate { input, .. }
447 | LogicalPlan::Sort { input, .. }
448 | LogicalPlan::Limit { input, .. } => Some(input),
449 LogicalPlan::Join { .. } => None,
450 _ => None,
451 }
452 }
453
454 pub fn table_name(&self) -> Option<&str> {
456 match self {
457 LogicalPlan::Scan { table, .. }
458 | LogicalPlan::Insert { table, .. }
459 | LogicalPlan::Update { table, .. }
460 | LogicalPlan::Delete { table, .. } => Some(table),
461 LogicalPlan::CreateTable { table, .. } => Some(&table.name),
462 LogicalPlan::DropTable { name, .. } => Some(name),
463 LogicalPlan::CreateIndex { index, .. } => Some(&index.table),
464 LogicalPlan::DropIndex { .. } => None,
465 LogicalPlan::Filter { input, .. }
466 | LogicalPlan::Project { input, .. }
467 | LogicalPlan::Aggregate { input, .. }
468 | LogicalPlan::Sort { input, .. }
469 | LogicalPlan::Limit { input, .. } => input.table_name(),
470 LogicalPlan::Join { .. } => None,
471 }
472 }
473}
474
475#[cfg(test)]
476mod tests {
477 use super::*;
478 use crate::ast::expr::Literal;
479 use crate::ast::span::Span;
480 use crate::catalog::ColumnMetadata;
481 use crate::planner::typed_expr::ProjectedColumn;
482 use crate::planner::types::ResolvedType;
483
484 fn create_test_table_metadata() -> TableMetadata {
485 TableMetadata::new(
486 "users",
487 vec![
488 ColumnMetadata::new("id", ResolvedType::Integer)
489 .with_primary_key(true)
490 .with_not_null(true),
491 ColumnMetadata::new("name", ResolvedType::Text).with_not_null(true),
492 ColumnMetadata::new("email", ResolvedType::Text),
493 ],
494 )
495 .with_primary_key(vec!["id".to_string()])
496 }
497
498 #[test]
499 fn test_scan_plan() {
500 let plan = LogicalPlan::scan(
501 "users".to_string(),
502 Projection::All(vec![
503 "id".to_string(),
504 "name".to_string(),
505 "email".to_string(),
506 ]),
507 );
508
509 assert_eq!(plan.name(), "Scan");
510 assert!(plan.is_query());
511 assert!(!plan.is_dml());
512 assert!(!plan.is_ddl());
513 assert_eq!(plan.table_name(), Some("users"));
514 assert!(plan.input().is_none());
515 }
516
517 #[test]
518 fn test_filter_plan() {
519 let scan = LogicalPlan::scan("users".to_string(), Projection::All(vec![]));
520 let predicate = TypedExpr::column_ref(
521 "users".to_string(),
522 "id".to_string(),
523 0,
524 ResolvedType::Integer,
525 Span::default(),
526 );
527
528 let plan = LogicalPlan::filter(scan, predicate);
529
530 assert_eq!(plan.name(), "Filter");
531 assert!(plan.is_query());
532 assert!(plan.input().is_some());
533 assert_eq!(plan.table_name(), Some("users"));
534 }
535
536 #[test]
537 fn test_sort_plan() {
538 let scan = LogicalPlan::scan("users".to_string(), Projection::All(vec![]));
539 let sort_expr = SortExpr::asc(TypedExpr::column_ref(
540 "users".to_string(),
541 "name".to_string(),
542 1,
543 ResolvedType::Text,
544 Span::default(),
545 ));
546
547 let plan = LogicalPlan::sort(scan, vec![sort_expr]);
548
549 assert_eq!(plan.name(), "Sort");
550 assert!(plan.is_query());
551 }
552
553 #[test]
554 fn test_limit_plan() {
555 let scan = LogicalPlan::scan("users".to_string(), Projection::All(vec![]));
556 let plan = LogicalPlan::limit(scan, Some(10), Some(5));
557
558 assert_eq!(plan.name(), "Limit");
559 assert!(plan.is_query());
560
561 if let LogicalPlan::Limit { limit, offset, .. } = &plan {
562 assert_eq!(*limit, Some(10));
563 assert_eq!(*offset, Some(5));
564 } else {
565 panic!("Expected Limit plan");
566 }
567 }
568
569 #[test]
570 fn test_nested_query_plan() {
571 let scan = LogicalPlan::scan(
573 "users".to_string(),
574 Projection::All(vec!["id".to_string(), "name".to_string()]),
575 );
576
577 let predicate = TypedExpr::literal(
578 Literal::Boolean(true),
579 ResolvedType::Boolean,
580 Span::default(),
581 );
582 let filter = LogicalPlan::filter(scan, predicate);
583
584 let sort_expr = SortExpr::asc(TypedExpr::column_ref(
585 "users".to_string(),
586 "name".to_string(),
587 1,
588 ResolvedType::Text,
589 Span::default(),
590 ));
591 let sort = LogicalPlan::sort(filter, vec![sort_expr]);
592
593 let limit = LogicalPlan::limit(sort, Some(10), None);
594
595 assert_eq!(limit.name(), "Limit");
597 assert_eq!(limit.table_name(), Some("users"));
598
599 let sort_plan = limit.input().unwrap();
600 assert_eq!(sort_plan.name(), "Sort");
601
602 let filter_plan = sort_plan.input().unwrap();
603 assert_eq!(filter_plan.name(), "Filter");
604
605 let scan_plan = filter_plan.input().unwrap();
606 assert_eq!(scan_plan.name(), "Scan");
607 assert!(scan_plan.input().is_none());
608 }
609
610 #[test]
611 fn test_insert_plan() {
612 let value1 = TypedExpr::literal(
613 Literal::Number("1".to_string()),
614 ResolvedType::Integer,
615 Span::default(),
616 );
617 let value2 = TypedExpr::literal(
618 Literal::String("Alice".to_string()),
619 ResolvedType::Text,
620 Span::default(),
621 );
622
623 let plan = LogicalPlan::insert(
624 "users".to_string(),
625 vec!["id".to_string(), "name".to_string()],
626 vec![vec![value1, value2]],
627 );
628
629 assert_eq!(plan.name(), "Insert");
630 assert!(plan.is_dml());
631 assert!(!plan.is_query());
632 assert!(!plan.is_ddl());
633 assert_eq!(plan.table_name(), Some("users"));
634
635 if let LogicalPlan::Insert {
636 table,
637 columns,
638 values,
639 } = &plan
640 {
641 assert_eq!(table, "users");
642 assert_eq!(columns, &vec!["id".to_string(), "name".to_string()]);
643 assert_eq!(values.len(), 1);
644 assert_eq!(values[0].len(), 2);
645 } else {
646 panic!("Expected Insert plan");
647 }
648 }
649
650 #[test]
651 fn test_update_plan() {
652 let assignment = TypedAssignment::new(
653 "name".to_string(),
654 1,
655 TypedExpr::literal(
656 Literal::String("Bob".to_string()),
657 ResolvedType::Text,
658 Span::default(),
659 ),
660 );
661
662 let filter = TypedExpr::literal(
663 Literal::Boolean(true),
664 ResolvedType::Boolean,
665 Span::default(),
666 );
667
668 let plan = LogicalPlan::update("users".to_string(), vec![assignment], Some(filter));
669
670 assert_eq!(plan.name(), "Update");
671 assert!(plan.is_dml());
672 assert_eq!(plan.table_name(), Some("users"));
673 }
674
675 #[test]
676 fn test_delete_plan() {
677 let filter = TypedExpr::column_ref(
678 "users".to_string(),
679 "id".to_string(),
680 0,
681 ResolvedType::Integer,
682 Span::default(),
683 );
684
685 let plan = LogicalPlan::delete("users".to_string(), Some(filter));
686
687 assert_eq!(plan.name(), "Delete");
688 assert!(plan.is_dml());
689 assert_eq!(plan.table_name(), Some("users"));
690 }
691
692 #[test]
693 fn test_create_table_plan() {
694 let table = create_test_table_metadata();
695 let plan = LogicalPlan::create_table(table, false, vec![]);
696
697 assert_eq!(plan.name(), "CreateTable");
698 assert!(plan.is_ddl());
699 assert!(!plan.is_dml());
700 assert!(!plan.is_query());
701 assert_eq!(plan.table_name(), Some("users"));
702 }
703
704 #[test]
705 fn test_drop_table_plan() {
706 let plan = LogicalPlan::drop_table("users".to_string(), true);
707
708 assert_eq!(plan.name(), "DropTable");
709 assert!(plan.is_ddl());
710 assert_eq!(plan.table_name(), Some("users"));
711
712 if let LogicalPlan::DropTable { name, if_exists } = &plan {
713 assert_eq!(name, "users");
714 assert!(*if_exists);
715 } else {
716 panic!("Expected DropTable plan");
717 }
718 }
719
720 #[test]
721 fn test_create_index_plan() {
722 let index = IndexMetadata::new(0, "idx_users_name", "users", vec!["name".into()]);
723 let plan = LogicalPlan::create_index(index, false);
724
725 assert_eq!(plan.name(), "CreateIndex");
726 assert!(plan.is_ddl());
727 assert_eq!(plan.table_name(), Some("users"));
728 }
729
730 #[test]
731 fn test_drop_index_plan() {
732 let plan = LogicalPlan::drop_index("idx_users_name".to_string(), false);
733
734 assert_eq!(plan.name(), "DropIndex");
735 assert!(plan.is_ddl());
736 assert!(plan.table_name().is_none());
738 }
739
740 #[test]
741 fn test_projection_columns() {
742 let col1 = ProjectedColumn::new(TypedExpr::column_ref(
743 "users".to_string(),
744 "id".to_string(),
745 0,
746 ResolvedType::Integer,
747 Span::default(),
748 ));
749 let col2 = ProjectedColumn::with_alias(
750 TypedExpr::column_ref(
751 "users".to_string(),
752 "name".to_string(),
753 1,
754 ResolvedType::Text,
755 Span::default(),
756 ),
757 "user_name".to_string(),
758 );
759
760 let plan = LogicalPlan::scan("users".to_string(), Projection::Columns(vec![col1, col2]));
761
762 if let LogicalPlan::Scan { projection, .. } = &plan {
763 assert_eq!(projection.len(), 2);
764 } else {
765 panic!("Expected Scan plan");
766 }
767 }
768}