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)]
59pub enum LogicalPlan {
60 Scan {
66 table: String,
68 projection: Projection,
70 },
71
72 Filter {
76 input: Box<LogicalPlan>,
78 predicate: TypedExpr,
80 },
81
82 Aggregate {
86 input: Box<LogicalPlan>,
88 group_keys: Vec<TypedExpr>,
90 aggregates: Vec<AggregateExpr>,
92 having: Option<TypedExpr>,
94 },
95
96 Sort {
100 input: Box<LogicalPlan>,
102 order_by: Vec<SortExpr>,
104 },
105
106 Limit {
110 input: Box<LogicalPlan>,
112 limit: Option<u64>,
114 offset: Option<u64>,
116 },
117
118 Insert {
125 table: String,
127 columns: Vec<String>,
130 values: Vec<Vec<TypedExpr>>,
132 },
133
134 Update {
138 table: String,
140 assignments: Vec<TypedAssignment>,
142 filter: Option<TypedExpr>,
144 },
145
146 Delete {
150 table: String,
152 filter: Option<TypedExpr>,
154 },
155
156 CreateTable {
161 table: TableMetadata,
163 if_not_exists: bool,
165 with_options: Vec<(String, String)>,
167 },
168
169 DropTable {
173 name: String,
175 if_exists: bool,
177 },
178
179 CreateIndex {
183 index: IndexMetadata,
185 if_not_exists: bool,
187 },
188
189 DropIndex {
193 name: String,
195 if_exists: bool,
197 },
198}
199
200impl LogicalPlan {
201 pub fn operation_name(&self) -> &'static str {
202 match self {
203 LogicalPlan::Scan { .. }
204 | LogicalPlan::Filter { .. }
205 | LogicalPlan::Aggregate { .. }
206 | LogicalPlan::Sort { .. }
207 | LogicalPlan::Limit { .. } => "SELECT",
208 LogicalPlan::Insert { .. } => "INSERT",
209 LogicalPlan::Update { .. } => "UPDATE",
210 LogicalPlan::Delete { .. } => "DELETE",
211 LogicalPlan::CreateTable { .. } => "CREATE TABLE",
212 LogicalPlan::DropTable { .. } => "DROP TABLE",
213 LogicalPlan::CreateIndex { .. } => "CREATE INDEX",
214 LogicalPlan::DropIndex { .. } => "DROP INDEX",
215 }
216 }
217
218 pub fn scan(table: String, projection: Projection) -> Self {
220 LogicalPlan::Scan { table, projection }
221 }
222
223 pub fn filter(input: LogicalPlan, predicate: TypedExpr) -> Self {
225 LogicalPlan::Filter {
226 input: Box::new(input),
227 predicate,
228 }
229 }
230
231 pub fn aggregate(
233 input: LogicalPlan,
234 group_keys: Vec<TypedExpr>,
235 aggregates: Vec<AggregateExpr>,
236 having: Option<TypedExpr>,
237 ) -> Self {
238 LogicalPlan::Aggregate {
239 input: Box::new(input),
240 group_keys,
241 aggregates,
242 having,
243 }
244 }
245
246 pub fn sort(input: LogicalPlan, order_by: Vec<SortExpr>) -> Self {
248 LogicalPlan::Sort {
249 input: Box::new(input),
250 order_by,
251 }
252 }
253
254 pub fn limit(input: LogicalPlan, limit: Option<u64>, offset: Option<u64>) -> Self {
256 LogicalPlan::Limit {
257 input: Box::new(input),
258 limit,
259 offset,
260 }
261 }
262
263 pub fn insert(table: String, columns: Vec<String>, values: Vec<Vec<TypedExpr>>) -> Self {
265 LogicalPlan::Insert {
266 table,
267 columns,
268 values,
269 }
270 }
271
272 pub fn update(
274 table: String,
275 assignments: Vec<TypedAssignment>,
276 filter: Option<TypedExpr>,
277 ) -> Self {
278 LogicalPlan::Update {
279 table,
280 assignments,
281 filter,
282 }
283 }
284
285 pub fn delete(table: String, filter: Option<TypedExpr>) -> Self {
287 LogicalPlan::Delete { table, filter }
288 }
289
290 pub fn create_table(
292 table: TableMetadata,
293 if_not_exists: bool,
294 with_options: Vec<(String, String)>,
295 ) -> Self {
296 LogicalPlan::CreateTable {
297 table,
298 if_not_exists,
299 with_options,
300 }
301 }
302
303 pub fn drop_table(name: String, if_exists: bool) -> Self {
305 LogicalPlan::DropTable { name, if_exists }
306 }
307
308 pub fn create_index(index: IndexMetadata, if_not_exists: bool) -> Self {
310 LogicalPlan::CreateIndex {
311 index,
312 if_not_exists,
313 }
314 }
315
316 pub fn drop_index(name: String, if_exists: bool) -> Self {
318 LogicalPlan::DropIndex { name, if_exists }
319 }
320
321 pub fn name(&self) -> &'static str {
323 match self {
324 LogicalPlan::Scan { .. } => "Scan",
325 LogicalPlan::Filter { .. } => "Filter",
326 LogicalPlan::Aggregate { .. } => "Aggregate",
327 LogicalPlan::Sort { .. } => "Sort",
328 LogicalPlan::Limit { .. } => "Limit",
329 LogicalPlan::Insert { .. } => "Insert",
330 LogicalPlan::Update { .. } => "Update",
331 LogicalPlan::Delete { .. } => "Delete",
332 LogicalPlan::CreateTable { .. } => "CreateTable",
333 LogicalPlan::DropTable { .. } => "DropTable",
334 LogicalPlan::CreateIndex { .. } => "CreateIndex",
335 LogicalPlan::DropIndex { .. } => "DropIndex",
336 }
337 }
338
339 pub fn is_query(&self) -> bool {
341 matches!(
342 self,
343 LogicalPlan::Scan { .. }
344 | LogicalPlan::Filter { .. }
345 | LogicalPlan::Aggregate { .. }
346 | LogicalPlan::Sort { .. }
347 | LogicalPlan::Limit { .. }
348 )
349 }
350
351 pub fn is_dml(&self) -> bool {
353 matches!(
354 self,
355 LogicalPlan::Insert { .. } | LogicalPlan::Update { .. } | LogicalPlan::Delete { .. }
356 )
357 }
358
359 pub fn is_ddl(&self) -> bool {
361 matches!(
362 self,
363 LogicalPlan::CreateTable { .. }
364 | LogicalPlan::DropTable { .. }
365 | LogicalPlan::CreateIndex { .. }
366 | LogicalPlan::DropIndex { .. }
367 )
368 }
369
370 pub fn input(&self) -> Option<&LogicalPlan> {
372 match self {
373 LogicalPlan::Filter { input, .. }
374 | LogicalPlan::Aggregate { input, .. }
375 | LogicalPlan::Sort { input, .. }
376 | LogicalPlan::Limit { input, .. } => Some(input),
377 _ => None,
378 }
379 }
380
381 pub fn table_name(&self) -> Option<&str> {
383 match self {
384 LogicalPlan::Scan { table, .. }
385 | LogicalPlan::Insert { table, .. }
386 | LogicalPlan::Update { table, .. }
387 | LogicalPlan::Delete { table, .. } => Some(table),
388 LogicalPlan::CreateTable { table, .. } => Some(&table.name),
389 LogicalPlan::DropTable { name, .. } => Some(name),
390 LogicalPlan::CreateIndex { index, .. } => Some(&index.table),
391 LogicalPlan::DropIndex { .. } => None,
392 LogicalPlan::Filter { input, .. }
393 | LogicalPlan::Aggregate { input, .. }
394 | LogicalPlan::Sort { input, .. }
395 | LogicalPlan::Limit { input, .. } => input.table_name(),
396 }
397 }
398}
399
400#[cfg(test)]
401mod tests {
402 use super::*;
403 use crate::ast::expr::Literal;
404 use crate::ast::span::Span;
405 use crate::catalog::ColumnMetadata;
406 use crate::planner::typed_expr::ProjectedColumn;
407 use crate::planner::types::ResolvedType;
408
409 fn create_test_table_metadata() -> TableMetadata {
410 TableMetadata::new(
411 "users",
412 vec![
413 ColumnMetadata::new("id", ResolvedType::Integer)
414 .with_primary_key(true)
415 .with_not_null(true),
416 ColumnMetadata::new("name", ResolvedType::Text).with_not_null(true),
417 ColumnMetadata::new("email", ResolvedType::Text),
418 ],
419 )
420 .with_primary_key(vec!["id".to_string()])
421 }
422
423 #[test]
424 fn test_scan_plan() {
425 let plan = LogicalPlan::scan(
426 "users".to_string(),
427 Projection::All(vec![
428 "id".to_string(),
429 "name".to_string(),
430 "email".to_string(),
431 ]),
432 );
433
434 assert_eq!(plan.name(), "Scan");
435 assert!(plan.is_query());
436 assert!(!plan.is_dml());
437 assert!(!plan.is_ddl());
438 assert_eq!(plan.table_name(), Some("users"));
439 assert!(plan.input().is_none());
440 }
441
442 #[test]
443 fn test_filter_plan() {
444 let scan = LogicalPlan::scan("users".to_string(), Projection::All(vec![]));
445 let predicate = TypedExpr::column_ref(
446 "users".to_string(),
447 "id".to_string(),
448 0,
449 ResolvedType::Integer,
450 Span::default(),
451 );
452
453 let plan = LogicalPlan::filter(scan, predicate);
454
455 assert_eq!(plan.name(), "Filter");
456 assert!(plan.is_query());
457 assert!(plan.input().is_some());
458 assert_eq!(plan.table_name(), Some("users"));
459 }
460
461 #[test]
462 fn test_sort_plan() {
463 let scan = LogicalPlan::scan("users".to_string(), Projection::All(vec![]));
464 let sort_expr = SortExpr::asc(TypedExpr::column_ref(
465 "users".to_string(),
466 "name".to_string(),
467 1,
468 ResolvedType::Text,
469 Span::default(),
470 ));
471
472 let plan = LogicalPlan::sort(scan, vec![sort_expr]);
473
474 assert_eq!(plan.name(), "Sort");
475 assert!(plan.is_query());
476 }
477
478 #[test]
479 fn test_limit_plan() {
480 let scan = LogicalPlan::scan("users".to_string(), Projection::All(vec![]));
481 let plan = LogicalPlan::limit(scan, Some(10), Some(5));
482
483 assert_eq!(plan.name(), "Limit");
484 assert!(plan.is_query());
485
486 if let LogicalPlan::Limit { limit, offset, .. } = &plan {
487 assert_eq!(*limit, Some(10));
488 assert_eq!(*offset, Some(5));
489 } else {
490 panic!("Expected Limit plan");
491 }
492 }
493
494 #[test]
495 fn test_nested_query_plan() {
496 let scan = LogicalPlan::scan(
498 "users".to_string(),
499 Projection::All(vec!["id".to_string(), "name".to_string()]),
500 );
501
502 let predicate = TypedExpr::literal(
503 Literal::Boolean(true),
504 ResolvedType::Boolean,
505 Span::default(),
506 );
507 let filter = LogicalPlan::filter(scan, predicate);
508
509 let sort_expr = SortExpr::asc(TypedExpr::column_ref(
510 "users".to_string(),
511 "name".to_string(),
512 1,
513 ResolvedType::Text,
514 Span::default(),
515 ));
516 let sort = LogicalPlan::sort(filter, vec![sort_expr]);
517
518 let limit = LogicalPlan::limit(sort, Some(10), None);
519
520 assert_eq!(limit.name(), "Limit");
522 assert_eq!(limit.table_name(), Some("users"));
523
524 let sort_plan = limit.input().unwrap();
525 assert_eq!(sort_plan.name(), "Sort");
526
527 let filter_plan = sort_plan.input().unwrap();
528 assert_eq!(filter_plan.name(), "Filter");
529
530 let scan_plan = filter_plan.input().unwrap();
531 assert_eq!(scan_plan.name(), "Scan");
532 assert!(scan_plan.input().is_none());
533 }
534
535 #[test]
536 fn test_insert_plan() {
537 let value1 = TypedExpr::literal(
538 Literal::Number("1".to_string()),
539 ResolvedType::Integer,
540 Span::default(),
541 );
542 let value2 = TypedExpr::literal(
543 Literal::String("Alice".to_string()),
544 ResolvedType::Text,
545 Span::default(),
546 );
547
548 let plan = LogicalPlan::insert(
549 "users".to_string(),
550 vec!["id".to_string(), "name".to_string()],
551 vec![vec![value1, value2]],
552 );
553
554 assert_eq!(plan.name(), "Insert");
555 assert!(plan.is_dml());
556 assert!(!plan.is_query());
557 assert!(!plan.is_ddl());
558 assert_eq!(plan.table_name(), Some("users"));
559
560 if let LogicalPlan::Insert {
561 table,
562 columns,
563 values,
564 } = &plan
565 {
566 assert_eq!(table, "users");
567 assert_eq!(columns, &vec!["id".to_string(), "name".to_string()]);
568 assert_eq!(values.len(), 1);
569 assert_eq!(values[0].len(), 2);
570 } else {
571 panic!("Expected Insert plan");
572 }
573 }
574
575 #[test]
576 fn test_update_plan() {
577 let assignment = TypedAssignment::new(
578 "name".to_string(),
579 1,
580 TypedExpr::literal(
581 Literal::String("Bob".to_string()),
582 ResolvedType::Text,
583 Span::default(),
584 ),
585 );
586
587 let filter = TypedExpr::literal(
588 Literal::Boolean(true),
589 ResolvedType::Boolean,
590 Span::default(),
591 );
592
593 let plan = LogicalPlan::update("users".to_string(), vec![assignment], Some(filter));
594
595 assert_eq!(plan.name(), "Update");
596 assert!(plan.is_dml());
597 assert_eq!(plan.table_name(), Some("users"));
598 }
599
600 #[test]
601 fn test_delete_plan() {
602 let filter = TypedExpr::column_ref(
603 "users".to_string(),
604 "id".to_string(),
605 0,
606 ResolvedType::Integer,
607 Span::default(),
608 );
609
610 let plan = LogicalPlan::delete("users".to_string(), Some(filter));
611
612 assert_eq!(plan.name(), "Delete");
613 assert!(plan.is_dml());
614 assert_eq!(plan.table_name(), Some("users"));
615 }
616
617 #[test]
618 fn test_create_table_plan() {
619 let table = create_test_table_metadata();
620 let plan = LogicalPlan::create_table(table, false, vec![]);
621
622 assert_eq!(plan.name(), "CreateTable");
623 assert!(plan.is_ddl());
624 assert!(!plan.is_dml());
625 assert!(!plan.is_query());
626 assert_eq!(plan.table_name(), Some("users"));
627 }
628
629 #[test]
630 fn test_drop_table_plan() {
631 let plan = LogicalPlan::drop_table("users".to_string(), true);
632
633 assert_eq!(plan.name(), "DropTable");
634 assert!(plan.is_ddl());
635 assert_eq!(plan.table_name(), Some("users"));
636
637 if let LogicalPlan::DropTable { name, if_exists } = &plan {
638 assert_eq!(name, "users");
639 assert!(*if_exists);
640 } else {
641 panic!("Expected DropTable plan");
642 }
643 }
644
645 #[test]
646 fn test_create_index_plan() {
647 let index = IndexMetadata::new(0, "idx_users_name", "users", vec!["name".into()]);
648 let plan = LogicalPlan::create_index(index, false);
649
650 assert_eq!(plan.name(), "CreateIndex");
651 assert!(plan.is_ddl());
652 assert_eq!(plan.table_name(), Some("users"));
653 }
654
655 #[test]
656 fn test_drop_index_plan() {
657 let plan = LogicalPlan::drop_index("idx_users_name".to_string(), false);
658
659 assert_eq!(plan.name(), "DropIndex");
660 assert!(plan.is_ddl());
661 assert!(plan.table_name().is_none());
663 }
664
665 #[test]
666 fn test_projection_columns() {
667 let col1 = ProjectedColumn::new(TypedExpr::column_ref(
668 "users".to_string(),
669 "id".to_string(),
670 0,
671 ResolvedType::Integer,
672 Span::default(),
673 ));
674 let col2 = ProjectedColumn::with_alias(
675 TypedExpr::column_ref(
676 "users".to_string(),
677 "name".to_string(),
678 1,
679 ResolvedType::Text,
680 Span::default(),
681 ),
682 "user_name".to_string(),
683 );
684
685 let plan = LogicalPlan::scan("users".to_string(), Projection::Columns(vec![col1, col2]));
686
687 if let LogicalPlan::Scan { projection, .. } = &plan {
688 assert_eq!(projection.len(), 2);
689 } else {
690 panic!("Expected Scan plan");
691 }
692 }
693}