1use std::sync::Arc;
22
23use super::metrics::{BaselineMetrics, ExecutionPlanMetricsSet, MetricsSet};
24use super::stream::{ObservedStream, RecordBatchReceiverStream};
25use super::{
26 DisplayAs, ExecutionPlanProperties, PlanProperties, SendableRecordBatchStream,
27 Statistics,
28};
29use crate::execution_plan::{
30 CardinalityEffect, EvaluationType, SchedulingType, replace_children_if_necessary,
31};
32use crate::filter_pushdown::{FilterDescription, FilterPushdownPhase};
33use crate::projection::{ProjectionExec, make_with_child};
34use crate::sort_pushdown::SortOrderPushdownResult;
35use crate::statistics::{ChildStats, StatisticsArgs};
36use crate::{
37 ChildrenPropertiesMode, DisplayFormatType, ExecutionPlan, Partitioning,
38 ReplaceChildrenOptions, validate_child_count,
39};
40use datafusion_physical_expr_common::sort_expr::PhysicalSortExpr;
41
42use datafusion_common::config::ConfigOptions;
43use datafusion_common::tree_node::TreeNodeRecursion;
44use datafusion_common::{Result, assert_eq_or_internal_err, internal_err};
45use datafusion_execution::TaskContext;
46use datafusion_physical_expr::PhysicalExpr;
47
48#[derive(Debug, Clone)]
51pub struct CoalescePartitionsExec {
52 input: Arc<dyn ExecutionPlan>,
54 metrics: ExecutionPlanMetricsSet,
56 cache: Arc<PlanProperties>,
57 pub(crate) fetch: Option<usize>,
59}
60
61impl CoalescePartitionsExec {
62 pub fn new(input: Arc<dyn ExecutionPlan>) -> Self {
64 let cache = Self::compute_properties(&input);
65 CoalescePartitionsExec {
66 input,
67 metrics: ExecutionPlanMetricsSet::new(),
68 cache: Arc::new(cache),
69 fetch: None,
70 }
71 }
72
73 pub fn with_fetch(mut self, fetch: Option<usize>) -> Self {
75 self.fetch = fetch;
76 self
77 }
78
79 pub fn input(&self) -> &Arc<dyn ExecutionPlan> {
81 &self.input
82 }
83
84 fn compute_properties(input: &Arc<dyn ExecutionPlan>) -> PlanProperties {
86 let input_partitions = input.output_partitioning().partition_count();
87 let (drive, scheduling) = if input_partitions > 1 {
88 (EvaluationType::Eager, SchedulingType::Cooperative)
89 } else {
90 (
91 input.properties().evaluation_type,
92 input.properties().scheduling_type,
93 )
94 };
95
96 let mut eq_properties = input.equivalence_properties().clone();
98 eq_properties.clear_orderings();
99 eq_properties.clear_per_partition_constants();
100 PlanProperties::new(
101 eq_properties, Partitioning::UnknownPartitioning(1), input.pipeline_behavior(),
104 input.boundedness(),
105 )
106 .with_evaluation_type(drive)
107 .with_scheduling_type(scheduling)
108 }
109}
110
111impl DisplayAs for CoalescePartitionsExec {
112 fn fmt_as(
113 &self,
114 t: DisplayFormatType,
115 f: &mut std::fmt::Formatter,
116 ) -> std::fmt::Result {
117 match t {
118 DisplayFormatType::Default | DisplayFormatType::Verbose => match self.fetch {
119 Some(fetch) => {
120 write!(f, "CoalescePartitionsExec: fetch={fetch}")
121 }
122 None => write!(f, "CoalescePartitionsExec"),
123 },
124 DisplayFormatType::TreeRender => match self.fetch {
125 Some(fetch) => {
126 write!(f, "limit: {fetch}")
127 }
128 None => write!(f, ""),
129 },
130 }
131 }
132}
133
134impl ExecutionPlan for CoalescePartitionsExec {
135 fn name(&self) -> &'static str {
136 "CoalescePartitionsExec"
137 }
138
139 fn properties(&self) -> &Arc<PlanProperties> {
141 &self.cache
142 }
143
144 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
145 vec![&self.input]
146 }
147
148 fn benefits_from_input_partitioning(&self) -> Vec<bool> {
149 vec![false]
150 }
151
152 fn apply_expressions(
153 &self,
154 _f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
155 ) -> Result<TreeNodeRecursion> {
156 Ok(TreeNodeRecursion::Continue)
157 }
158
159 fn replace_children(
160 self: Arc<Self>,
161 mut children: Vec<Arc<dyn ExecutionPlan>>,
162 options: ReplaceChildrenOptions,
163 ) -> Result<Arc<dyn ExecutionPlan>> {
164 validate_child_count!(self, children);
165 match options.children_properties {
166 ChildrenPropertiesMode::Keep => Ok(Arc::new(Self {
167 input: children.swap_remove(0),
168 metrics: ExecutionPlanMetricsSet::new(),
169 ..Self::clone(&*self)
170 })),
171 ChildrenPropertiesMode::Recompute => {
172 let mut plan = CoalescePartitionsExec::new(children.swap_remove(0));
173 plan.fetch = self.fetch;
174 Ok(Arc::new(plan))
175 }
176 }
177 }
178
179 fn with_new_children(
180 self: Arc<Self>,
181 children: Vec<Arc<dyn ExecutionPlan>>,
182 ) -> Result<Arc<dyn ExecutionPlan>> {
183 self.replace_children(
184 children,
185 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
186 )
187 }
188
189 fn with_new_children_and_same_properties(
190 self: Arc<Self>,
191 children: Vec<Arc<dyn ExecutionPlan>>,
192 ) -> Result<Arc<dyn ExecutionPlan>> {
193 self.replace_children(
194 children,
195 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Keep),
196 )
197 }
198
199 fn execute(
200 &self,
201 partition: usize,
202 context: Arc<TaskContext>,
203 ) -> Result<SendableRecordBatchStream> {
204 assert_eq_or_internal_err!(
206 partition,
207 0,
208 "CoalescePartitionsExec invalid partition {partition}"
209 );
210
211 let input_partitions = self.input.output_partitioning().partition_count();
212 match input_partitions {
213 0 => internal_err!(
214 "CoalescePartitionsExec requires at least one input partition"
215 ),
216 1 => {
217 let child_stream = self.input.execute(0, context)?;
220 if self.fetch.is_some() {
221 let baseline_metrics = BaselineMetrics::new(&self.metrics, partition);
222 return Ok(Box::pin(ObservedStream::new(
223 child_stream,
224 baseline_metrics,
225 self.fetch,
226 )));
227 }
228 Ok(child_stream)
229 }
230 _ => {
231 let baseline_metrics = BaselineMetrics::new(&self.metrics, partition);
232 let elapsed_compute = baseline_metrics.elapsed_compute().clone();
235 let _timer = elapsed_compute.timer();
236
237 let mut builder =
241 RecordBatchReceiverStream::builder(self.schema(), input_partitions);
242
243 for part_i in 0..input_partitions {
246 builder.run_input(
247 Arc::clone(&self.input),
248 part_i,
249 Arc::clone(&context),
250 );
251 }
252
253 let stream = builder.build();
254 Ok(Box::pin(ObservedStream::new(
255 stream,
256 baseline_metrics,
257 self.fetch,
258 )))
259 }
260 }
261 }
262
263 fn metrics(&self) -> Option<MetricsSet> {
264 Some(self.metrics.clone_inner())
265 }
266
267 fn child_stats_requests(&self, _partition: Option<usize>) -> Vec<ChildStats> {
268 vec![ChildStats::At(None)]
269 }
270
271 fn statistics_from_inputs(
272 &self,
273 input_stats: &[Arc<Statistics>],
274 _args: &StatisticsArgs,
275 ) -> Result<Arc<Statistics>> {
276 let stats = input_stats[0].as_ref().clone();
277 Ok(Arc::new(stats.with_fetch(self.fetch, 0, 1)?))
278 }
279
280 fn supports_limit_pushdown(&self) -> bool {
281 true
282 }
283
284 fn cardinality_effect(&self) -> CardinalityEffect {
285 CardinalityEffect::Equal
286 }
287
288 fn try_swapping_with_projection(
292 &self,
293 projection: &ProjectionExec,
294 ) -> Result<Option<Arc<dyn ExecutionPlan>>> {
295 if projection.expr().len() >= projection.input().schema().fields().len() {
297 return Ok(None);
298 }
299 make_with_child(projection, projection.input().children()[0]).map(|e| {
301 if self.fetch.is_some() {
302 let mut plan = CoalescePartitionsExec::new(e);
303 plan.fetch = self.fetch;
304 Some(Arc::new(plan) as _)
305 } else {
306 Some(Arc::new(CoalescePartitionsExec::new(e)) as _)
307 }
308 })
309 }
310
311 fn fetch(&self) -> Option<usize> {
312 self.fetch
313 }
314
315 fn with_fetch(&self, limit: Option<usize>) -> Option<Arc<dyn ExecutionPlan>> {
316 Some(Arc::new(CoalescePartitionsExec {
317 input: Arc::clone(&self.input),
318 fetch: limit,
319 metrics: self.metrics.clone(),
320 cache: Arc::clone(&self.cache),
321 }))
322 }
323
324 fn with_preserve_order(
325 &self,
326 preserve_order: bool,
327 ) -> Option<Arc<dyn ExecutionPlan>> {
328 self.input
329 .with_preserve_order(preserve_order)
330 .and_then(|new_input| {
331 replace_children_if_necessary(Arc::new(self.clone()), vec![new_input])
332 .ok()
333 })
334 }
335
336 fn gather_filters_for_pushdown(
337 &self,
338 _phase: FilterPushdownPhase,
339 parent_filters: Vec<Arc<dyn PhysicalExpr>>,
340 _config: &ConfigOptions,
341 ) -> Result<FilterDescription> {
342 FilterDescription::from_children(parent_filters, &self.children())
343 }
344
345 fn try_pushdown_sort(
346 &self,
347 order: &[PhysicalSortExpr],
348 ) -> Result<SortOrderPushdownResult<Arc<dyn ExecutionPlan>>> {
349 let result = self.input.try_pushdown_sort(order)?;
357
358 let has_multiple_partitions =
361 self.input.output_partitioning().partition_count() > 1;
362
363 result
364 .try_map(|new_input| {
365 Ok(
366 Arc::new(
367 CoalescePartitionsExec::new(new_input).with_fetch(self.fetch),
368 ) as Arc<dyn ExecutionPlan>,
369 )
370 })
371 .map(|r| {
372 if has_multiple_partitions {
373 r.into_inexact()
375 } else {
376 r
377 }
378 })
379 }
380
381 #[cfg(feature = "proto")]
382 fn try_to_proto(
383 &self,
384 ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
385 ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalPlanNode>> {
386 use datafusion_proto_models::protobuf;
387 let input = ctx.encode_child(self.input())?;
388 Ok(Some(protobuf::PhysicalPlanNode {
389 physical_plan_type: Some(
390 protobuf::physical_plan_node::PhysicalPlanType::Merge(Box::new(
391 protobuf::CoalescePartitionsExecNode {
392 input: Some(Box::new(input)),
393 fetch: self.fetch().map(|f| f as u32),
394 },
395 )),
396 ),
397 }))
398 }
399}
400
401#[cfg(feature = "proto")]
402impl CoalescePartitionsExec {
403 pub fn try_from_proto(
411 node: &datafusion_proto_models::protobuf::PhysicalPlanNode,
412 ctx: &crate::proto::ExecutionPlanDecodeCtx<'_>,
413 ) -> Result<Arc<dyn ExecutionPlan>> {
414 use datafusion_proto_models::protobuf;
415 let merge = crate::expect_plan_variant!(
416 node,
417 protobuf::physical_plan_node::PhysicalPlanType::Merge,
418 "CoalescePartitionsExec",
419 );
420 let input = ctx.decode_required_child(
421 merge.input.as_deref(),
422 "CoalescePartitionsExec",
423 "input",
424 )?;
425 Ok(Arc::new(
426 CoalescePartitionsExec::new(input)
427 .with_fetch(merge.fetch.map(|f| f as usize)),
428 ))
429 }
430}
431
432#[cfg(test)]
433mod tests {
434 use super::*;
435 use crate::test::exec::{
436 BarrierExec, BlockingExec, PanicExec, assert_strong_count_converges_to_zero,
437 };
438 use crate::test::{self, assert_is_pending};
439 use crate::{collect, common};
440
441 use std::time::Duration;
442
443 use arrow::array::RecordBatch;
444 use arrow::datatypes::{DataType, Field, Schema};
445
446 use futures::FutureExt;
447
448 #[tokio::test]
449 async fn merge() -> Result<()> {
450 let task_ctx = Arc::new(TaskContext::default());
451
452 let num_partitions = 4;
453 let csv = test::scan_partitioned(num_partitions);
454
455 assert_eq!(csv.output_partitioning().partition_count(), num_partitions);
457
458 let merge = CoalescePartitionsExec::new(csv);
459
460 assert_eq!(
462 merge.properties().output_partitioning().partition_count(),
463 1
464 );
465
466 let iter = merge.execute(0, task_ctx)?;
468 let batches = common::collect(iter).await?;
469 assert_eq!(batches.len(), num_partitions);
470
471 let row_count: usize = batches.iter().map(|batch| batch.num_rows()).sum();
473 assert_eq!(row_count, 400);
474
475 Ok(())
476 }
477
478 #[tokio::test]
479 async fn drops_input_plan_after_input_streams_start() -> Result<()> {
480 let task_ctx = Arc::new(TaskContext::default());
481 let schema =
482 Arc::new(Schema::new(vec![Field::new("a", DataType::Float32, true)]));
483 let input_partitions = 2;
484 let batch = RecordBatch::new_empty(Arc::clone(&schema));
485 let input = Arc::new(
486 BarrierExec::new(vec![vec![batch]; input_partitions], schema)
487 .without_start_barrier()
488 .with_finish_barrier()
489 .with_log(false),
490 );
491 let refs = Arc::downgrade(&input);
492
493 let input_plan: Arc<BarrierExec> = Arc::clone(&input);
494 let coalesce = CoalescePartitionsExec::new(input_plan);
495 let stream = coalesce.execute(0, task_ctx)?;
496 drop(coalesce);
497
498 tokio::time::timeout(Duration::from_secs(5), async {
499 while !input.is_finish_barrier_reached() {
502 tokio::task::yield_now().await;
503 }
504 })
505 .await
506 .expect("input streams should reach pending");
507
508 drop(input);
509
510 assert_strong_count_converges_to_zero(refs).await;
511
512 drop(stream);
513
514 Ok(())
515 }
516
517 #[tokio::test]
518 async fn test_drop_cancel() -> Result<()> {
519 let task_ctx = Arc::new(TaskContext::default());
520 let schema =
521 Arc::new(Schema::new(vec![Field::new("a", DataType::Float32, true)]));
522
523 let blocking_exec = Arc::new(BlockingExec::new(Arc::clone(&schema), 2));
524 let refs = blocking_exec.refs();
525 let coalesce_partitions_exec =
526 Arc::new(CoalescePartitionsExec::new(blocking_exec));
527
528 let fut = collect(coalesce_partitions_exec, task_ctx);
529 let mut fut = fut.boxed();
530
531 assert_is_pending(&mut fut);
532 drop(fut);
533 assert_strong_count_converges_to_zero(refs).await;
534
535 Ok(())
536 }
537
538 #[tokio::test]
539 #[should_panic(expected = "PanickingStream did panic")]
540 async fn test_panic() {
541 let task_ctx = Arc::new(TaskContext::default());
542 let schema =
543 Arc::new(Schema::new(vec![Field::new("a", DataType::Float32, true)]));
544
545 let panicking_exec = Arc::new(PanicExec::new(Arc::clone(&schema), 2));
546 let coalesce_partitions_exec =
547 Arc::new(CoalescePartitionsExec::new(panicking_exec));
548
549 collect(coalesce_partitions_exec, task_ctx).await.unwrap();
550 }
551
552 #[tokio::test]
553 async fn test_single_partition_with_fetch() -> Result<()> {
554 let task_ctx = Arc::new(TaskContext::default());
555
556 let input = test::scan_partitioned(1);
558
559 let coalesce = CoalescePartitionsExec::new(input).with_fetch(Some(3));
561
562 let stream = coalesce.execute(0, task_ctx)?;
563 let batches = common::collect(stream).await?;
564
565 let row_count: usize = batches.iter().map(|batch| batch.num_rows()).sum();
566 assert_eq!(row_count, 3, "Should only return 3 rows due to fetch=3");
567
568 Ok(())
569 }
570
571 #[tokio::test]
572 async fn test_multi_partition_with_fetch_one() -> Result<()> {
573 let task_ctx = Arc::new(TaskContext::default());
574
575 let input = test::scan_partitioned(4);
578
579 let coalesce = CoalescePartitionsExec::new(input).with_fetch(Some(1));
581
582 let stream = coalesce.execute(0, task_ctx)?;
583 let batches = common::collect(stream).await?;
584
585 let row_count: usize = batches.iter().map(|batch| batch.num_rows()).sum();
586 assert_eq!(
587 row_count, 1,
588 "Should only return 1 row due to fetch=1, not one per partition"
589 );
590
591 Ok(())
592 }
593
594 #[tokio::test]
595 async fn test_single_partition_without_fetch() -> Result<()> {
596 let task_ctx = Arc::new(TaskContext::default());
597
598 let input = test::scan_partitioned(1);
600
601 let coalesce = CoalescePartitionsExec::new(input);
603
604 let stream = coalesce.execute(0, task_ctx)?;
605 let batches = common::collect(stream).await?;
606
607 let row_count: usize = batches.iter().map(|batch| batch.num_rows()).sum();
608 assert_eq!(
609 row_count, 100,
610 "Should return all 100 rows when fetch is None"
611 );
612
613 Ok(())
614 }
615
616 #[tokio::test]
617 async fn test_single_partition_fetch_larger_than_batch() -> Result<()> {
618 let task_ctx = Arc::new(TaskContext::default());
619
620 let input = test::scan_partitioned(1);
622
623 let coalesce = CoalescePartitionsExec::new(input).with_fetch(Some(200));
625
626 let stream = coalesce.execute(0, task_ctx)?;
627 let batches = common::collect(stream).await?;
628
629 let row_count: usize = batches.iter().map(|batch| batch.num_rows()).sum();
630 assert_eq!(
631 row_count, 100,
632 "Should return all available rows (100) when fetch (200) is larger"
633 );
634
635 Ok(())
636 }
637
638 #[tokio::test]
639 async fn test_multi_partition_fetch_exact_match() -> Result<()> {
640 let task_ctx = Arc::new(TaskContext::default());
641
642 let num_partitions = 4;
644 let csv = test::scan_partitioned(num_partitions);
645
646 let coalesce = CoalescePartitionsExec::new(csv).with_fetch(Some(400));
648
649 let stream = coalesce.execute(0, task_ctx)?;
650 let batches = common::collect(stream).await?;
651
652 let row_count: usize = batches.iter().map(|batch| batch.num_rows()).sum();
653 assert_eq!(row_count, 400, "Should return exactly 400 rows");
654
655 Ok(())
656 }
657}