quill_sql/optimizer/rule/
merge_limit.rs1use crate::error::QuillSQLResult;
2use crate::optimizer::logical_optimizer::ApplyOrder;
3use crate::optimizer::LogicalOptimizerRule;
4use crate::plan::logical_plan::{Limit, LogicalPlan};
5use std::cmp::min;
6use std::sync::Arc;
7
8pub struct MergeLimit;
9
10impl LogicalOptimizerRule for MergeLimit {
11 fn try_optimize(&self, plan: &LogicalPlan) -> QuillSQLResult<Option<LogicalPlan>> {
12 let LogicalPlan::Limit(parent) = plan else {
13 return Ok(None);
14 };
15
16 if let LogicalPlan::Limit(child) = &*parent.input {
17 let new_limit = match (parent.limit, child.limit) {
18 (Some(parent_limit), Some(child_limit)) => {
19 Some(min(parent_limit, child_limit.saturating_sub(parent.offset)))
20 }
21 (Some(parent_limit), None) => Some(parent_limit),
22 (None, Some(child_limit)) => Some(child_limit.saturating_sub(parent.offset)),
23 (None, None) => None,
24 };
25 let plan = LogicalPlan::Limit(Limit {
26 limit: new_limit,
27 offset: child.offset + parent.offset,
28 input: Arc::new((*child.input).clone()),
29 });
30 self.try_optimize(&plan)
31 .map(|opt_plan| opt_plan.or_else(|| Some(plan)))
32 } else {
33 Ok(None)
34 }
35 }
36
37 fn name(&self) -> &str {
38 "MergeLimit"
39 }
40
41 fn apply_order(&self) -> Option<ApplyOrder> {
42 Some(ApplyOrder::TopDown)
43 }
44}
45
46#[cfg(test)]
47mod tests {
48 use crate::catalog::EMPTY_SCHEMA_REF;
49 use crate::optimizer::rule::MergeLimit;
50 use crate::optimizer::LogicalOptimizer;
51 use crate::plan::logical_plan::{EmptyRelation, Limit, LogicalPlan};
52 use std::sync::Arc;
53
54 fn build_optimizer() -> LogicalOptimizer {
55 LogicalOptimizer::with_rules(vec![Arc::new(MergeLimit)])
56 }
57
58 #[test]
59 fn merge_limit() {
60 let plan = LogicalPlan::Limit(Limit {
61 limit: Some(10),
62 offset: 0,
63 input: Arc::new(LogicalPlan::Limit(Limit {
64 limit: Some(1000),
65 offset: 0,
66 input: Arc::new(LogicalPlan::Limit(Limit {
67 limit: None,
68 offset: 10,
69 input: Arc::new(LogicalPlan::EmptyRelation(EmptyRelation {
70 produce_one_row: false,
71 schema: EMPTY_SCHEMA_REF.clone(),
72 })),
73 })),
74 })),
75 });
76 let optimized_plan = build_optimizer().optimize(&plan).unwrap();
77
78 if let LogicalPlan::Limit(Limit {
79 limit,
80 offset,
81 input,
82 }) = optimized_plan
83 {
84 assert_eq!(limit, Some(10));
85 assert_eq!(offset, 10);
86 assert!(matches!(input.as_ref(), LogicalPlan::EmptyRelation(_)));
87 } else {
88 panic!("the first node should be limit");
89 }
90 }
91}