datafusion_physical_optimizer/
limited_distinct_aggregation.rs1use std::sync::Arc;
22
23use datafusion_physical_plan::aggregates::{AggregateExec, LimitOptions};
24use datafusion_physical_plan::limit::{GlobalLimitExec, LocalLimitExec};
25use datafusion_physical_plan::{ExecutionPlan, ExecutionPlanProperties};
26
27use datafusion_common::Result;
28use datafusion_common::config::ConfigOptions;
29use datafusion_common::tree_node::{Transformed, TransformedResult, TreeNode};
30
31use crate::PhysicalOptimizerRule;
32use itertools::Itertools;
33
34#[derive(Debug)]
39pub struct LimitedDistinctAggregation {}
40
41impl LimitedDistinctAggregation {
42 pub fn new() -> Self {
44 Self {}
45 }
46
47 fn transform_agg(
48 aggr: &AggregateExec,
49 limit: usize,
50 ) -> Option<Arc<dyn ExecutionPlan>> {
51 if !aggr.is_unordered_unfiltered_group_by_distinct() {
53 return None;
54 }
55
56 let new_aggr = aggr.with_new_limit_options(Some(LimitOptions::new(limit)));
58
59 Some(Arc::new(new_aggr))
60 }
61
62 fn transform_limit(plan: Arc<dyn ExecutionPlan>) -> Option<Arc<dyn ExecutionPlan>> {
67 let limit: usize;
68 let mut global_fetch: Option<usize> = None;
69 let mut global_skip: usize = 0;
70 let children: Vec<Arc<dyn ExecutionPlan>>;
71 let mut is_global_limit = false;
72 if let Some(local_limit) = plan.downcast_ref::<LocalLimitExec>() {
73 limit = local_limit.fetch();
74 children = local_limit.children().into_iter().cloned().collect();
75 } else {
76 let global_limit = plan.downcast_ref::<GlobalLimitExec>()?;
77 global_fetch = global_limit.fetch();
78 global_fetch?;
79 global_skip = global_limit.skip();
80 limit = global_fetch.unwrap() + global_skip;
82 children = global_limit.children().into_iter().cloned().collect();
83 is_global_limit = true
84 }
85 let child = children.iter().exactly_one().ok()?;
86 if plan.output_ordering().is_some() {
88 return None;
89 }
90 if plan.required_input_ordering()[0].is_some() {
92 return None;
93 }
94
95 let mut match_aggr: Arc<dyn ExecutionPlan> = plan;
98 let mut found_match_aggr = false;
99
100 let mut rewrite_applicable = true;
101 let closure = |plan: Arc<dyn ExecutionPlan>| {
102 if !rewrite_applicable {
103 return Ok(Transformed::no(plan));
104 }
105 if let Some(aggr) = plan.downcast_ref::<AggregateExec>() {
106 if found_match_aggr
107 && let Some(parent_aggr) = match_aggr.downcast_ref::<AggregateExec>()
108 && !parent_aggr.group_expr().eq(aggr.group_expr())
109 {
110 rewrite_applicable = false;
113 return Ok(Transformed::no(plan));
114 }
115 match Self::transform_agg(aggr, limit) {
118 None => {}
119 Some(new_aggr) => {
120 match_aggr = plan;
121 found_match_aggr = true;
122 return Ok(Transformed::yes(new_aggr));
123 }
124 }
125 }
126 rewrite_applicable = false;
127 Ok(Transformed::no(plan))
128 };
129 let child = child.to_owned().transform_down(closure).data().ok()?;
130 if is_global_limit {
131 return Some(Arc::new(GlobalLimitExec::new(
132 child,
133 global_skip,
134 global_fetch,
135 )));
136 }
137 Some(Arc::new(LocalLimitExec::new(child, limit)))
138 }
139}
140
141impl Default for LimitedDistinctAggregation {
142 fn default() -> Self {
143 Self::new()
144 }
145}
146
147impl PhysicalOptimizerRule for LimitedDistinctAggregation {
148 fn optimize(
149 &self,
150 plan: Arc<dyn ExecutionPlan>,
151 config: &ConfigOptions,
152 ) -> Result<Arc<dyn ExecutionPlan>> {
153 if config.optimizer.enable_distinct_aggregation_soft_limit {
154 plan.transform_down(|plan| {
155 Ok(
156 if let Some(plan) =
157 LimitedDistinctAggregation::transform_limit(plan.to_owned())
158 {
159 Transformed::yes(plan)
160 } else {
161 Transformed::no(plan)
162 },
163 )
164 })
165 .data()
166 } else {
167 Ok(plan)
168 }
169 }
170
171 fn name(&self) -> &str {
172 "LimitedDistinctAggregation"
173 }
174
175 fn schema_check(&self) -> bool {
176 true
177 }
178}
179
180