datafusion_physical_optimizer/
output_requirements.rs1use std::sync::Arc;
26
27use crate::PhysicalOptimizerRule;
28
29use datafusion_common::config::ConfigOptions;
30use datafusion_common::tree_node::{
31 Transformed, TransformedResult, TreeNode, TreeNodeRecursion,
32};
33use datafusion_common::{Result, Statistics, internal_err};
34use datafusion_execution::TaskContext;
35use datafusion_physical_expr::Distribution;
36use datafusion_physical_expr_common::sort_expr::OrderingRequirements;
37use datafusion_physical_plan::execution_plan::{
38 Boundedness, replace_children_if_necessary,
39};
40use datafusion_physical_plan::projection::{
41 ProjectionExec, make_with_child, update_expr, update_ordering_requirement,
42};
43use datafusion_physical_plan::scalar_subquery::ScalarSubqueryExec;
44use datafusion_physical_plan::sorts::sort::SortExec;
45use datafusion_physical_plan::sorts::sort_preserving_merge::SortPreservingMergeExec;
46use datafusion_physical_plan::{
47 ChildStats, ChildrenPropertiesMode, DisplayAs, DisplayFormatType, ExecutionPlan,
48 ExecutionPlanProperties, PlanProperties, ReplaceChildrenOptions,
49 SendableRecordBatchStream, StatisticsArgs,
50};
51
52#[derive(Debug)]
62pub struct OutputRequirements {
63 mode: RuleMode,
64}
65
66impl OutputRequirements {
67 pub fn new_add_mode() -> Self {
74 Self {
75 mode: RuleMode::Add,
76 }
77 }
78
79 pub fn new_remove_mode() -> Self {
86 Self {
87 mode: RuleMode::Remove,
88 }
89 }
90}
91
92#[derive(Debug, Ord, PartialOrd, PartialEq, Eq, Hash)]
93enum RuleMode {
94 Add,
95 Remove,
96}
97
98#[derive(Debug)]
105pub struct OutputRequirementExec {
106 input: Arc<dyn ExecutionPlan>,
107 order_requirement: Option<OrderingRequirements>,
108 dist_requirement: Distribution,
109 cache: Arc<PlanProperties>,
110 fetch: Option<usize>,
111}
112
113impl OutputRequirementExec {
114 pub fn new(
115 input: Arc<dyn ExecutionPlan>,
116 requirements: Option<OrderingRequirements>,
117 dist_requirement: Distribution,
118 fetch: Option<usize>,
119 ) -> Self {
120 let cache = Self::compute_properties(&input, &fetch);
121 Self {
122 input,
123 order_requirement: requirements,
124 dist_requirement,
125 cache: Arc::new(cache),
126 fetch,
127 }
128 }
129
130 pub fn input(&self) -> Arc<dyn ExecutionPlan> {
131 Arc::clone(&self.input)
132 }
133
134 fn compute_properties(
136 input: &Arc<dyn ExecutionPlan>,
137 fetch: &Option<usize>,
138 ) -> PlanProperties {
139 let boundedness = if fetch.is_some() {
140 Boundedness::Bounded
141 } else {
142 input.boundedness()
143 };
144
145 PlanProperties::new(
146 input.equivalence_properties().clone(), input.output_partitioning().clone(), input.pipeline_behavior(), boundedness, )
151 }
152
153 pub fn fetch(&self) -> Option<usize> {
155 self.fetch
156 }
157}
158
159impl DisplayAs for OutputRequirementExec {
160 fn fmt_as(
161 &self,
162 t: DisplayFormatType,
163 f: &mut std::fmt::Formatter,
164 ) -> std::fmt::Result {
165 match t {
166 DisplayFormatType::Default | DisplayFormatType::Verbose => {
167 let order_cols = self
168 .order_requirement
169 .as_ref()
170 .map(|reqs| reqs.first())
171 .map(|lex| {
172 let pairs: Vec<String> = lex
173 .iter()
174 .map(|req| {
175 let direction = req
176 .options
177 .as_ref()
178 .map(
179 |opt| if opt.descending { "desc" } else { "asc" },
180 )
181 .unwrap_or("unspecified");
182 format!("({}, {direction})", req.expr)
183 })
184 .collect();
185 format!("[{}]", pairs.join(", "))
186 })
187 .unwrap_or_else(|| "[]".to_string());
188
189 write!(
190 f,
191 "OutputRequirementExec: order_by={}, dist_by={}",
192 order_cols, self.dist_requirement
193 )
194 }
195 DisplayFormatType::TreeRender => {
196 write!(f, "")
197 }
198 }
199 }
200}
201
202impl ExecutionPlan for OutputRequirementExec {
203 fn name(&self) -> &'static str {
204 "OutputRequirementExec"
205 }
206
207 fn properties(&self) -> &Arc<PlanProperties> {
208 &self.cache
209 }
210
211 fn benefits_from_input_partitioning(&self) -> Vec<bool> {
212 vec![false]
213 }
214
215 fn required_input_distribution(&self) -> Vec<Distribution> {
216 self.input_distribution_requirements().into_per_child()
217 }
218
219 fn input_distribution_requirements(
220 &self,
221 ) -> datafusion_physical_plan::InputDistributionRequirements {
222 datafusion_physical_plan::InputDistributionRequirements::new(vec![
223 self.dist_requirement.clone(),
224 ])
225 }
226
227 fn maintains_input_order(&self) -> Vec<bool> {
228 vec![true]
229 }
230
231 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
232 vec![&self.input]
233 }
234
235 fn required_input_ordering(&self) -> Vec<Option<OrderingRequirements>> {
236 vec![self.order_requirement.clone()]
237 }
238
239 fn replace_children(
240 self: Arc<Self>,
241 mut children: Vec<Arc<dyn ExecutionPlan>>,
242 _: ReplaceChildrenOptions,
243 ) -> Result<Arc<dyn ExecutionPlan>> {
244 Ok(Arc::new(Self::new(
245 children.remove(0), self.order_requirement.clone(),
247 self.dist_requirement.clone(),
248 self.fetch,
249 )))
250 }
251
252 fn with_new_children(
253 self: Arc<Self>,
254 children: Vec<Arc<dyn ExecutionPlan>>,
255 ) -> Result<Arc<dyn ExecutionPlan>> {
256 self.replace_children(
257 children,
258 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
259 )
260 }
261
262 fn execute(
263 &self,
264 _partition: usize,
265 _context: Arc<TaskContext>,
266 ) -> Result<SendableRecordBatchStream> {
267 unreachable!();
268 }
269
270 fn child_stats_requests(&self, partition: Option<usize>) -> Vec<ChildStats> {
271 vec![ChildStats::At(partition)]
272 }
273
274 fn statistics_from_inputs(
275 &self,
276 input_stats: &[Arc<Statistics>],
277 _args: &StatisticsArgs,
278 ) -> Result<Arc<Statistics>> {
279 Ok(Arc::clone(&input_stats[0]))
280 }
281
282 #[expect(
283 deprecated,
284 reason = "HashPartitioned is accepted during the KeyPartitioned migration"
285 )]
286 fn try_swapping_with_projection(
287 &self,
288 projection: &ProjectionExec,
289 ) -> Result<Option<Arc<dyn ExecutionPlan>>> {
290 let proj_exprs = projection.expr();
292 if proj_exprs.len() >= projection.input().schema().fields().len() {
293 return Ok(None);
294 }
295
296 let mut requirements = self.required_input_ordering().swap_remove(0);
297 if let Some(reqs) = requirements {
298 let mut updated_reqs = vec![];
299 let (lexes, soft) = reqs.into_alternatives();
300 for lex in lexes.into_iter() {
301 let Some(updated_lex) = update_ordering_requirement(lex, proj_exprs)?
302 else {
303 return Ok(None);
304 };
305 updated_reqs.push(updated_lex);
306 }
307 requirements = OrderingRequirements::new_alternatives(updated_reqs, soft);
308 }
309
310 let input_distributions = self.input_distribution_requirements();
311 let dist_req = match input_distributions.child_distribution(0) {
312 Some(
313 Distribution::HashPartitioned(exprs)
314 | Distribution::KeyPartitioned(exprs),
315 ) => {
316 let mut updated_exprs = vec![];
317 for expr in exprs {
318 let Some(new_expr) = update_expr(expr, projection.expr(), false)?
319 else {
320 return Ok(None);
321 };
322 updated_exprs.push(new_expr);
323 }
324 Distribution::KeyPartitioned(updated_exprs)
325 }
326 Some(dist) => dist.clone(),
327 None => {
328 return internal_err!(
329 "OutputRequirementExec missing input distribution requirement"
330 );
331 }
332 };
333
334 make_with_child(projection, &self.input()).map(|input| {
335 let e = OutputRequirementExec::new(input, requirements, dist_req, self.fetch);
336 Some(Arc::new(e) as _)
337 })
338 }
339
340 fn fetch(&self) -> Option<usize> {
341 self.fetch
342 }
343
344 fn apply_expressions(
345 &self,
346 _f: &mut dyn FnMut(
347 &Arc<dyn datafusion_physical_expr_common::physical_expr::PhysicalExpr>,
348 ) -> Result<TreeNodeRecursion>,
349 ) -> Result<TreeNodeRecursion> {
350 Ok(TreeNodeRecursion::Continue)
351 }
352}
353
354impl PhysicalOptimizerRule for OutputRequirements {
355 fn optimize(
356 &self,
357 plan: Arc<dyn ExecutionPlan>,
358 _config: &ConfigOptions,
359 ) -> Result<Arc<dyn ExecutionPlan>> {
360 match self.mode {
361 RuleMode::Add => require_top_ordering(plan),
362 RuleMode::Remove => plan
363 .transform_up(|plan| {
364 if let Some(sort_req) = plan.downcast_ref::<OutputRequirementExec>() {
365 Ok(Transformed::yes(sort_req.input()))
366 } else {
367 Ok(Transformed::no(plan))
368 }
369 })
370 .data(),
371 }
372 }
373
374 fn name(&self) -> &str {
375 "OutputRequirements"
376 }
377
378 fn schema_check(&self) -> bool {
379 true
380 }
381}
382
383fn require_top_ordering(plan: Arc<dyn ExecutionPlan>) -> Result<Arc<dyn ExecutionPlan>> {
391 if plan.downcast_ref::<OutputRequirementExec>().is_some() {
392 return Ok(plan);
393 }
394 let (new_plan, is_changed) = require_top_ordering_helper(plan)?;
395 if is_changed {
396 Ok(new_plan)
397 } else {
398 Ok(Arc::new(OutputRequirementExec::new(
400 new_plan,
401 None,
403 Distribution::UnspecifiedDistribution,
404 None,
405 )) as _)
406 }
407}
408
409fn output_requirement_child(plan: &dyn ExecutionPlan) -> Option<usize> {
412 if plan.children().len() == 1 {
413 Some(0)
414 } else if plan.downcast_ref::<ScalarSubqueryExec>().is_some() {
415 Some(0)
420 } else {
421 None
422 }
423}
424
425fn require_top_ordering_helper(
429 plan: Arc<dyn ExecutionPlan>,
430) -> Result<(Arc<dyn ExecutionPlan>, bool)> {
431 if plan.downcast_ref::<OutputRequirementExec>().is_some() {
434 return Ok((plan, true));
435 }
436
437 if let Some(sort_exec) = plan.downcast_ref::<SortExec>() {
439 let req_dist = sort_exec
443 .input_distribution_requirements()
444 .into_per_child()
445 .swap_remove(0);
446 let req_ordering = sort_exec.expr();
447 let reqs = OrderingRequirements::from(req_ordering.clone());
448 let fetch = sort_exec.fetch();
449
450 Ok((
451 Arc::new(OutputRequirementExec::new(
452 plan,
453 Some(reqs),
454 req_dist,
455 fetch,
456 )) as _,
457 true,
458 ))
459 } else if let Some(spm) = plan.downcast_ref::<SortPreservingMergeExec>() {
460 let reqs = OrderingRequirements::from(spm.expr().clone());
461 let fetch = spm.fetch();
462 Ok((
463 Arc::new(OutputRequirementExec::new(
464 plan,
465 Some(reqs),
466 Distribution::SinglePartition,
467 fetch,
468 )) as _,
469 true,
470 ))
471 } else if let Some(idx) = output_requirement_child(plan.as_ref()) {
472 if plan.maintains_input_order()[idx]
478 && plan.required_input_ordering()[idx]
479 .as_ref()
480 .is_none_or(|o| matches!(o, OrderingRequirements::Soft(_)))
481 {
482 let mut children: Vec<Arc<dyn ExecutionPlan>> =
483 plan.children().into_iter().map(Arc::clone).collect();
484 let (new_child, is_changed) =
485 require_top_ordering_helper(Arc::clone(&children[idx]))?;
486 if is_changed {
487 children[idx] = new_child;
488 return Ok((replace_children_if_necessary(plan, children)?, true));
489 }
490 }
491 Ok((plan, false))
492 } else {
493 Ok((plan, false))
495 }
496}
497
498