1use std::cmp::Ordering;
23use std::fmt::{Debug, Display, Formatter};
24use std::pin::Pin;
25use std::sync::Arc;
26use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
27use std::task::{Context, Poll};
28use std::vec;
29
30use super::common::SharedMemoryReservation;
31use super::metrics::{self, ExecutionPlanMetricsSet, MetricBuilder, MetricsSet};
32use super::{
33 DisplayAs, ExecutionPlanProperties, RecordBatchStream, SendableRecordBatchStream,
34};
35use crate::coalesce::LimitedBatchCoalescer;
36use crate::execution_plan::{CardinalityEffect, EvaluationType, SchedulingType};
37use crate::hash_utils::create_hashes;
38use crate::metrics::{BaselineMetrics, SpillMetrics};
39use crate::projection::{ProjectionExec, all_columns, make_with_child, update_expr};
40use crate::sorts::streaming_merge::StreamingMergeBuilder;
41use crate::spill::spill_manager::SpillManager;
42use crate::spill::spill_pool::{self, SpillPoolSink, SpillPoolWriter};
43use crate::statistics::{ChildStats, StatisticsArgs};
44use crate::stream::{EmptyRecordBatchStream, RecordBatchStreamAdapter};
45use crate::{
46 ChildrenPropertiesMode, DisplayFormatType, ExecutionPlan, Partitioning,
47 PlanProperties, ReplaceChildrenOptions, Statistics, validate_child_count,
48};
49
50use arrow::array::{Array, PrimitiveArray, RecordBatch, RecordBatchOptions, UInt64Array};
51use arrow::compute::take_arrays;
52use arrow::datatypes::{DataType, Schema, SchemaRef, UInt32Type};
53use arrow_schema::SortOptions;
54use datafusion_common::config::ConfigOptions;
55use datafusion_common::stats::Precision;
56use datafusion_common::tree_node::TreeNodeRecursion;
57use datafusion_common::utils::{compare_rows, extract_row_at_idx_to_buf, transpose};
58use datafusion_common::{
59 ColumnStatistics, DataFusionError, HashMap, ScalarValue, SplitPoint,
60 assert_or_internal_err, internal_datafusion_err, internal_err,
61 validate_range_split_points,
62};
63use datafusion_common::{Result, not_impl_err};
64use datafusion_common_runtime::SpawnedTask;
65use datafusion_execution::TaskContext;
66use datafusion_execution::memory_pool::MemoryConsumer;
67use datafusion_expr::ColumnarValue;
68use datafusion_physical_expr::{EquivalenceProperties, PhysicalExpr, RangePartitioning};
69use datafusion_physical_expr_common::physical_expr::PhysicalExprRef;
70use datafusion_physical_expr_common::sort_expr::LexOrdering;
71#[cfg(feature = "proto")]
72use datafusion_physical_expr_common::sort_expr::{
73 sort_exprs_try_from_proto, sort_exprs_try_to_proto,
74};
75#[cfg(feature = "proto")]
76use datafusion_proto_models::protobuf;
77
78use crate::filter_pushdown::{
79 ChildPushdownResult, FilterDescription, FilterPushdownPhase,
80 FilterPushdownPropagation,
81};
82use crate::joins::SeededRandomState;
83use crate::sort_pushdown::SortOrderPushdownResult;
84use datafusion_physical_expr_common::sort_expr::PhysicalSortExpr;
85use datafusion_physical_expr_common::utils::evaluate_expressions_to_arrays;
86use futures::stream::Stream;
87use futures::{FutureExt, StreamExt, TryStreamExt};
88use log::trace;
89use parking_lot::Mutex;
90
91mod distributor_channels;
92use crate::repartition::distributor_channels::SendError;
93use distributor_channels::{
94 DistributionReceiver, DistributionSender, channels, partition_aware_channels,
95};
96
97#[derive(Debug)]
154enum RepartitionBatch {
155 Memory(RecordBatch),
157 Spilled,
162}
163
164type MaybeBatch = Option<Result<RepartitionBatch>>;
165type InputPartitionsToCurrentPartitionSender = Vec<DistributionSender<MaybeBatch>>;
166type InputPartitionsToCurrentPartitionReceiver = Vec<DistributionReceiver<MaybeBatch>>;
167
168struct OutputChannel {
175 sender: DistributionSender<MaybeBatch>,
176 reservation: SharedMemoryReservation,
177 spill_writer: SpillPoolSink,
178 shared_coalescer: Option<SharedCoalescer>,
179}
180
181enum PartitionSpillWriters {
185 PerInput(Vec<Option<SpillPoolSink>>),
188 Shared(SpillPoolWriter),
190}
191
192impl PartitionSpillWriters {
193 fn take_for_input(&mut self, input: usize) -> Result<SpillPoolSink> {
198 match self {
199 PartitionSpillWriters::PerInput(writers) => {
200 writers[input].take().ok_or_else(|| {
201 internal_datafusion_err!(
202 "spill writer for input partition requested more than once"
203 )
204 })
205 }
206 PartitionSpillWriters::Shared(writer) => Ok(writer.new_sink()),
207 }
208 }
209}
210
211impl OutputChannel {
212 fn coalesce(&mut self, batch: RecordBatch) -> Result<Vec<RecordBatch>> {
213 match &self.shared_coalescer {
214 Some(shared) => Ok(shared.push_and_drain(batch)?),
215 None => Ok(vec![batch]),
216 }
217 }
218
219 async fn send(&mut self, batch: RecordBatch) -> Result<(), SendError<MaybeBatch>> {
225 let size = batch.get_array_memory_size();
226
227 let (payload, is_memory_batch) = {
230 match self.reservation.try_grow(size) {
231 Ok(_) => (Ok(RepartitionBatch::Memory(batch)), true),
232 Err(_) => match self.spill_writer.push_batch(&batch) {
233 Ok(()) => (Ok(RepartitionBatch::Spilled), false),
234 Err(err) => (Err(err), false),
235 },
236 }
237 };
238
239 let result = self.sender.send(Some(payload)).await;
240 if result.is_err() && is_memory_batch {
241 self.reservation.shrink(size);
242 }
243 result
244 }
245
246 async fn finalize(mut self) -> Result<()> {
247 let Some(shared) = self.shared_coalescer.take() else {
248 return Ok(());
249 };
250 for batch in shared.finalize()? {
251 let _ = self.send(batch).await;
254 }
255 Ok(())
256 }
257}
258
259#[derive(Clone)]
269struct SharedCoalescer {
270 inner: Arc<Mutex<LimitedBatchCoalescer>>,
271 active_senders: Arc<AtomicUsize>,
272}
273
274impl SharedCoalescer {
275 fn new(schema: SchemaRef, target_batch_size: usize, num_senders: usize) -> Self {
276 Self {
277 inner: Arc::new(Mutex::new(LimitedBatchCoalescer::new(
278 schema,
279 target_batch_size,
280 None,
281 ))),
282 active_senders: Arc::new(AtomicUsize::new(num_senders)),
283 }
284 }
285
286 fn push_and_drain(&self, batch: RecordBatch) -> Result<Vec<RecordBatch>> {
289 let mut acc = Vec::new();
290 let mut c = self.inner.lock();
291 c.push_batch(batch)?;
292 while let Some(b) = c.next_completed_batch() {
293 acc.push(b);
294 }
295 Ok(acc)
296 }
297
298 fn finalize(&self) -> Result<Vec<RecordBatch>> {
302 let was_last = self.active_senders.fetch_sub(1, AtomicOrdering::AcqRel) == 1;
303 if !was_last {
304 return Ok(vec![]);
305 }
306 let mut acc = Vec::new();
307 let mut c = self.inner.lock();
308 c.finish()?;
309 while let Some(b) = c.next_completed_batch() {
310 acc.push(b);
311 }
312 Ok(acc)
313 }
314}
315
316struct PartitionChannels {
338 tx: InputPartitionsToCurrentPartitionSender,
340 rx: InputPartitionsToCurrentPartitionReceiver,
342 reservation: SharedMemoryReservation,
344 shared_coalescer: Option<SharedCoalescer>,
348 spill_writers: PartitionSpillWriters,
353 spill_readers: Vec<SendableRecordBatchStream>,
356}
357
358struct ConsumingInputStreamsState {
359 channels: HashMap<usize, PartitionChannels>,
362
363 abort_helper: Arc<Vec<SpawnedTask<()>>>,
365}
366
367impl Debug for ConsumingInputStreamsState {
368 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
369 f.debug_struct("ConsumingInputStreamsState")
370 .field("num_channels", &self.channels.len())
371 .field("abort_helper", &self.abort_helper)
372 .finish()
373 }
374}
375
376#[derive(Default)]
378enum RepartitionExecState {
379 #[default]
382 NotInitialized,
383 InputStreamsInitialized(Vec<(SendableRecordBatchStream, RepartitionMetrics)>),
387 ConsumingInputStreams(ConsumingInputStreamsState),
390}
391
392impl Debug for RepartitionExecState {
393 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
394 match self {
395 RepartitionExecState::NotInitialized => write!(f, "NotInitialized"),
396 RepartitionExecState::InputStreamsInitialized(v) => {
397 write!(f, "InputStreamsInitialized({:?})", v.len())
398 }
399 RepartitionExecState::ConsumingInputStreams(v) => {
400 write!(f, "ConsumingInputStreams({v:?})")
401 }
402 }
403 }
404}
405
406impl RepartitionExecState {
407 fn ensure_input_streams_initialized(
408 &mut self,
409 input: &Arc<dyn ExecutionPlan>,
410 metrics: &ExecutionPlanMetricsSet,
411 output_partitions: usize,
412 ctx: &Arc<TaskContext>,
413 ) -> Result<()> {
414 if !matches!(self, RepartitionExecState::NotInitialized) {
415 return Ok(());
416 }
417
418 let num_input_partitions = input.output_partitioning().partition_count();
419 let mut streams_and_metrics = Vec::with_capacity(num_input_partitions);
420
421 for i in 0..num_input_partitions {
422 let metrics = RepartitionMetrics::new(i, output_partitions, metrics);
423
424 let timer = metrics.fetch_time.timer();
425 let stream = input.execute(i, Arc::clone(ctx))?;
426 timer.done();
427
428 streams_and_metrics.push((stream, metrics));
429 }
430 *self = RepartitionExecState::InputStreamsInitialized(streams_and_metrics);
431 Ok(())
432 }
433
434 #[expect(clippy::too_many_arguments)]
435 fn consume_input_streams(
436 &mut self,
437 input: &Arc<dyn ExecutionPlan>,
438 metrics: &ExecutionPlanMetricsSet,
439 partitioning: &Partitioning,
440 preserve_order: bool,
441 name: &str,
442 context: &Arc<TaskContext>,
443 spill_manager: SpillManager,
444 ) -> Result<&mut ConsumingInputStreamsState> {
445 let streams_and_metrics = match self {
446 RepartitionExecState::NotInitialized => {
447 self.ensure_input_streams_initialized(
448 input,
449 metrics,
450 partitioning.partition_count(),
451 context,
452 )?;
453 let RepartitionExecState::InputStreamsInitialized(value) = self else {
454 return internal_err!(
457 "Programming error: RepartitionExecState must be in the InputStreamsInitialized state after calling RepartitionExecState::ensure_input_streams_initialized"
458 );
459 };
460 value
461 }
462 RepartitionExecState::ConsumingInputStreams(value) => return Ok(value),
463 RepartitionExecState::InputStreamsInitialized(value) => value,
464 };
465
466 let num_input_partitions = streams_and_metrics.len();
467 let num_output_partitions = partitioning.partition_count();
468 let coalesce_batches = !preserve_order && !input.boundedness().is_unbounded();
469
470 let spill_manager = Arc::new(spill_manager);
471
472 let (txs, rxs) = if preserve_order {
473 let (txs_all, rxs_all) =
476 partition_aware_channels(num_input_partitions, num_output_partitions);
477 let txs = transpose(txs_all);
479 let rxs = transpose(rxs_all);
480 (txs, rxs)
481 } else {
482 let (txs, rxs) = channels(num_output_partitions);
484 let txs = txs
486 .into_iter()
487 .map(|item| vec![item; num_input_partitions])
488 .collect::<Vec<_>>();
489 let rxs = rxs.into_iter().map(|item| vec![item]).collect::<Vec<_>>();
490 (txs, rxs)
491 };
492
493 let mut channels = HashMap::with_capacity(txs.len());
494 for (partition, (tx, rx)) in txs.into_iter().zip(rxs).enumerate() {
495 let reservation = Arc::new(
496 MemoryConsumer::new(format!("{name}[{partition}]"))
497 .with_can_spill(true)
498 .register(context.memory_pool()),
499 );
500
501 let max_file_size = context
506 .session_config()
507 .options()
508 .execution
509 .max_spill_file_size_bytes
510 .get();
511
512 let (spill_writers, spill_readers) = if preserve_order {
513 let mut writers = Vec::with_capacity(num_input_partitions);
517 let mut readers = Vec::with_capacity(num_input_partitions);
518 for _ in 0..num_input_partitions {
519 let (writer, reader) = spill_pool::spsc_channel(
520 max_file_size,
521 Arc::clone(&spill_manager),
522 );
523 writers.push(Some(writer));
524 readers.push(reader);
525 }
526 (PartitionSpillWriters::PerInput(writers), readers)
527 } else {
528 let (writer, reader) =
531 spill_pool::mpsc_channel(max_file_size, Arc::clone(&spill_manager));
532 (PartitionSpillWriters::Shared(writer), vec![reader])
533 };
534
535 let shared_coalescer = coalesce_batches.then(|| {
541 SharedCoalescer::new(
542 input.schema(),
543 context.session_config().batch_size(),
544 num_input_partitions,
545 )
546 });
547
548 channels.insert(
549 partition,
550 PartitionChannels {
551 tx,
552 rx,
553 reservation,
554 spill_readers,
555 spill_writers,
556 shared_coalescer,
557 },
558 );
559 }
560
561 let mut spawned_tasks = Vec::with_capacity(num_input_partitions);
563 for (i, (stream, metrics)) in
564 std::mem::take(streams_and_metrics).into_iter().enumerate()
565 {
566 let txs: HashMap<_, _> = channels
567 .iter_mut()
568 .map(|(partition, channels)| {
569 Ok((
573 *partition,
574 OutputChannel {
575 sender: channels.tx[i].clone(),
576 reservation: Arc::clone(&channels.reservation),
577 spill_writer: channels.spill_writers.take_for_input(i)?,
578 shared_coalescer: channels.shared_coalescer.clone(),
579 },
580 ))
581 })
582 .collect::<Result<HashMap<_, _>>>()?;
583
584 let senders: HashMap<_, _> = txs
586 .iter()
587 .map(|(partition, channel)| (*partition, channel.sender.clone()))
588 .collect();
589
590 let input_task = SpawnedTask::spawn(RepartitionExec::pull_from_input(
591 stream,
592 txs,
593 partitioning.clone(),
594 metrics,
595 if preserve_order { 0 } else { i },
597 num_input_partitions,
598 ));
599
600 let wait_for_task =
603 SpawnedTask::spawn(RepartitionExec::wait_for_task(input_task, senders));
604 spawned_tasks.push(wait_for_task);
605 }
606 *self = Self::ConsumingInputStreams(ConsumingInputStreamsState {
607 channels,
608 abort_helper: Arc::new(spawned_tasks),
609 });
610 match self {
611 RepartitionExecState::ConsumingInputStreams(value) => Ok(value),
612 _ => unreachable!(),
613 }
614 }
615}
616
617pub struct BatchPartitioner {
619 state: BatchPartitionerState,
620 timer: metrics::Time,
621}
622
623enum BatchPartitionerState {
624 Hash {
625 exprs: Vec<Arc<dyn PhysicalExpr>>,
626 partition_reducer: StrengthReducedU64,
627 hash_buffer: Vec<u64>,
628 indices: Vec<Vec<u32>>,
629 },
630 RoundRobin {
631 num_partitions: usize,
632 next_idx: usize,
633 },
634 Range {
635 ordering: LexOrdering,
637 sort_options: Vec<SortOptions>,
639 split_points: Vec<SplitPoint>,
641 indices: Vec<Vec<u32>>,
643 partition_buffer: Vec<ScalarValue>,
645 },
646}
647
648pub const REPARTITION_RANDOM_STATE: SeededRandomState = SeededRandomState::with_seed(0);
651
652#[derive(Debug, Hash, PartialEq, Eq)]
657pub struct RangeExpr {
658 on_columns: Vec<PhysicalExprRef>,
659 split_points: Vec<SplitPoint>,
660 sort_options: Vec<SortOptions>,
661}
662
663impl RangeExpr {
664 pub fn try_new(
667 on_columns: Vec<PhysicalExprRef>,
668 range_partitioning: &RangePartitioning,
669 ) -> Result<Self> {
670 let sort_options = range_partitioning
671 .ordering()
672 .iter()
673 .map(|expr| expr.options)
674 .collect();
675 Self::try_new_parts(
676 on_columns,
677 range_partitioning.split_points().to_vec(),
678 sort_options,
679 )
680 }
681
682 fn try_new_parts(
683 on_columns: Vec<PhysicalExprRef>,
684 split_points: Vec<SplitPoint>,
685 sort_options: Vec<SortOptions>,
686 ) -> Result<Self> {
687 assert_or_internal_err!(!on_columns.is_empty(), "RangeExpr requires a key");
688 assert_or_internal_err!(
689 on_columns.len() == sort_options.len(),
690 "RangeExpr key count must match sort options"
691 );
692 validate_range_split_points(&split_points, &sort_options)?;
693 Ok(Self {
694 on_columns,
695 split_points,
696 sort_options,
697 })
698 }
699
700 pub fn on_columns(&self) -> &[PhysicalExprRef] {
702 &self.on_columns
703 }
704
705 pub fn split_points(&self) -> &[SplitPoint] {
707 &self.split_points
708 }
709
710 pub fn sort_options(&self) -> &[SortOptions] {
712 &self.sort_options
713 }
714}
715
716impl Display for RangeExpr {
717 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
718 write!(f, "range_partition")
719 }
720}
721
722impl PhysicalExpr for RangeExpr {
723 fn children(&self) -> Vec<&PhysicalExprRef> {
724 self.on_columns.iter().collect()
725 }
726
727 fn with_new_children(
728 self: Arc<Self>,
729 children: Vec<PhysicalExprRef>,
730 ) -> Result<PhysicalExprRef> {
731 assert_or_internal_err!(
732 children.len() == self.on_columns.len(),
733 "RangeExpr expected {} children, got {}",
734 self.on_columns.len(),
735 children.len()
736 );
737 Ok(Arc::new(Self::try_new_parts(
738 children,
739 self.split_points.clone(),
740 self.sort_options.clone(),
741 )?))
742 }
743
744 fn data_type(&self, _input_schema: &Schema) -> Result<DataType> {
745 Ok(DataType::UInt64)
746 }
747
748 fn nullable(&self, _input_schema: &Schema) -> Result<bool> {
749 Ok(false)
750 }
751
752 fn evaluate(&self, batch: &RecordBatch) -> Result<ColumnarValue> {
753 let arrays = evaluate_expressions_to_arrays(self.on_columns.iter(), batch)?;
754 let mut row_key_buffer = Vec::with_capacity(arrays.len());
755 let mut partition_ids = Vec::with_capacity(batch.num_rows());
756 for row_idx in 0..batch.num_rows() {
757 extract_row_at_idx_to_buf(&arrays, row_idx, &mut row_key_buffer)?;
758 partition_ids.push(range_partition_id(
759 &row_key_buffer,
760 &self.split_points,
761 &self.sort_options,
762 )? as u64);
763 }
764 Ok(ColumnarValue::Array(Arc::new(UInt64Array::from(
765 partition_ids,
766 ))))
767 }
768
769 fn fmt_sql(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
770 write!(f, "range_partition")
771 }
772
773 #[cfg(feature = "proto")]
774 fn try_to_proto(
775 &self,
776 ctx: &datafusion_physical_expr_common::physical_expr::proto_encode::PhysicalExprEncodeCtx<'_>,
777 ) -> Result<Option<protobuf::PhysicalExprNode>> {
778 let sort_exprs = self
781 .on_columns
782 .iter()
783 .zip(&self.sort_options)
784 .map(|(expr, options)| PhysicalSortExpr::new(Arc::clone(expr), *options))
785 .collect::<Vec<_>>();
786 let sort_expr = sort_exprs_try_to_proto(&sort_exprs, ctx)?;
787 let split_point = self
788 .split_points
789 .iter()
790 .map(|split_point| {
791 let value = split_point
792 .values()
793 .iter()
794 .map(|value| value.try_into().map_err(Into::into))
795 .collect::<Result<Vec<_>>>()?;
796 Ok(protobuf::PhysicalRangeSplitPoint { value })
797 })
798 .collect::<Result<Vec<_>>>()?;
799 Ok(Some(protobuf::PhysicalExprNode {
800 expr_id: None,
801 expr_type: Some(protobuf::physical_expr_node::ExprType::RangeExpr(
802 protobuf::PhysicalRangeExprNode {
803 sort_expr,
804 split_point,
805 },
806 )),
807 }))
808 }
809}
810
811#[cfg(feature = "proto")]
812impl RangeExpr {
813 pub fn try_from_proto(
815 node: &protobuf::PhysicalExprNode,
816 ctx: &datafusion_physical_expr_common::physical_expr::proto_decode::PhysicalExprDecodeCtx<'_>,
817 ) -> Result<PhysicalExprRef> {
818 let range_expr = match &node.expr_type {
820 Some(protobuf::physical_expr_node::ExprType::RangeExpr(expr)) => expr,
821 _ => return internal_err!("PhysicalExprNode is not a RangeExpr"),
822 };
823 let sort_exprs = sort_exprs_try_from_proto(&range_expr.sort_expr, ctx)?;
824 let (on_columns, sort_options) = sort_exprs
825 .into_iter()
826 .map(|sort_expr| (sort_expr.expr, sort_expr.options))
827 .unzip();
828 let split_points = range_expr
829 .split_point
830 .iter()
831 .map(|split_point| {
832 let values = split_point
833 .value
834 .iter()
835 .map(|value| ScalarValue::try_from(value).map_err(Into::into))
836 .collect::<Result<Vec<_>>>()?;
837 Ok(SplitPoint::new(values))
838 })
839 .collect::<Result<Vec<_>>>()?;
840 Ok(Arc::new(Self::try_new_parts(
841 on_columns,
842 split_points,
843 sort_options,
844 )?))
845 }
846}
847
848fn range_partition_id(
849 row_key: &[ScalarValue],
850 split_points: &[SplitPoint],
851 sort_options: &[SortOptions],
852) -> Result<usize> {
853 let mut low = 0;
854 let mut high = split_points.len();
855 while low < high {
856 let mid = low + (high - low) / 2;
857 match compare_rows(row_key, split_points[mid].values(), sort_options)? {
858 Ordering::Less => high = mid,
859 Ordering::Equal | Ordering::Greater => low = mid + 1,
860 }
861 }
862 Ok(low)
863}
864
865#[derive(Debug, Clone, Copy)]
874enum StrengthReducedU64 {
875 PowerOfTwo { mask: u64 },
876 Reciprocal { divisor: u64, reciprocal: u128 },
877}
878
879impl StrengthReducedU64 {
880 fn new(divisor: u64) -> Self {
881 debug_assert!(divisor > 0);
882
883 if divisor.is_power_of_two() {
884 Self::PowerOfTwo { mask: divisor - 1 }
885 } else {
886 Self::Reciprocal {
887 divisor,
888 reciprocal: u128::MAX / u128::from(divisor) + 1,
890 }
891 }
892 }
893
894 fn partition_indices(self, hash_buffer: &[u64], indices: &mut [Vec<u32>]) {
895 match self {
896 Self::PowerOfTwo { mask } => {
897 for (index, hash) in hash_buffer.iter().enumerate() {
898 indices[(*hash & mask) as usize].push(index as u32);
899 }
900 }
901 Self::Reciprocal {
902 divisor,
903 reciprocal,
904 } => {
905 for (index, hash) in hash_buffer.iter().enumerate() {
906 let quotient = Self::quotient(*hash, reciprocal);
907 let partition = *hash - quotient * divisor;
908 indices[partition as usize].push(index as u32);
909 }
910 }
911 }
912 }
913
914 #[cfg(test)]
915 fn remainder(self, value: u64) -> u64 {
916 match self {
917 Self::PowerOfTwo { mask } => value & mask,
918 Self::Reciprocal {
919 divisor,
920 reciprocal,
921 } => value - Self::quotient(value, reciprocal) * divisor,
922 }
923 }
924
925 #[inline]
926 fn quotient(value: u64, reciprocal: u128) -> u64 {
927 let reciprocal_low = reciprocal as u64;
928 let reciprocal_high = (reciprocal >> 64) as u64;
929 let low_product = u128::from(value) * u128::from(reciprocal_low);
930 let high_product = u128::from(value) * u128::from(reciprocal_high);
931 let carry = ((high_product & u128::from(u64::MAX)) + (low_product >> 64)) >> 64;
932
933 ((high_product >> 64) + carry) as u64
934 }
935}
936
937impl BatchPartitioner {
938 pub fn new_hash_partitioner(
951 exprs: Vec<Arc<dyn PhysicalExpr>>,
952 num_partitions: usize,
953 timer: metrics::Time,
954 ) -> Result<Self> {
955 if num_partitions == 0 {
956 return internal_err!("Hash repartition requires at least one partition");
957 }
958
959 Ok(Self {
960 state: BatchPartitionerState::Hash {
961 exprs,
962 partition_reducer: StrengthReducedU64::new(num_partitions as u64),
963 hash_buffer: vec![],
964 indices: vec![vec![]; num_partitions],
965 },
966 timer,
967 })
968 }
969
970 pub fn new_round_robin_partitioner(
982 num_partitions: usize,
983 timer: metrics::Time,
984 input_partition: usize,
985 num_input_partitions: usize,
986 ) -> Self {
987 Self {
988 state: BatchPartitionerState::RoundRobin {
989 num_partitions,
990 next_idx: (input_partition * num_partitions) / num_input_partitions,
991 },
992 timer,
993 }
994 }
995
996 pub fn new_range_partitioner(
1002 range_partitioning: &RangePartitioning,
1003 timer: metrics::Time,
1004 ) -> Self {
1005 let ordering = range_partitioning.ordering().clone();
1006 let split_points = range_partitioning.split_points().to_vec();
1007 let num_partitions = range_partitioning.partition_count();
1008 let sort_options: Vec<SortOptions> = ordering.iter().map(|e| e.options).collect();
1009
1010 Self {
1011 state: BatchPartitionerState::Range {
1012 partition_buffer: Vec::with_capacity(ordering.len()),
1013 ordering,
1014 sort_options,
1015 split_points,
1016 indices: vec![vec![]; num_partitions],
1017 },
1018 timer,
1019 }
1020 }
1021
1022 pub fn try_new(
1037 partitioning: Partitioning,
1038 timer: metrics::Time,
1039 input_partition: usize,
1040 num_input_partitions: usize,
1041 ) -> Result<Self> {
1042 match partitioning {
1043 Partitioning::Hash(exprs, num_partitions) => {
1044 Self::new_hash_partitioner(exprs, num_partitions, timer)
1045 }
1046 Partitioning::RoundRobinBatch(num_partitions) => {
1047 Ok(Self::new_round_robin_partitioner(
1048 num_partitions,
1049 timer,
1050 input_partition,
1051 num_input_partitions,
1052 ))
1053 }
1054 Partitioning::Range(range_repartitioning) => {
1055 Ok(Self::new_range_partitioner(&range_repartitioning, timer))
1056 }
1057 other => {
1058 not_impl_err!("Unsupported repartitioning scheme {other:?}")
1059 }
1060 }
1061 }
1062
1063 pub fn partition<F>(&mut self, batch: RecordBatch, mut f: F) -> Result<()>
1073 where
1074 F: FnMut(usize, RecordBatch) -> Result<()>,
1075 {
1076 self.partition_iter(batch)?.try_for_each(|res| match res {
1077 Ok((partition, batch)) => f(partition, batch),
1078 Err(e) => Err(e),
1079 })
1080 }
1081
1082 pub fn partition_iter(
1097 &mut self,
1098 batch: RecordBatch,
1099 ) -> Result<impl Iterator<Item = Result<(usize, RecordBatch)>> + Send + '_> {
1100 let it: Box<dyn Iterator<Item = Result<(usize, RecordBatch)>> + Send> =
1101 match &mut self.state {
1102 BatchPartitionerState::RoundRobin {
1103 num_partitions,
1104 next_idx,
1105 } => {
1106 let idx = *next_idx;
1107 *next_idx = (*next_idx + 1) % *num_partitions;
1108 Box::new(std::iter::once(Ok((idx, batch))))
1109 }
1110 BatchPartitionerState::Hash {
1111 exprs,
1112 partition_reducer,
1113 hash_buffer,
1114 indices,
1115 } => {
1116 let timer = self.timer.timer();
1118
1119 let arrays =
1120 evaluate_expressions_to_arrays(exprs.as_slice(), &batch)?;
1121
1122 hash_buffer.clear();
1123 hash_buffer.resize(batch.num_rows(), 0);
1124
1125 create_hashes(
1126 &arrays,
1127 REPARTITION_RANDOM_STATE.random_state(),
1128 hash_buffer,
1129 )?;
1130
1131 indices.iter_mut().for_each(|v| v.clear());
1132
1133 partition_reducer.partition_indices(hash_buffer, indices);
1134
1135 timer.done();
1137
1138 let partitioned_batches =
1139 Self::partition_grouped_take(&batch, indices, &self.timer)?;
1140
1141 Box::new(partitioned_batches.into_iter())
1142 }
1143 BatchPartitionerState::Range {
1144 ordering,
1145 sort_options,
1146 split_points,
1147 indices,
1148 partition_buffer,
1149 } => {
1150 let timer = self.timer.timer();
1152 if split_points.is_empty() {
1153 timer.done();
1154 Box::new(std::iter::once(Ok((0, batch))))
1155 } else {
1156 let arrays = evaluate_expressions_to_arrays(
1157 ordering.iter().map(|e| &e.expr),
1158 &batch,
1159 )?;
1160
1161 indices.iter_mut().for_each(|v| v.clear());
1162
1163 Self::partition_range_indices(
1164 &arrays,
1165 split_points,
1166 sort_options,
1167 partition_buffer,
1168 indices,
1169 )?;
1170
1171 timer.done();
1173
1174 let partitioned_batches =
1175 Self::partition_grouped_take(&batch, indices, &self.timer)?;
1176
1177 Box::new(partitioned_batches.into_iter())
1178 }
1179 }
1180 };
1181
1182 Ok(it)
1183 }
1184
1185 fn partition_range_indices(
1188 arrays: &[Arc<dyn Array>],
1189 split_points: &[SplitPoint],
1190 sort_options: &[SortOptions],
1191 row_key_buffer: &mut Vec<ScalarValue>,
1192 indices: &mut [Vec<u32>],
1193 ) -> Result<()> {
1194 let num_rows = arrays.first().map(|a| a.len()).unwrap_or(0);
1195 for row_idx in 0..num_rows {
1196 extract_row_at_idx_to_buf(arrays, row_idx, row_key_buffer)?;
1198
1199 let partition =
1200 range_partition_id(row_key_buffer, split_points, sort_options)?;
1201 indices[partition].push(row_idx as u32)
1202 }
1203
1204 Ok(())
1205 }
1206
1207 fn num_partitions(&self) -> usize {
1209 match &self.state {
1210 BatchPartitionerState::RoundRobin { num_partitions, .. } => *num_partitions,
1211 BatchPartitionerState::Hash { indices, .. }
1212 | BatchPartitionerState::Range { indices, .. } => indices.len(),
1213 }
1214 }
1215
1216 fn partition_grouped_take(
1233 batch: &RecordBatch,
1234 indices: &mut [Vec<u32>],
1235 timer: &metrics::Time,
1236 ) -> Result<Vec<Result<(usize, RecordBatch)>>> {
1237 let mut partition_ranges = Vec::with_capacity(indices.len());
1238 let mut reordered_indices = Vec::with_capacity(batch.num_rows());
1239
1240 for (partition, p_indices) in indices.iter_mut().enumerate() {
1241 if p_indices.is_empty() {
1242 continue;
1243 }
1244
1245 let start = reordered_indices.len();
1246 reordered_indices.extend_from_slice(p_indices);
1247 partition_ranges.push((partition, start, p_indices.len()));
1248 p_indices.clear();
1249 }
1250
1251 if reordered_indices.is_empty() {
1252 return Ok(vec![]);
1253 }
1254
1255 let batches = {
1256 let _timer = timer.timer();
1257 let indices_array: PrimitiveArray<UInt32Type> = reordered_indices.into();
1258 let columns = take_arrays(batch.columns(), &indices_array, None)?;
1259
1260 let mut options = RecordBatchOptions::new();
1261 options = options.with_row_count(Some(indices_array.len()));
1262 let reordered_batch =
1263 RecordBatch::try_new_with_options(batch.schema(), columns, &options)?;
1264
1265 partition_ranges
1266 .into_iter()
1267 .map(|(partition, start, len)| {
1268 Ok((partition, reordered_batch.slice(start, len)))
1269 })
1270 .collect()
1271 };
1272
1273 Ok(batches)
1274 }
1275}
1276
1277#[derive(Debug, Clone)]
1409pub struct RepartitionExec {
1410 input: Arc<dyn ExecutionPlan>,
1412 state: Arc<Mutex<RepartitionExecState>>,
1415 metrics: ExecutionPlanMetricsSet,
1417 preserve_order: bool,
1420 cache: Arc<PlanProperties>,
1422}
1423
1424#[derive(Debug, Clone)]
1425struct RepartitionMetrics {
1426 fetch_time: metrics::Time,
1428 repartition_time: metrics::Time,
1430 send_time: Vec<metrics::Time>,
1434}
1435
1436impl RepartitionMetrics {
1437 pub fn new(
1438 input_partition: usize,
1439 num_output_partitions: usize,
1440 metrics: &ExecutionPlanMetricsSet,
1441 ) -> Self {
1442 let fetch_time =
1444 MetricBuilder::new(metrics).subset_time("fetch_time", input_partition);
1445
1446 let repartition_time =
1448 MetricBuilder::new(metrics).subset_time("repartition_time", input_partition);
1449
1450 let send_time = (0..num_output_partitions)
1452 .map(|output_partition| {
1453 let label =
1454 metrics::Label::new("outputPartition", output_partition.to_string());
1455 MetricBuilder::new(metrics)
1456 .with_label(label)
1457 .subset_time("send_time", input_partition)
1458 })
1459 .collect();
1460
1461 Self {
1462 fetch_time,
1463 repartition_time,
1464 send_time,
1465 }
1466 }
1467}
1468
1469impl RepartitionExec {
1470 pub fn input(&self) -> &Arc<dyn ExecutionPlan> {
1472 &self.input
1473 }
1474
1475 pub fn partitioning(&self) -> &Partitioning {
1477 &self.cache.partitioning
1478 }
1479
1480 pub fn preserve_order(&self) -> bool {
1483 self.preserve_order
1484 }
1485
1486 pub fn name(&self) -> &str {
1488 "RepartitionExec"
1489 }
1490}
1491
1492impl DisplayAs for RepartitionExec {
1493 fn fmt_as(&self, t: DisplayFormatType, f: &mut Formatter) -> std::fmt::Result {
1494 let input_partition_count = self.input.output_partitioning().partition_count();
1495 match t {
1496 DisplayFormatType::Default | DisplayFormatType::Verbose => {
1497 write!(
1498 f,
1499 "{}: partitioning={}, input_partitions={}",
1500 self.name(),
1501 self.partitioning(),
1502 input_partition_count,
1503 )?;
1504
1505 if self.preserve_order {
1506 write!(f, ", preserve_order=true")?;
1507 } else if input_partition_count <= 1
1508 && self.input.output_ordering().is_some()
1509 {
1510 write!(f, ", maintains_sort_order=true")?;
1513 }
1514
1515 if let Some(sort_exprs) = self.sort_exprs() {
1516 write!(f, ", sort_exprs={}", sort_exprs.clone())?;
1517 }
1518 Ok(())
1519 }
1520 DisplayFormatType::TreeRender => {
1521 writeln!(f, "partitioning_scheme={}", self.partitioning(),)?;
1522 let output_partition_count = self.partitioning().partition_count();
1523 let input_to_output_partition_str =
1524 format!("{input_partition_count} -> {output_partition_count}");
1525 writeln!(
1526 f,
1527 "partition_count(in->out)={input_to_output_partition_str}"
1528 )?;
1529
1530 if self.preserve_order {
1531 writeln!(f, "preserve_order={}", self.preserve_order)?;
1532 }
1533 Ok(())
1534 }
1535 }
1536 }
1537}
1538
1539impl ExecutionPlan for RepartitionExec {
1540 fn name(&self) -> &'static str {
1541 "RepartitionExec"
1542 }
1543
1544 fn properties(&self) -> &Arc<PlanProperties> {
1546 &self.cache
1547 }
1548
1549 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
1550 vec![&self.input]
1551 }
1552
1553 fn apply_expressions(
1554 &self,
1555 f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
1556 ) -> Result<TreeNodeRecursion> {
1557 match self.partitioning() {
1558 Partitioning::Hash(exprs, _) => crate::apply_expression_roots(exprs, f),
1559 Partitioning::Range(range) => crate::apply_expression_roots(
1560 range.ordering().iter().map(|sort_expr| &sort_expr.expr),
1561 f,
1562 ),
1563 _ => Ok(TreeNodeRecursion::Continue),
1564 }
1565 }
1566
1567 fn replace_children(
1568 self: Arc<Self>,
1569 mut children: Vec<Arc<dyn ExecutionPlan>>,
1570 options: ReplaceChildrenOptions,
1571 ) -> Result<Arc<dyn ExecutionPlan>> {
1572 validate_child_count!(self, children);
1573 match options.children_properties {
1574 ChildrenPropertiesMode::Keep => Ok(Arc::new(Self {
1575 input: children.swap_remove(0),
1576 metrics: ExecutionPlanMetricsSet::new(),
1577 state: Default::default(),
1578 ..Self::clone(&*self)
1579 })),
1580 ChildrenPropertiesMode::Recompute => {
1581 let mut repartition = RepartitionExec::try_new(
1582 children.swap_remove(0),
1583 self.partitioning().clone(),
1584 )?;
1585 if self.preserve_order {
1586 repartition = repartition.with_preserve_order();
1587 }
1588 Ok(Arc::new(repartition))
1589 }
1590 }
1591 }
1592
1593 fn with_new_children(
1594 self: Arc<Self>,
1595 children: Vec<Arc<dyn ExecutionPlan>>,
1596 ) -> Result<Arc<dyn ExecutionPlan>> {
1597 self.replace_children(
1598 children,
1599 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
1600 )
1601 }
1602
1603 fn with_new_children_and_same_properties(
1604 self: Arc<Self>,
1605 children: Vec<Arc<dyn ExecutionPlan>>,
1606 ) -> Result<Arc<dyn ExecutionPlan>> {
1607 self.replace_children(
1608 children,
1609 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Keep),
1610 )
1611 }
1612
1613 fn benefits_from_input_partitioning(&self) -> Vec<bool> {
1614 vec![matches!(self.partitioning(), Partitioning::Hash(_, _))]
1615 }
1616
1617 fn maintains_input_order(&self) -> Vec<bool> {
1618 Self::maintains_input_order_helper(self.input(), self.preserve_order)
1619 }
1620
1621 fn execute(
1622 &self,
1623 partition: usize,
1624 context: Arc<TaskContext>,
1625 ) -> Result<SendableRecordBatchStream> {
1626 trace!(
1627 "Start {}::execute for partition: {}",
1628 self.name(),
1629 partition
1630 );
1631
1632 let spill_metrics = SpillMetrics::new(&self.metrics, partition);
1633
1634 let input = Arc::clone(&self.input);
1635 let partitioning = self.partitioning().clone();
1636 let metrics = self.metrics.clone();
1637 let preserve_order = self.sort_exprs().is_some();
1638 let name = self.name().to_owned();
1639 let schema = self.schema();
1640 let schema_captured = Arc::clone(&schema);
1641
1642 let spill_manager = SpillManager::new(
1643 Arc::clone(&context.runtime_env()),
1644 spill_metrics,
1645 input.schema(),
1646 );
1647
1648 let sort_exprs = self.sort_exprs().cloned();
1650
1651 let state = Arc::clone(&self.state);
1652 if let Some(mut state) = state.try_lock() {
1653 state.ensure_input_streams_initialized(
1654 &input,
1655 &metrics,
1656 partitioning.partition_count(),
1657 &context,
1658 )?;
1659 }
1660
1661 let num_input_partitions = input.output_partitioning().partition_count();
1662
1663 let stream = futures::stream::once(async move {
1664 let (rx, reservation, spill_readers, abort_helper) = {
1666 let mut state = state.lock();
1668 let state = state.consume_input_streams(
1669 &input,
1670 &metrics,
1671 &partitioning,
1672 preserve_order,
1673 &name,
1674 &context,
1675 spill_manager.clone(),
1676 )?;
1677
1678 let PartitionChannels {
1681 rx,
1682 reservation,
1683 spill_readers,
1684 ..
1685 } = state
1686 .channels
1687 .remove(&partition)
1688 .expect("partition not used yet");
1689
1690 (
1691 rx,
1692 reservation,
1693 spill_readers,
1694 Arc::clone(&state.abort_helper),
1695 )
1696 };
1697
1698 trace!(
1699 "Before returning stream in {name}::execute for partition: {partition}"
1700 );
1701
1702 if preserve_order {
1703 let input_streams = rx
1712 .into_iter()
1713 .zip(spill_readers)
1714 .map(|(receiver, spill_stream)| {
1715 Box::pin(PerPartitionStream::new(
1717 Arc::clone(&schema_captured),
1718 receiver,
1719 Arc::clone(&abort_helper),
1720 Arc::clone(&reservation),
1721 spill_stream,
1722 1, None,
1724 )) as SendableRecordBatchStream
1725 })
1726 .collect::<Vec<_>>();
1727 let fetch = None;
1732 let merge_reservation =
1733 MemoryConsumer::new(format!("{name}[Merge {partition}]"))
1734 .register(context.memory_pool());
1735 StreamingMergeBuilder::new()
1736 .with_streams(input_streams)
1737 .with_schema(schema_captured)
1738 .with_expressions(&sort_exprs.unwrap())
1739 .with_metrics(BaselineMetrics::new(&metrics, partition))
1740 .with_batch_size(context.session_config().batch_size())
1741 .with_fetch(fetch)
1742 .with_reservation(merge_reservation)
1743 .with_spill_manager(spill_manager)
1744 .build()
1745 } else {
1746 let spill_stream = spill_readers
1748 .into_iter()
1749 .next()
1750 .expect("at least one spill reader should exist");
1751
1752 Ok(Box::pin(PerPartitionStream::new(
1753 schema_captured,
1754 rx.into_iter()
1755 .next()
1756 .expect("at least one receiver should exist"),
1757 abort_helper,
1758 reservation,
1759 spill_stream,
1760 num_input_partitions,
1761 Some(BaselineMetrics::new(&metrics, partition)),
1762 )) as SendableRecordBatchStream)
1763 }
1764 })
1765 .try_flatten();
1766 let stream = RecordBatchStreamAdapter::new(schema, stream);
1767 Ok(Box::pin(stream))
1768 }
1769
1770 fn metrics(&self) -> Option<MetricsSet> {
1771 Some(self.metrics.clone_inner())
1772 }
1773
1774 fn child_stats_requests(&self, _partition: Option<usize>) -> Vec<ChildStats> {
1775 vec![ChildStats::At(None)]
1776 }
1777
1778 fn statistics_from_inputs(
1779 &self,
1780 input_stats: &[Arc<Statistics>],
1781 args: &StatisticsArgs,
1782 ) -> Result<Arc<Statistics>> {
1783 if args.partition().is_some() {
1784 let partition_count = self.partitioning().partition_count();
1785 assert_or_internal_err!(
1790 partition_count > 0,
1791 "RepartitionExec statistics requested for a partition but the partition count is 0"
1792 );
1793
1794 let mut stats = input_stats[0].as_ref().clone();
1795
1796 stats.num_rows = stats
1798 .num_rows
1799 .get_value()
1800 .map(|rows| Precision::Inexact(rows / partition_count))
1801 .unwrap_or(Precision::Absent);
1802 stats.total_byte_size = stats
1803 .total_byte_size
1804 .get_value()
1805 .map(|bytes| Precision::Inexact(bytes / partition_count))
1806 .unwrap_or(Precision::Absent);
1807
1808 stats.column_statistics = stats
1810 .column_statistics
1811 .iter()
1812 .map(|_| ColumnStatistics::new_unknown())
1813 .collect();
1814
1815 Ok(Arc::new(stats))
1816 } else {
1817 Ok(Arc::clone(&input_stats[0]))
1818 }
1819 }
1820
1821 fn cardinality_effect(&self) -> CardinalityEffect {
1822 CardinalityEffect::Equal
1823 }
1824
1825 fn try_swapping_with_projection(
1826 &self,
1827 projection: &ProjectionExec,
1828 ) -> Result<Option<Arc<dyn ExecutionPlan>>> {
1829 if projection.expr().len() >= projection.input().schema().fields().len() {
1831 return Ok(None);
1832 }
1833
1834 if projection.benefits_from_input_partitioning()[0]
1836 || !all_columns(projection.expr())
1837 {
1838 return Ok(None);
1839 }
1840
1841 let new_projection = make_with_child(projection, self.input())?;
1842
1843 let new_partitioning = match self.partitioning() {
1844 Partitioning::Hash(partitions, size) => {
1845 let mut new_partitions = vec![];
1846 for partition in partitions {
1847 let Some(new_partition) =
1848 update_expr(partition, projection.expr(), false)?
1849 else {
1850 return Ok(None);
1851 };
1852 new_partitions.push(new_partition);
1853 }
1854 Partitioning::Hash(new_partitions, *size)
1855 }
1856 Partitioning::Range(range_partitioning) => {
1857 let mut sort_exprs =
1859 Vec::with_capacity(range_partitioning.ordering().len());
1860 for sort_expr in range_partitioning.ordering() {
1861 let Some(new_expr) =
1862 update_expr(&sort_expr.expr, projection.expr(), false)?
1863 else {
1864 return Ok(None);
1865 };
1866 sort_exprs.push(PhysicalSortExpr::new(new_expr, sort_expr.options));
1867 }
1868
1869 let Some(ordering) = LexOrdering::new(sort_exprs) else {
1870 return internal_err!(
1871 "failed to create LexOrdering for range partitioning"
1872 );
1873 };
1874
1875 Partitioning::Range(RangePartitioning::try_new(
1876 ordering,
1877 range_partitioning.split_points().to_vec(),
1878 )?)
1879 }
1880 others => others.clone(),
1881 };
1882
1883 Ok(Some(Arc::new(RepartitionExec::try_new(
1884 new_projection,
1885 new_partitioning,
1886 )?)))
1887 }
1888
1889 fn gather_filters_for_pushdown(
1890 &self,
1891 _phase: FilterPushdownPhase,
1892 parent_filters: Vec<Arc<dyn PhysicalExpr>>,
1893 _config: &ConfigOptions,
1894 ) -> Result<FilterDescription> {
1895 FilterDescription::from_children(parent_filters, &self.children())
1896 }
1897
1898 fn handle_child_pushdown_result(
1899 &self,
1900 _phase: FilterPushdownPhase,
1901 child_pushdown_result: ChildPushdownResult,
1902 _config: &ConfigOptions,
1903 ) -> Result<FilterPushdownPropagation<Arc<dyn ExecutionPlan>>> {
1904 Ok(FilterPushdownPropagation::if_all(child_pushdown_result))
1905 }
1906
1907 fn try_pushdown_sort(
1908 &self,
1909 order: &[PhysicalSortExpr],
1910 ) -> Result<SortOrderPushdownResult<Arc<dyn ExecutionPlan>>> {
1911 if !self.maintains_input_order()[0] {
1914 return Ok(SortOrderPushdownResult::Unsupported);
1915 }
1916
1917 self.input.try_pushdown_sort(order)?.try_map(|new_input| {
1919 let mut new_repartition =
1920 RepartitionExec::try_new(new_input, self.partitioning().clone())?;
1921 if self.preserve_order {
1922 new_repartition = new_repartition.with_preserve_order();
1923 }
1924 Ok(Arc::new(new_repartition) as Arc<dyn ExecutionPlan>)
1925 })
1926 }
1927
1928 fn repartitioned(
1929 &self,
1930 target_partitions: usize,
1931 _config: &ConfigOptions,
1932 ) -> Result<Option<Arc<dyn ExecutionPlan>>> {
1933 use Partitioning::*;
1934 let mut new_properties = PlanProperties::clone(&self.cache);
1935 new_properties.partitioning = match new_properties.partitioning {
1936 RoundRobinBatch(_) => RoundRobinBatch(target_partitions),
1937 Hash(hash, _) => Hash(hash, target_partitions),
1938 Range(_) => {
1939 return Ok(None);
1941 }
1942 UnknownPartitioning(_) => UnknownPartitioning(target_partitions),
1943 };
1944 Ok(Some(Arc::new(Self {
1945 input: Arc::clone(&self.input),
1946 state: Arc::clone(&self.state),
1947 metrics: self.metrics.clone(),
1948 preserve_order: self.preserve_order,
1949 cache: new_properties.into(),
1950 })))
1951 }
1952
1953 #[cfg(feature = "proto")]
1954 fn try_to_proto(
1955 &self,
1956 ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
1957 ) -> Result<Option<protobuf::PhysicalPlanNode>> {
1958 let input = ctx.encode_child(self.input())?;
1959
1960 let partitioning = self.partitioning().try_to_proto(&ctx.expr_ctx())?;
1961
1962 Ok(Some(protobuf::PhysicalPlanNode {
1963 physical_plan_type: Some(
1964 protobuf::physical_plan_node::PhysicalPlanType::Repartition(Box::new(
1965 protobuf::RepartitionExecNode {
1966 input: Some(Box::new(input)),
1967 partitioning: Some(partitioning),
1968 preserve_order: self.preserve_order(),
1969 },
1970 )),
1971 ),
1972 }))
1973 }
1974}
1975
1976#[cfg(feature = "proto")]
1977impl RepartitionExec {
1978 pub fn try_from_proto(
1980 node: &protobuf::PhysicalPlanNode,
1981 ctx: &crate::proto::ExecutionPlanDecodeCtx<'_>,
1982 ) -> Result<Arc<dyn ExecutionPlan>> {
1983 let repart = crate::expect_plan_variant!(
1984 node,
1985 protobuf::physical_plan_node::PhysicalPlanType::Repartition,
1986 "RepartitionExec",
1987 );
1988 let input = ctx.decode_required_child(
1989 repart.input.as_deref(),
1990 "RepartitionExec",
1991 "input",
1992 )?;
1993 let input_schema = input.schema();
1994
1995 let partitioning = repart
1996 .partitioning
1997 .as_ref()
1998 .map(|partitioning| {
1999 Partitioning::try_from_proto(
2000 partitioning,
2001 &ctx.expr_ctx(input_schema.as_ref()),
2002 )
2003 })
2004 .transpose()?
2005 .flatten()
2006 .ok_or_else(|| {
2007 datafusion_common::internal_datafusion_err!(
2008 "RepartitionExec is missing required field 'partitioning'"
2009 )
2010 })?;
2011
2012 let mut repart_exec = RepartitionExec::try_new(input, partitioning)?;
2013 if repart.preserve_order {
2014 repart_exec = repart_exec.with_preserve_order();
2015 }
2016 Ok(Arc::new(repart_exec))
2017 }
2018}
2019
2020impl RepartitionExec {
2021 pub fn try_new(
2025 input: Arc<dyn ExecutionPlan>,
2026 partitioning: Partitioning,
2027 ) -> Result<Self> {
2028 let preserve_order = false;
2029 let cache = Self::compute_properties(&input, partitioning, preserve_order);
2030 Ok(RepartitionExec {
2031 input,
2032 state: Default::default(),
2033 metrics: ExecutionPlanMetricsSet::new(),
2034 preserve_order,
2035 cache: Arc::new(cache),
2036 })
2037 }
2038
2039 fn maintains_input_order_helper(
2040 input: &Arc<dyn ExecutionPlan>,
2041 preserve_order: bool,
2042 ) -> Vec<bool> {
2043 vec![preserve_order || input.output_partitioning().partition_count() <= 1]
2045 }
2046
2047 fn eq_properties_helper(
2048 input: &Arc<dyn ExecutionPlan>,
2049 preserve_order: bool,
2050 ) -> EquivalenceProperties {
2051 let mut eq_properties = input.equivalence_properties().clone();
2053 if !Self::maintains_input_order_helper(input, preserve_order)[0] {
2055 eq_properties.clear_orderings();
2056 }
2057 if input.output_partitioning().partition_count() > 1 {
2060 eq_properties.clear_per_partition_constants();
2061 }
2062 eq_properties
2063 }
2064
2065 fn compute_properties(
2067 input: &Arc<dyn ExecutionPlan>,
2068 partitioning: Partitioning,
2069 preserve_order: bool,
2070 ) -> PlanProperties {
2071 PlanProperties::new(
2072 Self::eq_properties_helper(input, preserve_order),
2073 partitioning,
2074 input.pipeline_behavior(),
2075 input.boundedness(),
2076 )
2077 .with_scheduling_type(SchedulingType::Cooperative)
2078 .with_evaluation_type(EvaluationType::Eager)
2079 }
2080
2081 pub fn with_preserve_order(mut self) -> Self {
2089 self.preserve_order =
2090 self.input.output_ordering().is_some() &&
2092 self.input.output_partitioning().partition_count() > 1;
2095 let eq_properties = Self::eq_properties_helper(&self.input, self.preserve_order);
2096 Arc::make_mut(&mut self.cache).set_eq_properties(eq_properties);
2097 self
2098 }
2099
2100 fn sort_exprs(&self) -> Option<&LexOrdering> {
2102 if self.preserve_order {
2103 self.input.output_ordering()
2104 } else {
2105 None
2106 }
2107 }
2108
2109 async fn pull_from_input(
2114 mut stream: SendableRecordBatchStream,
2115 mut output_channels: HashMap<usize, OutputChannel>,
2116 partitioning: Partitioning,
2117 metrics: RepartitionMetrics,
2118 input_partition: usize,
2119 num_input_partitions: usize,
2120 ) -> Result<()> {
2121 let mut partitioner = BatchPartitioner::try_new(
2122 partitioning,
2123 metrics.repartition_time.clone(),
2124 input_partition,
2125 num_input_partitions,
2126 )?;
2127
2128 let mut batches_until_yield = partitioner.num_partitions();
2130 while !output_channels.is_empty() {
2131 let timer = metrics.fetch_time.timer();
2133 let result = stream.next().await;
2134 timer.done();
2135
2136 let batch = match result {
2138 Some(result) => result?,
2139 None => break,
2140 };
2141
2142 if batch.num_rows() == 0 {
2144 continue;
2145 }
2146
2147 for res in partitioner.partition_iter(batch)? {
2148 let (partition, batch) = res?;
2149
2150 let timer = metrics.send_time[partition].timer();
2151 if let Some(output_channel) = output_channels.get_mut(&partition) {
2153 for batch in output_channel.coalesce(batch)? {
2154 if output_channel.send(batch).await.is_err() {
2155 output_channels.remove(&partition);
2158 break;
2159 }
2160 }
2161 }
2162 timer.done();
2163 }
2164
2165 if batches_until_yield == 0 {
2182 tokio::task::yield_now().await;
2183 batches_until_yield = partitioner.num_partitions();
2184 } else {
2185 batches_until_yield -= 1;
2186 }
2187 }
2188
2189 for (_, output_channel) in output_channels.drain() {
2194 output_channel.finalize().await?;
2195 }
2196
2197 Ok(())
2200 }
2201
2202 async fn wait_for_task(
2208 input_task: SpawnedTask<Result<()>>,
2209 txs: HashMap<usize, DistributionSender<MaybeBatch>>,
2210 ) {
2211 match input_task.join().await {
2215 Err(e) => {
2217 let e = Arc::new(e);
2218
2219 for (_, tx) in txs {
2220 let err = Err(DataFusionError::Context(
2221 "Join Error".to_string(),
2222 Box::new(DataFusionError::External(Box::new(Arc::clone(&e)))),
2223 ));
2224 tx.send(Some(err)).await.ok();
2225 }
2226 }
2227 Ok(Err(e)) => {
2229 let e = Arc::new(e);
2231
2232 for (_, tx) in txs {
2233 let err = Err(DataFusionError::from(&e));
2235 tx.send(Some(err)).await.ok();
2236 }
2237 }
2238 Ok(Ok(())) => {
2240 for (_partition, tx) in txs {
2242 tx.send(None).await.ok();
2243 }
2244 }
2245 }
2246 }
2247}
2248
2249#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2292enum StreamState {
2293 ReadingMemory,
2295 ReadingSpilled,
2298}
2299
2300struct PerPartitionStream {
2303 schema: SchemaRef,
2305
2306 receiver: DistributionReceiver<MaybeBatch>,
2308
2309 _drop_helper: Arc<Vec<SpawnedTask<()>>>,
2311
2312 reservation: SharedMemoryReservation,
2314
2315 spill_stream: SendableRecordBatchStream,
2317
2318 state: StreamState,
2320
2321 remaining_partitions: usize,
2325
2326 baseline_metrics: Option<BaselineMetrics>,
2328}
2329
2330impl PerPartitionStream {
2331 fn new(
2332 schema: SchemaRef,
2333 receiver: DistributionReceiver<MaybeBatch>,
2334 drop_helper: Arc<Vec<SpawnedTask<()>>>,
2335 reservation: SharedMemoryReservation,
2336 spill_stream: SendableRecordBatchStream,
2337 num_input_partitions: usize,
2338 baseline_metrics: Option<BaselineMetrics>,
2339 ) -> Self {
2340 Self {
2341 schema,
2342 receiver,
2343 _drop_helper: drop_helper,
2344 reservation,
2345 spill_stream,
2346 state: StreamState::ReadingMemory,
2347 remaining_partitions: num_input_partitions,
2348 baseline_metrics,
2349 }
2350 }
2351
2352 fn poll_next_inner(
2353 self: &mut Pin<&mut Self>,
2354 cx: &mut Context<'_>,
2355 ) -> Poll<Option<Result<RecordBatch>>> {
2356 use futures::StreamExt;
2357 let elapsed = self
2358 .baseline_metrics
2359 .as_ref()
2360 .map(|m| m.elapsed_compute().clone());
2361 let _timer = elapsed.as_ref().map(|t| t.timer());
2362
2363 loop {
2364 match self.state {
2365 StreamState::ReadingMemory => {
2366 let value = match self.receiver.recv().poll_unpin(cx) {
2368 Poll::Ready(v) => v,
2369 Poll::Pending => {
2370 return Poll::Pending;
2372 }
2373 };
2374
2375 match value {
2376 Some(Some(v)) => match v {
2377 Ok(RepartitionBatch::Memory(batch)) => {
2378 self.reservation.shrink(batch.get_array_memory_size());
2380 return Poll::Ready(Some(Ok(batch)));
2381 }
2382 Ok(RepartitionBatch::Spilled) => {
2383 self.state = StreamState::ReadingSpilled;
2387 continue;
2388 }
2389 Err(e) => {
2390 return Poll::Ready(Some(Err(e)));
2391 }
2392 },
2393 Some(None) => {
2394 self.remaining_partitions -= 1;
2396 if self.remaining_partitions == 0 {
2397 return Poll::Ready(None);
2399 }
2400 continue;
2402 }
2403 None => {
2404 return Poll::Ready(None);
2406 }
2407 }
2408 }
2409 StreamState::ReadingSpilled => {
2410 match self.spill_stream.poll_next_unpin(cx) {
2412 Poll::Ready(Some(Ok(batch))) => {
2413 self.state = StreamState::ReadingMemory;
2414 return Poll::Ready(Some(Ok(batch)));
2415 }
2416 Poll::Ready(Some(Err(e))) => {
2417 return Poll::Ready(Some(Err(e)));
2418 }
2419 Poll::Ready(None) => {
2420 let spill_schema = self.spill_stream.schema();
2423 self.spill_stream =
2424 Box::pin(EmptyRecordBatchStream::new(spill_schema));
2425 self.state = StreamState::ReadingMemory;
2426 }
2427 Poll::Pending => {
2428 return Poll::Pending;
2431 }
2432 }
2433 }
2434 }
2435 }
2436 }
2437}
2438
2439impl Stream for PerPartitionStream {
2440 type Item = Result<RecordBatch>;
2441
2442 fn poll_next(
2443 mut self: Pin<&mut Self>,
2444 cx: &mut Context<'_>,
2445 ) -> Poll<Option<Self::Item>> {
2446 let poll = self.poll_next_inner(cx);
2447 if let Some(metrics) = &self.baseline_metrics {
2448 metrics.record_poll(poll)
2449 } else {
2450 poll
2451 }
2452 }
2453}
2454
2455impl RecordBatchStream for PerPartitionStream {
2456 fn schema(&self) -> SchemaRef {
2458 Arc::clone(&self.schema)
2459 }
2460}
2461
2462#[cfg(test)]
2463mod tests {
2464 use std::collections::HashSet;
2465
2466 use super::*;
2467 use crate::empty::EmptyExec;
2468 use crate::projection::ProjectionExpr;
2469 use crate::streaming::{PartitionStream, StreamingTableExec};
2470 use crate::test::TestMemoryExec;
2471 use crate::{
2472 test::{
2473 assert_is_pending,
2474 exec::{
2475 BarrierExec, BlockingExec, ErrorExec, MockExec,
2476 assert_strong_count_converges_to_zero,
2477 },
2478 },
2479 {collect, expressions::col},
2480 };
2481
2482 use arrow::array::{ArrayRef, StringArray, UInt32Array};
2483 use arrow::datatypes::{DataType, Field, Schema};
2484 use datafusion_common::ScalarValue;
2485 use datafusion_common::cast::{as_string_array, as_uint32_array};
2486 use datafusion_common::exec_err;
2487 use datafusion_common::test_util::batches_to_sort_string;
2488 use datafusion_common_runtime::JoinSet;
2489 use datafusion_execution::config::SessionConfig;
2490 use datafusion_execution::runtime_env::RuntimeEnvBuilder;
2491 use datafusion_physical_expr::{PhysicalSortExpr, RangePartitioning, SplitPoint};
2492 use insta::assert_snapshot;
2493
2494 #[derive(Debug)]
2495 struct UnboundedTestPartition {
2496 schema: SchemaRef,
2497 batch: RecordBatch,
2498 }
2499
2500 impl PartitionStream for UnboundedTestPartition {
2501 fn schema(&self) -> &SchemaRef {
2502 &self.schema
2503 }
2504
2505 fn execute(&self, _ctx: Arc<TaskContext>) -> SendableRecordBatchStream {
2506 let stream = futures::stream::iter([Ok(self.batch.clone())])
2507 .chain(futures::stream::pending());
2508 Box::pin(RecordBatchStreamAdapter::new(
2509 Arc::clone(&self.schema),
2510 stream,
2511 ))
2512 }
2513 }
2514
2515 #[test]
2516 fn range_expr_preserves_duplicate_remapped_children() -> Result<()> {
2517 let schema = Arc::new(Schema::new(vec![
2518 Field::new("a", DataType::UInt32, false),
2519 Field::new("b", DataType::UInt32, false),
2520 ]));
2521 let sort_options = [SortOptions::new(false, false), SortOptions::new(true, true)];
2522 let split_points = vec![SplitPoint::new(vec![
2523 ScalarValue::UInt32(Some(10)),
2524 ScalarValue::UInt32(Some(20)),
2525 ])];
2526 let range_partitioning = RangePartitioning::try_new(
2527 [
2528 PhysicalSortExpr::new(col("a", &schema)?, sort_options[0]),
2529 PhysicalSortExpr::new(col("b", &schema)?, sort_options[1]),
2530 ]
2531 .into(),
2532 split_points.clone(),
2533 )?;
2534 let expr = Arc::new(RangeExpr::try_new(
2535 vec![col("a", &schema)?, col("b", &schema)?],
2536 &range_partitioning,
2537 )?);
2538 let remapped = col("a", &schema)?;
2539 let rewritten =
2540 expr.with_new_children(vec![Arc::clone(&remapped), Arc::clone(&remapped)])?;
2541
2542 let rewritten = rewritten
2543 .downcast_ref::<RangeExpr>()
2544 .expect("rewritten expression should remain a RangeExpr");
2545 assert_eq!(rewritten.on_columns().len(), 2);
2546 assert!(Arc::ptr_eq(
2547 &rewritten.on_columns()[0],
2548 &rewritten.on_columns()[1]
2549 ));
2550 assert_eq!(rewritten.sort_options(), sort_options);
2551 assert_eq!(rewritten.split_points(), split_points);
2552
2553 Ok(())
2554 }
2555
2556 #[test]
2557 fn strength_reduced_u64_remainder_matches_modulo() {
2558 let divisors = [
2559 1,
2560 2,
2561 3,
2562 4,
2563 5,
2564 7,
2565 8,
2566 10,
2567 16,
2568 31,
2569 32,
2570 63,
2571 64,
2572 65,
2573 97,
2574 u64::from(u32::MAX),
2575 u64::from(u32::MAX) + 1,
2576 1_u64 << 32,
2577 (1_u64 << 63) - 1,
2578 1_u64 << 63,
2579 u64::MAX - 1,
2580 u64::MAX,
2581 ];
2582 let values = [
2583 0,
2584 1,
2585 2,
2586 3,
2587 4,
2588 5,
2589 31,
2590 32,
2591 33,
2592 63,
2593 64,
2594 65,
2595 u64::from(u32::MAX) - 1,
2596 u64::from(u32::MAX),
2597 u64::from(u32::MAX) + 1,
2598 (1_u64 << 32) - 1,
2599 1_u64 << 32,
2600 (1_u64 << 32) + 1,
2601 (1_u64 << 63) - 1,
2602 1_u64 << 63,
2603 (1_u64 << 63) + 1,
2604 u64::MAX - 1,
2605 u64::MAX,
2606 ];
2607
2608 for divisor in divisors {
2609 let reducer = StrengthReducedU64::new(divisor);
2610 for value in values {
2611 assert_eq!(
2612 reducer.remainder(value),
2613 value % divisor,
2614 "value={value} divisor={divisor}"
2615 );
2616 }
2617
2618 let mut value = 0x1234_5678_9abc_def0 ^ divisor;
2619 for _ in 0..10_000 {
2620 value = value
2621 .wrapping_mul(6_364_136_223_846_793_005)
2622 .wrapping_add(1_442_695_040_888_963_407);
2623 assert_eq!(
2624 reducer.remainder(value),
2625 value % divisor,
2626 "value={value} divisor={divisor}"
2627 );
2628 }
2629 }
2630 }
2631
2632 #[test]
2633 fn hash_partitioner_requires_nonzero_partitions() {
2634 let metrics = ExecutionPlanMetricsSet::new();
2635 let timer = MetricBuilder::new(&metrics).subset_time("test", 0);
2636
2637 let err = BatchPartitioner::new_hash_partitioner(vec![], 0, timer)
2638 .err()
2639 .expect("zero hash partitions should fail")
2640 .to_string();
2641
2642 assert!(
2643 err.contains("Hash repartition requires at least one partition"),
2644 "actual: {err}"
2645 );
2646 }
2647
2648 #[tokio::test]
2649 async fn one_to_many_round_robin() -> Result<()> {
2650 let schema = test_schema(false);
2652 let partition = create_vec_batches(50);
2653 let partitions = vec![partition];
2654
2655 let output_partitions =
2657 repartition(&schema, partitions, Partitioning::RoundRobinBatch(4)).await?;
2658
2659 assert_eq!(4, output_partitions.len());
2660 for partition in &output_partitions {
2661 assert_eq!(1, partition.len());
2662 }
2663 assert_eq!(13 * 8, output_partitions[0][0].num_rows());
2664 assert_eq!(13 * 8, output_partitions[1][0].num_rows());
2665 assert_eq!(12 * 8, output_partitions[2][0].num_rows());
2666 assert_eq!(12 * 8, output_partitions[3][0].num_rows());
2667
2668 Ok(())
2669 }
2670
2671 #[tokio::test]
2672 async fn many_to_one_round_robin() -> Result<()> {
2673 let schema = test_schema(false);
2675 let partition = create_vec_batches(50);
2676 let partitions = vec![partition.clone(), partition.clone(), partition.clone()];
2677
2678 let output_partitions =
2680 repartition(&schema, partitions, Partitioning::RoundRobinBatch(1)).await?;
2681
2682 assert_eq!(1, output_partitions.len());
2683 assert_eq!(150 * 8, output_partitions[0][0].num_rows());
2684
2685 Ok(())
2686 }
2687
2688 #[tokio::test]
2689 async fn many_to_many_round_robin() -> Result<()> {
2690 let schema = test_schema(false);
2692 let partition = create_vec_batches(50);
2693 let partitions = vec![partition.clone(), partition.clone(), partition.clone()];
2694
2695 let output_partitions =
2697 repartition(&schema, partitions, Partitioning::RoundRobinBatch(5)).await?;
2698
2699 let total_rows_per_partition = 8 * 50 * 3 / 5;
2700 assert_eq!(5, output_partitions.len());
2701 for partition in output_partitions {
2702 assert_eq!(1, partition.len());
2703 assert_eq!(total_rows_per_partition, partition[0].num_rows());
2704 }
2705
2706 Ok(())
2707 }
2708
2709 #[tokio::test]
2710 async fn many_to_many_hash_partition() -> Result<()> {
2711 let schema = test_schema(false);
2713 let partition = create_vec_batches(50);
2714 let partitions = vec![partition.clone(), partition.clone(), partition.clone()];
2715
2716 let output_partitions = repartition(
2717 &schema,
2718 partitions,
2719 Partitioning::Hash(vec![col("c0", &schema)?], 8),
2720 )
2721 .await?;
2722
2723 let total_rows: usize = output_partitions
2724 .iter()
2725 .map(|x| x.iter().map(|x| x.num_rows()).sum::<usize>())
2726 .sum();
2727
2728 assert_eq!(8, output_partitions.len());
2729 assert_eq!(total_rows, 8 * 50 * 3);
2730
2731 Ok(())
2732 }
2733
2734 #[tokio::test]
2735 async fn many_to_many_range_partition() -> Result<()> {
2736 let schema = test_schema(false);
2737 let partition = create_vec_batches(50);
2738 let partitions = vec![partition.clone(), partition.clone(), partition.clone()];
2739
2740 let partitioning =
2743 u32_range_partitioning(&schema, SortOptions::default(), vec![3, 6])?;
2744
2745 let output_partitions = repartition(&schema, partitions, partitioning).await?;
2746
2747 assert_eq!(3, output_partitions.len());
2748 assert_eq!(300, partition_row_count(&output_partitions[0]));
2749 assert_eq!(450, partition_row_count(&output_partitions[1]));
2750 assert_eq!(450, partition_row_count(&output_partitions[2]));
2751 assert_eq!(
2752 collect_partition_u32_values(&output_partitions[0])
2753 .into_iter()
2754 .flatten()
2755 .collect::<HashSet<_>>(),
2756 HashSet::from([1, 2])
2757 );
2758 assert_eq!(
2759 collect_partition_u32_values(&output_partitions[1])
2760 .into_iter()
2761 .flatten()
2762 .collect::<HashSet<_>>(),
2763 HashSet::from([3, 4, 5])
2764 );
2765 assert_eq!(
2766 collect_partition_u32_values(&output_partitions[2])
2767 .into_iter()
2768 .flatten()
2769 .collect::<HashSet<_>>(),
2770 HashSet::from([6, 7, 8])
2771 );
2772
2773 Ok(())
2774 }
2775
2776 #[tokio::test]
2777 async fn range_repartition_routes_compound_keys() -> Result<()> {
2778 let schema = Arc::new(Schema::new(vec![
2779 Field::new("a", DataType::UInt32, false),
2780 Field::new("b", DataType::UInt32, false),
2781 ]));
2782 let batch = RecordBatch::try_new(
2783 Arc::clone(&schema),
2784 vec![
2785 Arc::new(UInt32Array::from(vec![5, 10, 10, 10, 10, 15])),
2786 Arc::new(UInt32Array::from(vec![1, 1, 3, 5, 7, 0])),
2787 ],
2788 )?;
2789 let partitioning = Partitioning::Range(RangePartitioning::try_new(
2790 [
2791 PhysicalSortExpr::new(col("a", &schema)?, SortOptions::default()),
2792 PhysicalSortExpr::new(col("b", &schema)?, SortOptions::default()),
2793 ]
2794 .into(),
2795 vec![
2796 SplitPoint::new(vec![
2797 ScalarValue::UInt32(Some(10)),
2798 ScalarValue::UInt32(Some(1)),
2799 ]),
2800 SplitPoint::new(vec![
2801 ScalarValue::UInt32(Some(10)),
2802 ScalarValue::UInt32(Some(5)),
2803 ]),
2804 ],
2805 )?);
2806
2807 let output_partitions =
2808 repartition(&schema, vec![vec![batch]], partitioning).await?;
2809
2810 assert_eq!(3, output_partitions.len());
2811 assert_eq!(
2812 vec![(5, 1)],
2813 collect_partition_u32_pairs(&output_partitions[0])
2814 );
2815 assert_eq!(
2816 vec![(10, 1), (10, 3)],
2817 collect_partition_u32_pairs(&output_partitions[1])
2818 );
2819 assert_eq!(
2820 vec![(10, 5), (10, 7), (15, 0)],
2821 collect_partition_u32_pairs(&output_partitions[2])
2822 );
2823
2824 Ok(())
2825 }
2826
2827 #[tokio::test]
2828 async fn range_repartition_routes_nulls_asc_nulls_last() -> Result<()> {
2829 let schema = test_schema(true);
2830 let batch = RecordBatch::try_new(
2831 Arc::clone(&schema),
2832 vec![Arc::new(UInt32Array::from(vec![
2833 None,
2834 Some(5),
2835 Some(10),
2836 Some(15),
2837 ]))],
2838 )?;
2839 let partitioning =
2840 u32_range_partitioning(&schema, SortOptions::new(false, false), vec![10])?;
2841
2842 let output_partitions =
2843 repartition(&schema, vec![vec![batch]], partitioning).await?;
2844
2845 assert_eq!(2, output_partitions.len());
2846 assert_eq!(
2847 vec![Some(5)],
2848 collect_partition_u32_values(&output_partitions[0])
2849 );
2850 assert_eq!(
2851 vec![None, Some(10), Some(15)],
2852 collect_partition_u32_values(&output_partitions[1])
2853 );
2854
2855 Ok(())
2856 }
2857
2858 #[tokio::test]
2859 async fn range_repartition_routes_nulls_asc_nulls_first() -> Result<()> {
2860 let schema = test_schema(true);
2861 let batch = RecordBatch::try_new(
2862 Arc::clone(&schema),
2863 vec![Arc::new(UInt32Array::from(vec![
2864 None,
2865 Some(5),
2866 Some(10),
2867 Some(15),
2868 ]))],
2869 )?;
2870 let partitioning =
2871 u32_range_partitioning(&schema, SortOptions::new(false, true), vec![10])?;
2872
2873 let output_partitions =
2874 repartition(&schema, vec![vec![batch]], partitioning).await?;
2875
2876 assert_eq!(2, output_partitions.len());
2877 assert_eq!(
2878 vec![None, Some(5)],
2879 collect_partition_u32_values(&output_partitions[0])
2880 );
2881 assert_eq!(
2882 vec![Some(10), Some(15)],
2883 collect_partition_u32_values(&output_partitions[1])
2884 );
2885
2886 Ok(())
2887 }
2888
2889 #[tokio::test]
2890 async fn range_repartition_routes_rows_asc() -> Result<()> {
2891 let schema = test_schema(false);
2892 let batch = RecordBatch::try_new(
2893 Arc::clone(&schema),
2894 vec![Arc::new(UInt32Array::from(vec![5, 10, 15, 25]))],
2895 )?;
2896 let partitioning =
2897 u32_range_partitioning(&schema, SortOptions::default(), vec![10, 20])?;
2898
2899 let output_partitions =
2900 repartition(&schema, vec![vec![batch]], partitioning).await?;
2901
2902 assert_eq!(3, output_partitions.len());
2903 assert_eq!(
2904 vec![Some(5)],
2905 collect_partition_u32_values(&output_partitions[0])
2906 );
2907 assert_eq!(
2908 vec![Some(10), Some(15)],
2909 collect_partition_u32_values(&output_partitions[1])
2910 );
2911 assert_eq!(
2912 vec![Some(25)],
2913 collect_partition_u32_values(&output_partitions[2])
2914 );
2915
2916 Ok(())
2917 }
2918
2919 #[tokio::test]
2920 async fn range_repartition_routes_rows_desc() -> Result<()> {
2921 let schema = test_schema(false);
2922 let batch = RecordBatch::try_new(
2923 Arc::clone(&schema),
2924 vec![Arc::new(UInt32Array::from(vec![5, 10, 15, 20, 25]))],
2925 )?;
2926 let partitioning =
2927 u32_range_partitioning(&schema, SortOptions::new(true, false), vec![20, 10])?;
2928
2929 let output_partitions =
2930 repartition(&schema, vec![vec![batch]], partitioning).await?;
2931
2932 assert_eq!(3, output_partitions.len());
2933 assert_eq!(
2934 vec![Some(25)],
2935 collect_partition_u32_values(&output_partitions[0])
2936 );
2937 assert_eq!(
2938 vec![Some(15), Some(20)],
2939 collect_partition_u32_values(&output_partitions[1])
2940 );
2941 assert_eq!(
2942 vec![Some(5), Some(10)],
2943 collect_partition_u32_values(&output_partitions[2])
2944 );
2945
2946 Ok(())
2947 }
2948
2949 #[tokio::test]
2950 async fn range_repartition_routes_string_rows() -> Result<()> {
2951 let task_ctx = Arc::new(TaskContext::default());
2952 let batch = RecordBatch::try_from_iter(vec![(
2953 "my_awesome_field",
2954 Arc::new(StringArray::from(vec!["bar", "baz", "foo", "qux"])) as ArrayRef,
2955 )])?;
2956
2957 let schema = batch.schema();
2958 let expr = col("my_awesome_field", &schema)?;
2959 let input = MockExec::new(vec![Ok(batch)], Arc::clone(&schema));
2960 let partitioning = Partitioning::Range(RangePartitioning::try_new(
2961 [PhysicalSortExpr::new_default(expr)].into(),
2962 vec![SplitPoint::new(vec![ScalarValue::Utf8(Some(
2963 "foo".to_string(),
2964 ))])],
2965 )?);
2966 let exec = RepartitionExec::try_new(Arc::new(input), partitioning)?;
2967
2968 let mut partition_0 = Vec::new();
2969 let mut stream = exec.execute(0, Arc::clone(&task_ctx))?;
2970 while let Some(result) = stream.next().await {
2971 partition_0.push(result?);
2972 }
2973
2974 let mut partition_1 = Vec::new();
2975 let mut stream = exec.execute(1, task_ctx)?;
2976 while let Some(result) = stream.next().await {
2977 partition_1.push(result?);
2978 }
2979
2980 assert_eq!(
2981 vec!["bar", "baz"],
2982 collect_partition_string_values(&partition_0)
2983 );
2984 assert_eq!(
2985 vec!["foo", "qux"],
2986 collect_partition_string_values(&partition_1)
2987 );
2988
2989 Ok(())
2990 }
2991
2992 #[test]
2993 fn range_repartition_swaps_with_projection_rewrites_key_index() -> Result<()> {
2994 let schema = Arc::new(Schema::new(vec![
2997 Field::new("id", DataType::UInt32, false),
2998 Field::new("region", DataType::Utf8, false),
2999 Field::new("payload", DataType::UInt32, false),
3000 ]));
3001 let repartition = Arc::new(RepartitionExec::try_new(
3002 Arc::new(EmptyExec::new(Arc::clone(&schema))),
3003 range_partitioning_on_columns(&schema, &["id"], vec![vec![10]])?,
3004 )?);
3005
3006 let projection =
3007 projection_on_columns(&(Arc::clone(&repartition) as _), &["payload", "id"])?;
3008
3009 let swapped = repartition
3010 .try_swapping_with_projection(&projection)?
3011 .expect("swap should succeed when projection keeps the range key");
3012 let swapped_repartition = swapped
3013 .downcast_ref::<RepartitionExec>()
3014 .expect("top node should be RepartitionExec");
3015
3016 assert!(swapped_repartition.input().is::<ProjectionExec>());
3017 let range = expect_range_partitioning(swapped_repartition.partitioning());
3018 assert_eq!(range.ordering()[0].to_string(), "id@1 ASC");
3019 assert_eq!(
3020 range.split_points(),
3021 &[SplitPoint::new(vec![ScalarValue::UInt32(Some(10))])]
3022 );
3023
3024 Ok(())
3025 }
3026
3027 #[test]
3028 fn range_repartition_does_not_swap_when_projection_drops_key() -> Result<()> {
3029 let schema = Arc::new(Schema::new(vec![
3031 Field::new("id", DataType::UInt32, false),
3032 Field::new("payload", DataType::UInt32, false),
3033 ]));
3034 let repartition = Arc::new(RepartitionExec::try_new(
3035 Arc::new(EmptyExec::new(Arc::clone(&schema))),
3036 range_partitioning_on_columns(&schema, &["id"], vec![vec![10]])?,
3037 )?);
3038 let projection =
3039 projection_on_columns(&(Arc::clone(&repartition) as _), &["payload"])?;
3040 assert!(
3041 repartition
3042 .try_swapping_with_projection(&projection)?
3043 .is_none()
3044 );
3045
3046 let schema = Arc::new(Schema::new(vec![
3048 Field::new("a", DataType::UInt32, false),
3049 Field::new("b", DataType::UInt32, false),
3050 Field::new("c", DataType::UInt32, false),
3051 ]));
3052 let repartition = Arc::new(RepartitionExec::try_new(
3053 Arc::new(EmptyExec::new(Arc::clone(&schema))),
3054 range_partitioning_on_columns(&schema, &["a", "b"], vec![vec![10, 1]])?,
3055 )?);
3056 let projection =
3057 projection_on_columns(&(Arc::clone(&repartition) as _), &["a", "c"])?;
3058 assert!(
3059 repartition
3060 .try_swapping_with_projection(&projection)?
3061 .is_none()
3062 );
3063
3064 Ok(())
3065 }
3066
3067 #[test]
3068 fn range_repartition_try_pushdown_sort_when_maintains_order() -> Result<()> {
3069 let schema =
3070 Arc::new(Schema::new(vec![Field::new("id", DataType::UInt32, false)]));
3071 let ordering = LexOrdering::new([PhysicalSortExpr::new(
3072 col("id", &schema)?,
3073 SortOptions::default(),
3074 )])
3075 .expect("ordering must not be empty");
3076
3077 let source = Arc::new(ExactSortPushdownExec::new(
3079 Arc::clone(&schema),
3080 2,
3081 ordering.clone(),
3082 ));
3083 let repartition = Arc::new(
3084 RepartitionExec::try_new(
3085 source,
3086 range_partitioning_on_columns(&schema, &["id"], vec![vec![10]])?,
3087 )?
3088 .with_preserve_order(),
3089 );
3090 assert!(repartition.maintains_input_order()[0]);
3091
3092 match repartition.try_pushdown_sort(ordering.as_ref())? {
3093 SortOrderPushdownResult::Exact { inner } => {
3094 let pushed = inner
3095 .downcast_ref::<RepartitionExec>()
3096 .expect("pushdown should keep RepartitionExec");
3097
3098 assert!(pushed.preserve_order());
3099 assert!(pushed.maintains_input_order()[0]);
3100
3101 let range = expect_range_partitioning(pushed.partitioning());
3102 assert_eq!(range.ordering()[0].to_string(), "id@0 ASC");
3103 assert_eq!(
3104 inner.properties().output_ordering().map(|o| o.to_string()),
3105 Some(ordering.to_string()),
3106 "pushed repartition output ordering should match the requested sort"
3107 );
3108 }
3109 other => panic!("expected Exact sort pushdown, got {other:?}"),
3110 }
3111
3112 Ok(())
3113 }
3114
3115 #[test]
3116 fn range_repartition_try_pushdown_sort_unsupported_without_order_maintenance()
3117 -> Result<()> {
3118 let schema =
3119 Arc::new(Schema::new(vec![Field::new("id", DataType::UInt32, false)]));
3120 let ordering = LexOrdering::new([PhysicalSortExpr::new(
3121 col("id", &schema)?,
3122 SortOptions::default(),
3123 )])
3124 .expect("ordering must not be empty");
3125
3126 let source = Arc::new(ExactSortPushdownExec::new(
3128 Arc::clone(&schema),
3129 2,
3130 ordering.clone(),
3131 ));
3132 let repartition = Arc::new(RepartitionExec::try_new(
3133 source,
3134 range_partitioning_on_columns(&schema, &["id"], vec![vec![10]])?,
3135 )?);
3136 assert!(!repartition.maintains_input_order()[0]);
3137
3138 assert!(matches!(
3139 repartition.try_pushdown_sort(ordering.as_ref())?,
3140 SortOrderPushdownResult::Unsupported
3141 ));
3142
3143 Ok(())
3144 }
3145
3146 fn range_partitioning_on_columns(
3147 schema: &SchemaRef,
3148 key_columns: &[&str],
3149 split_points: Vec<Vec<u32>>,
3150 ) -> Result<Partitioning> {
3151 let Some(ordering) = LexOrdering::new(
3152 key_columns
3153 .iter()
3154 .map(|name| {
3155 Ok(PhysicalSortExpr::new(
3156 col(name, schema)?,
3157 SortOptions::default(),
3158 ))
3159 })
3160 .collect::<Result<Vec<_>>>()?,
3161 ) else {
3162 return exec_err!("range ordering must not be empty");
3163 };
3164 Ok(Partitioning::Range(RangePartitioning::try_new(
3165 ordering,
3166 split_points
3167 .into_iter()
3168 .map(|values| {
3169 SplitPoint::new(
3170 values
3171 .into_iter()
3172 .map(|value| ScalarValue::UInt32(Some(value)))
3173 .collect(),
3174 )
3175 })
3176 .collect(),
3177 )?))
3178 }
3179
3180 fn projection_on_columns(
3181 input: &Arc<dyn ExecutionPlan>,
3182 names: &[&str],
3183 ) -> Result<ProjectionExec> {
3184 let exprs = names
3185 .iter()
3186 .map(|name| {
3187 Ok(ProjectionExpr {
3188 expr: col(name, &input.schema())?,
3189 alias: (*name).to_string(),
3190 })
3191 })
3192 .collect::<Result<Vec<_>>>()?;
3193 ProjectionExec::try_new(exprs, Arc::clone(input))
3194 }
3195
3196 fn expect_range_partitioning(partitioning: &Partitioning) -> &RangePartitioning {
3197 match partitioning {
3198 Partitioning::Range(range) => range,
3199 other => panic!("expected Range partitioning, got {other:?}"),
3200 }
3201 }
3202
3203 #[derive(Debug, Clone)]
3205 struct ExactSortPushdownExec {
3206 cache: Arc<PlanProperties>,
3207 }
3208
3209 impl ExactSortPushdownExec {
3210 fn new(schema: SchemaRef, num_partitions: usize, ordering: LexOrdering) -> Self {
3211 use crate::execution_plan::{Boundedness, EmissionType};
3212 Self {
3213 cache: Arc::new(PlanProperties::new(
3214 EquivalenceProperties::new_with_orderings(schema, [ordering]),
3215 Partitioning::UnknownPartitioning(num_partitions),
3216 EmissionType::Incremental,
3217 Boundedness::Bounded,
3218 )),
3219 }
3220 }
3221 }
3222
3223 impl DisplayAs for ExactSortPushdownExec {
3224 fn fmt_as(&self, _t: DisplayFormatType, f: &mut Formatter) -> std::fmt::Result {
3225 write!(f, "ExactSortPushdownExec")
3226 }
3227 }
3228
3229 impl ExecutionPlan for ExactSortPushdownExec {
3230 fn name(&self) -> &str {
3231 "ExactSortPushdownExec"
3232 }
3233
3234 fn properties(&self) -> &Arc<PlanProperties> {
3235 &self.cache
3236 }
3237
3238 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
3239 vec![]
3240 }
3241
3242 fn apply_expressions(
3243 &self,
3244 _f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
3245 ) -> Result<TreeNodeRecursion> {
3246 Ok(TreeNodeRecursion::Continue)
3247 }
3248
3249 fn replace_children(
3250 self: Arc<Self>,
3251 _: Vec<Arc<dyn ExecutionPlan>>,
3252 _: ReplaceChildrenOptions,
3253 ) -> Result<Arc<dyn ExecutionPlan>> {
3254 Ok(self)
3255 }
3256
3257 fn with_new_children(
3258 self: Arc<Self>,
3259 children: Vec<Arc<dyn ExecutionPlan>>,
3260 ) -> Result<Arc<dyn ExecutionPlan>> {
3261 self.replace_children(
3262 children,
3263 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
3264 )
3265 }
3266
3267 fn execute(
3268 &self,
3269 _partition: usize,
3270 _context: Arc<TaskContext>,
3271 ) -> Result<SendableRecordBatchStream> {
3272 Ok(Box::pin(EmptyRecordBatchStream::new(self.schema())))
3273 }
3274
3275 fn try_pushdown_sort(
3276 &self,
3277 _order: &[PhysicalSortExpr],
3278 ) -> Result<SortOrderPushdownResult<Arc<dyn ExecutionPlan>>> {
3279 Ok(SortOrderPushdownResult::Exact {
3280 inner: Arc::new(self.clone()),
3281 })
3282 }
3283 }
3284
3285 #[tokio::test]
3286 async fn test_repartition_with_coalescing() -> Result<()> {
3287 let schema = test_schema(false);
3288 let partition = create_vec_batches(50);
3290 let partitions = vec![partition.clone(), partition.clone()];
3291 let partitioning = Partitioning::RoundRobinBatch(1);
3292
3293 let session_config = SessionConfig::new().with_batch_size(200);
3294 let task_ctx = TaskContext::default().with_session_config(session_config);
3295 let task_ctx = Arc::new(task_ctx);
3296
3297 let exec = TestMemoryExec::try_new_exec(&partitions, Arc::clone(&schema), None)?;
3299 let exec = RepartitionExec::try_new(exec, partitioning)?;
3300
3301 for i in 0..exec.partitioning().partition_count() {
3302 let mut stream = exec.execute(i, Arc::clone(&task_ctx))?;
3303 while let Some(result) = stream.next().await {
3304 let batch = result?;
3305 assert_eq!(200, batch.num_rows());
3306 }
3307 }
3308 Ok(())
3309 }
3310
3311 #[tokio::test]
3312 async fn unbounded_input_emits_before_batch_size() -> Result<()> {
3313 let schema = test_schema(false);
3314 let batch = create_batch();
3315 let source = Arc::new(StreamingTableExec::try_new(
3316 Arc::clone(&schema),
3317 vec![Arc::new(UnboundedTestPartition {
3318 schema: Arc::clone(&schema),
3319 batch: batch.clone(),
3320 })],
3321 None,
3322 vec![],
3323 true,
3324 None,
3325 )?);
3326 let exec = RepartitionExec::try_new(source, Partitioning::RoundRobinBatch(1))?;
3327 let session_config = SessionConfig::new().with_batch_size(batch.num_rows() * 2);
3328 let task_ctx =
3329 Arc::new(TaskContext::default().with_session_config(session_config));
3330
3331 let mut stream = exec.execute(0, task_ctx)?;
3332 let output =
3333 tokio::time::timeout(std::time::Duration::from_secs(5), stream.next())
3334 .await
3335 .expect("unbounded repartition withheld a partial batch")
3336 .expect("unbounded input ended unexpectedly")?;
3337
3338 assert_eq!(batch, output);
3339 Ok(())
3340 }
3341
3342 fn test_schema(nullable: bool) -> Arc<Schema> {
3343 Arc::new(Schema::new(vec![Field::new(
3344 "c0",
3345 DataType::UInt32,
3346 nullable,
3347 )]))
3348 }
3349
3350 fn u32_range_partitioning(
3351 schema: &SchemaRef,
3352 sort_options: SortOptions,
3353 split_values: Vec<u32>,
3354 ) -> Result<Partitioning> {
3355 let expr = col("c0", schema)?;
3356 Ok(Partitioning::Range(RangePartitioning::try_new(
3357 [PhysicalSortExpr::new(expr, sort_options)].into(),
3358 split_values
3359 .into_iter()
3360 .map(|value| SplitPoint::new(vec![ScalarValue::UInt32(Some(value))]))
3361 .collect(),
3362 )?))
3363 }
3364
3365 fn partition_row_count(batches: &[RecordBatch]) -> usize {
3366 batches.iter().map(|batch| batch.num_rows()).sum()
3367 }
3368
3369 fn collect_partition_u32_values(batches: &[RecordBatch]) -> Vec<Option<u32>> {
3370 batches
3371 .iter()
3372 .flat_map(|batch| {
3373 let array =
3374 as_uint32_array(batch.column(0)).expect("expected UInt32 column");
3375 (0..array.len())
3376 .map(|idx| {
3377 if array.is_null(idx) {
3378 None
3379 } else {
3380 Some(array.value(idx))
3381 }
3382 })
3383 .collect::<Vec<_>>()
3384 })
3385 .collect()
3386 }
3387
3388 fn collect_partition_u32_pairs(batches: &[RecordBatch]) -> Vec<(u32, u32)> {
3389 batches
3390 .iter()
3391 .flat_map(|batch| {
3392 let a = as_uint32_array(batch.column(0)).expect("expected UInt32 column");
3393 let b = as_uint32_array(batch.column(1)).expect("expected UInt32 column");
3394 (0..a.len())
3395 .map(|idx| (a.value(idx), b.value(idx)))
3396 .collect::<Vec<_>>()
3397 })
3398 .collect()
3399 }
3400
3401 fn collect_partition_string_values(batches: &[RecordBatch]) -> Vec<&str> {
3402 batches
3403 .iter()
3404 .flat_map(|batch| {
3405 let array =
3406 as_string_array(batch.column(0)).expect("expected Utf8 column");
3407 (0..array.len())
3408 .map(|idx| array.value(idx))
3409 .collect::<Vec<_>>()
3410 })
3411 .collect()
3412 }
3413
3414 async fn repartition(
3415 schema: &SchemaRef,
3416 input_partitions: Vec<Vec<RecordBatch>>,
3417 partitioning: Partitioning,
3418 ) -> Result<Vec<Vec<RecordBatch>>> {
3419 let task_ctx = Arc::new(TaskContext::default());
3420 let exec =
3422 TestMemoryExec::try_new_exec(&input_partitions, Arc::clone(schema), None)?;
3423 let exec = RepartitionExec::try_new(exec, partitioning)?;
3424
3425 let mut output_partitions = vec![];
3427 for i in 0..exec.partitioning().partition_count() {
3428 let mut stream = exec.execute(i, Arc::clone(&task_ctx))?;
3430 let mut batches = vec![];
3431 while let Some(result) = stream.next().await {
3432 batches.push(result?);
3433 }
3434 output_partitions.push(batches);
3435 }
3436 Ok(output_partitions)
3437 }
3438
3439 #[tokio::test]
3440 async fn many_to_many_round_robin_within_tokio_task() -> Result<()> {
3441 let handle: SpawnedTask<Result<Vec<Vec<RecordBatch>>>> =
3442 SpawnedTask::spawn(async move {
3443 let schema = test_schema(false);
3445 let partition = create_vec_batches(50);
3446 let partitions =
3447 vec![partition.clone(), partition.clone(), partition.clone()];
3448
3449 repartition(&schema, partitions, Partitioning::RoundRobinBatch(5)).await
3451 });
3452
3453 let output_partitions = handle.join().await.unwrap().unwrap();
3454
3455 let total_rows_per_partition = 8 * 50 * 3 / 5;
3456 assert_eq!(5, output_partitions.len());
3457 for partition in output_partitions {
3458 assert_eq!(1, partition.len());
3459 assert_eq!(total_rows_per_partition, partition[0].num_rows());
3460 }
3461
3462 Ok(())
3463 }
3464
3465 #[tokio::test]
3466 async fn unsupported_partitioning() {
3467 let task_ctx = Arc::new(TaskContext::default());
3468 let batch = RecordBatch::try_from_iter(vec![(
3470 "my_awesome_field",
3471 Arc::new(StringArray::from(vec!["foo", "bar"])) as ArrayRef,
3472 )])
3473 .unwrap();
3474
3475 let schema = batch.schema();
3476 let input = MockExec::new(vec![Ok(batch)], schema);
3477 let partitioning = Partitioning::UnknownPartitioning(1);
3481 let exec = RepartitionExec::try_new(Arc::new(input), partitioning).unwrap();
3482 let output_stream = exec.execute(0, task_ctx).unwrap();
3483
3484 let result_string = crate::common::collect(output_stream)
3486 .await
3487 .unwrap_err()
3488 .to_string();
3489 assert!(
3490 result_string
3491 .contains("Unsupported repartitioning scheme UnknownPartitioning(1)"),
3492 "actual: {result_string}"
3493 );
3494 }
3495
3496 #[tokio::test]
3497 async fn error_for_input_exec() {
3498 let task_ctx = Arc::new(TaskContext::default());
3502 let input = ErrorExec::new();
3503 let partitioning = Partitioning::RoundRobinBatch(1);
3504 let exec = RepartitionExec::try_new(Arc::new(input), partitioning).unwrap();
3505
3506 let result_string = exec.execute(0, task_ctx).err().unwrap().to_string();
3508
3509 assert!(
3510 result_string.contains("ErrorExec, unsurprisingly, errored in partition 0"),
3511 "actual: {result_string}"
3512 );
3513 }
3514
3515 #[tokio::test]
3516 async fn repartition_with_error_in_stream() {
3517 let task_ctx = Arc::new(TaskContext::default());
3518 let batch = RecordBatch::try_from_iter(vec![(
3519 "my_awesome_field",
3520 Arc::new(StringArray::from(vec!["foo", "bar"])) as ArrayRef,
3521 )])
3522 .unwrap();
3523
3524 let err = exec_err!("bad data error");
3527
3528 let schema = batch.schema();
3529 let input = MockExec::new(vec![Ok(batch), err], schema);
3530 let partitioning = Partitioning::RoundRobinBatch(1);
3531 let exec = RepartitionExec::try_new(Arc::new(input), partitioning).unwrap();
3532
3533 let output_stream = exec.execute(0, task_ctx).unwrap();
3536
3537 let result_string = crate::common::collect(output_stream)
3539 .await
3540 .unwrap_err()
3541 .to_string();
3542 assert!(
3543 result_string.contains("bad data error"),
3544 "actual: {result_string}"
3545 );
3546 }
3547
3548 #[tokio::test]
3549 async fn repartition_with_delayed_stream() {
3550 let task_ctx = Arc::new(TaskContext::default());
3551 let batch1 = RecordBatch::try_from_iter(vec![(
3552 "my_awesome_field",
3553 Arc::new(StringArray::from(vec!["foo", "bar"])) as ArrayRef,
3554 )])
3555 .unwrap();
3556
3557 let batch2 = RecordBatch::try_from_iter(vec![(
3558 "my_awesome_field",
3559 Arc::new(StringArray::from(vec!["frob", "baz"])) as ArrayRef,
3560 )])
3561 .unwrap();
3562
3563 let schema = batch1.schema();
3566 let expected_batches = vec![batch1.clone(), batch2.clone()];
3567 let input = MockExec::new(vec![Ok(batch1), Ok(batch2)], schema);
3568 let partitioning = Partitioning::RoundRobinBatch(1);
3569
3570 let exec = RepartitionExec::try_new(Arc::new(input), partitioning).unwrap();
3571
3572 assert_snapshot!(batches_to_sort_string(&expected_batches), @r"
3573 +------------------+
3574 | my_awesome_field |
3575 +------------------+
3576 | bar |
3577 | baz |
3578 | foo |
3579 | frob |
3580 +------------------+
3581 ");
3582
3583 let output_stream = exec.execute(0, task_ctx).unwrap();
3584 let batches = crate::common::collect(output_stream).await.unwrap();
3585
3586 assert_snapshot!(batches_to_sort_string(&batches), @r"
3587 +------------------+
3588 | my_awesome_field |
3589 +------------------+
3590 | bar |
3591 | baz |
3592 | foo |
3593 | frob |
3594 +------------------+
3595 ");
3596 }
3597
3598 #[tokio::test]
3599 async fn robin_repartition_with_dropping_output_stream() {
3600 let task_ctx = Arc::new(TaskContext::default());
3601 let partitioning = Partitioning::RoundRobinBatch(2);
3602 let input = Arc::new(make_barrier_exec());
3605
3606 let exec = RepartitionExec::try_new(
3608 Arc::clone(&input) as Arc<dyn ExecutionPlan>,
3609 partitioning,
3610 )
3611 .unwrap();
3612
3613 let output_stream0 = exec.execute(0, Arc::clone(&task_ctx)).unwrap();
3614 let output_stream1 = exec.execute(1, Arc::clone(&task_ctx)).unwrap();
3615
3616 drop(output_stream0);
3619
3620 let mut background_task = JoinSet::new();
3622 background_task.spawn(async move {
3623 input.wait().await;
3624 });
3625
3626 let batches = crate::common::collect(output_stream1).await.unwrap();
3628
3629 assert_snapshot!(batches_to_sort_string(&batches), @r"
3630 +------------------+
3631 | my_awesome_field |
3632 +------------------+
3633 | baz |
3634 | frob |
3635 | gar |
3636 | goo |
3637 +------------------+
3638 ");
3639 }
3640
3641 #[tokio::test]
3642 async fn hash_repartition_with_dropping_output_stream() {
3646 let task_ctx = Arc::new(TaskContext::default());
3647 let partitioning = Partitioning::Hash(
3648 vec![Arc::new(crate::expressions::Column::new(
3649 "my_awesome_field",
3650 0,
3651 ))],
3652 2,
3653 );
3654
3655 let input = Arc::new(make_barrier_exec());
3657 let exec = RepartitionExec::try_new(
3658 Arc::clone(&input) as Arc<dyn ExecutionPlan>,
3659 partitioning.clone(),
3660 )
3661 .unwrap();
3662 let output_stream1 = exec.execute(1, Arc::clone(&task_ctx)).unwrap();
3663 let mut background_task = JoinSet::new();
3664 background_task.spawn(async move {
3665 input.wait().await;
3666 });
3667 let batches_without_drop = crate::common::collect(output_stream1).await.unwrap();
3668
3669 let items_vec = str_batches_to_vec(&batches_without_drop);
3671 let items_set: HashSet<&str> = items_vec.iter().copied().collect();
3672 assert_eq!(items_vec.len(), items_set.len());
3673 let source_str_set: HashSet<&str> =
3674 ["foo", "bar", "frob", "baz", "goo", "gar", "grob", "gaz"]
3675 .iter()
3676 .copied()
3677 .collect();
3678 assert_eq!(items_set.difference(&source_str_set).count(), 0);
3679
3680 let input = Arc::new(make_barrier_exec());
3682 let exec = RepartitionExec::try_new(
3683 Arc::clone(&input) as Arc<dyn ExecutionPlan>,
3684 partitioning,
3685 )
3686 .unwrap();
3687 let output_stream0 = exec.execute(0, Arc::clone(&task_ctx)).unwrap();
3688 let output_stream1 = exec.execute(1, Arc::clone(&task_ctx)).unwrap();
3689 drop(output_stream0);
3692 let mut background_task = JoinSet::new();
3693 background_task.spawn(async move {
3694 input.wait().await;
3695 });
3696 let batches_with_drop = crate::common::collect(output_stream1).await.unwrap();
3697
3698 let items_vec_with_drop = str_batches_to_vec(&batches_with_drop);
3699 let items_set_with_drop: HashSet<&str> =
3700 items_vec_with_drop.iter().copied().collect();
3701 assert_eq!(
3702 items_set_with_drop.symmetric_difference(&items_set).count(),
3703 0
3704 );
3705 }
3706
3707 fn str_batches_to_vec(batches: &[RecordBatch]) -> Vec<&str> {
3708 batches
3709 .iter()
3710 .flat_map(|batch| {
3711 assert_eq!(batch.columns().len(), 1);
3712 let string_array = as_string_array(batch.column(0))
3713 .expect("Unexpected type for repartitioned batch");
3714
3715 string_array
3716 .iter()
3717 .map(|v| v.expect("Unexpected null"))
3718 .collect::<Vec<_>>()
3719 })
3720 .collect::<Vec<_>>()
3721 }
3722
3723 fn make_barrier_exec() -> BarrierExec {
3725 let batch1 = RecordBatch::try_from_iter(vec![(
3726 "my_awesome_field",
3727 Arc::new(StringArray::from(vec!["foo", "bar"])) as ArrayRef,
3728 )])
3729 .unwrap();
3730
3731 let batch2 = RecordBatch::try_from_iter(vec![(
3732 "my_awesome_field",
3733 Arc::new(StringArray::from(vec!["frob", "baz"])) as ArrayRef,
3734 )])
3735 .unwrap();
3736
3737 let batch3 = RecordBatch::try_from_iter(vec![(
3738 "my_awesome_field",
3739 Arc::new(StringArray::from(vec!["goo", "gar"])) as ArrayRef,
3740 )])
3741 .unwrap();
3742
3743 let batch4 = RecordBatch::try_from_iter(vec![(
3744 "my_awesome_field",
3745 Arc::new(StringArray::from(vec!["grob", "gaz"])) as ArrayRef,
3746 )])
3747 .unwrap();
3748
3749 let schema = batch1.schema();
3752 BarrierExec::new(vec![vec![batch1, batch2], vec![batch3, batch4]], schema)
3753 }
3754
3755 #[tokio::test]
3756 async fn test_drop_cancel() -> Result<()> {
3757 let task_ctx = Arc::new(TaskContext::default());
3758 let schema =
3759 Arc::new(Schema::new(vec![Field::new("a", DataType::Float32, true)]));
3760
3761 let blocking_exec = Arc::new(BlockingExec::new(Arc::clone(&schema), 2));
3762 let refs = blocking_exec.refs();
3763 let repartition_exec = Arc::new(RepartitionExec::try_new(
3764 blocking_exec,
3765 Partitioning::UnknownPartitioning(1),
3766 )?);
3767
3768 let fut = collect(repartition_exec, task_ctx);
3769 let mut fut = fut.boxed();
3770
3771 assert_is_pending(&mut fut);
3772 drop(fut);
3773 assert_strong_count_converges_to_zero(refs).await;
3774
3775 Ok(())
3776 }
3777
3778 #[tokio::test]
3779 async fn hash_repartition_avoid_empty_batch() -> Result<()> {
3780 let task_ctx = Arc::new(TaskContext::default());
3781 let batch = RecordBatch::try_from_iter(vec![(
3782 "a",
3783 Arc::new(StringArray::from(vec!["foo"])) as ArrayRef,
3784 )])
3785 .unwrap();
3786 let partitioning = Partitioning::Hash(
3787 vec![Arc::new(crate::expressions::Column::new("a", 0))],
3788 2,
3789 );
3790 let schema = batch.schema();
3791 let input = MockExec::new(vec![Ok(batch)], schema);
3792 let exec = RepartitionExec::try_new(Arc::new(input), partitioning).unwrap();
3793 let output_stream0 = exec.execute(0, Arc::clone(&task_ctx)).unwrap();
3794 let batch0 = crate::common::collect(output_stream0).await.unwrap();
3795 let output_stream1 = exec.execute(1, Arc::clone(&task_ctx)).unwrap();
3796 let batch1 = crate::common::collect(output_stream1).await.unwrap();
3797 assert!(batch0.is_empty() || batch1.is_empty());
3798 Ok(())
3799 }
3800
3801 #[tokio::test]
3802 async fn repartition_with_spilling() -> Result<()> {
3803 let schema = test_schema(false);
3805 let partition = create_vec_batches(50);
3806 let input_partitions = vec![partition];
3807 let partitioning = Partitioning::RoundRobinBatch(4);
3808
3809 let runtime = RuntimeEnvBuilder::default()
3811 .with_memory_limit(1, 1.0)
3812 .build_arc()?;
3813
3814 let task_ctx = TaskContext::default().with_runtime(runtime);
3815 let task_ctx = Arc::new(task_ctx);
3816
3817 let exec =
3819 TestMemoryExec::try_new_exec(&input_partitions, Arc::clone(&schema), None)?;
3820 let exec = RepartitionExec::try_new(exec, partitioning)?;
3821
3822 let mut total_rows = 0;
3824 for i in 0..exec.partitioning().partition_count() {
3825 let mut stream = exec.execute(i, Arc::clone(&task_ctx))?;
3826 while let Some(result) = stream.next().await {
3827 let batch = result?;
3828 total_rows += batch.num_rows();
3829 }
3830 }
3831
3832 assert_eq!(total_rows, 50 * 8);
3834
3835 let metrics = exec.metrics().unwrap();
3837 assert!(
3838 metrics.spill_count().unwrap() > 0,
3839 "Expected spill_count > 0, but got {:?}",
3840 metrics.spill_count()
3841 );
3842 println!("Spilled {} times", metrics.spill_count().unwrap());
3843 assert!(
3844 metrics.spilled_bytes().unwrap() > 0,
3845 "Expected spilled_bytes > 0, but got {:?}",
3846 metrics.spilled_bytes()
3847 );
3848 println!(
3849 "Spilled {} bytes in {} spills",
3850 metrics.spilled_bytes().unwrap(),
3851 metrics.spill_count().unwrap()
3852 );
3853 assert!(
3854 metrics.spilled_rows().unwrap() > 0,
3855 "Expected spilled_rows > 0, but got {:?}",
3856 metrics.spilled_rows()
3857 );
3858 println!("Spilled {} rows", metrics.spilled_rows().unwrap());
3859
3860 Ok(())
3861 }
3862
3863 #[tokio::test]
3864 async fn repartition_with_partial_spilling() -> Result<()> {
3865 let schema = test_schema(false);
3867 let partition = create_vec_batches(50);
3868 let input_partitions = vec![partition];
3869 let partitioning = Partitioning::RoundRobinBatch(4);
3870
3871 let runtime = RuntimeEnvBuilder::default()
3875 .with_memory_limit(8 * 1024, 1.0)
3876 .build_arc()?;
3877
3878 let session_config = SessionConfig::new().with_batch_size(1024);
3879 let task_ctx = TaskContext::default()
3880 .with_runtime(runtime)
3881 .with_session_config(session_config);
3882 let task_ctx = Arc::new(task_ctx);
3883
3884 let exec =
3886 TestMemoryExec::try_new_exec(&input_partitions, Arc::clone(&schema), None)?;
3887 let exec = RepartitionExec::try_new(exec, partitioning)?;
3888
3889 let mut total_rows = 0;
3891 for i in 0..exec.partitioning().partition_count() {
3892 let mut stream = exec.execute(i, Arc::clone(&task_ctx))?;
3893 while let Some(result) = stream.next().await {
3894 let batch = result?;
3895 total_rows += batch.num_rows();
3896 }
3897 }
3898
3899 assert_eq!(total_rows, 50 * 8);
3901
3902 let metrics = exec.metrics().unwrap();
3904 let spill_count = metrics.spill_count().unwrap();
3905 let spilled_rows = metrics.spilled_rows().unwrap();
3906 let spilled_bytes = metrics.spilled_bytes().unwrap();
3907
3908 assert!(
3909 spill_count > 0,
3910 "Expected some spilling to occur, but got spill_count={spill_count}"
3911 );
3912 assert!(
3913 spilled_rows > 0 && spilled_rows < total_rows,
3914 "Expected partial spilling (0 < spilled_rows < {total_rows}), but got spilled_rows={spilled_rows}"
3915 );
3916 assert!(
3917 spilled_bytes > 0,
3918 "Expected some bytes to be spilled, but got spilled_bytes={spilled_bytes}"
3919 );
3920
3921 println!(
3922 "Partial spilling: spilled {} out of {} rows ({:.1}%) in {} spills, {} bytes",
3923 spilled_rows,
3924 total_rows,
3925 (spilled_rows as f64 / total_rows as f64) * 100.0,
3926 spill_count,
3927 spilled_bytes
3928 );
3929
3930 Ok(())
3931 }
3932
3933 #[tokio::test]
3934 async fn repartition_without_spilling() -> Result<()> {
3935 let schema = test_schema(false);
3937 let partition = create_vec_batches(50);
3938 let input_partitions = vec![partition];
3939 let partitioning = Partitioning::RoundRobinBatch(4);
3940
3941 let runtime = RuntimeEnvBuilder::default()
3943 .with_memory_limit(10 * 1024 * 1024, 1.0) .build_arc()?;
3945
3946 let task_ctx = TaskContext::default().with_runtime(runtime);
3947 let task_ctx = Arc::new(task_ctx);
3948
3949 let exec =
3951 TestMemoryExec::try_new_exec(&input_partitions, Arc::clone(&schema), None)?;
3952 let exec = RepartitionExec::try_new(exec, partitioning)?;
3953
3954 let mut total_rows = 0;
3956 for i in 0..exec.partitioning().partition_count() {
3957 let mut stream = exec.execute(i, Arc::clone(&task_ctx))?;
3958 while let Some(result) = stream.next().await {
3959 let batch = result?;
3960 total_rows += batch.num_rows();
3961 }
3962 }
3963
3964 assert_eq!(total_rows, 50 * 8);
3966
3967 let metrics = exec.metrics().unwrap();
3969 assert_eq!(
3970 metrics.spill_count(),
3971 Some(0),
3972 "Expected no spilling, but got spill_count={:?}",
3973 metrics.spill_count()
3974 );
3975 assert_eq!(
3976 metrics.spilled_bytes(),
3977 Some(0),
3978 "Expected no bytes spilled, but got spilled_bytes={:?}",
3979 metrics.spilled_bytes()
3980 );
3981 assert_eq!(
3982 metrics.spilled_rows(),
3983 Some(0),
3984 "Expected no rows spilled, but got spilled_rows={:?}",
3985 metrics.spilled_rows()
3986 );
3987
3988 println!("No spilling occurred - all data processed in memory");
3989
3990 Ok(())
3991 }
3992
3993 #[tokio::test]
3994 async fn oom() -> Result<()> {
3995 use datafusion_execution::disk_manager::{DiskManagerBuilder, DiskManagerMode};
3996
3997 let schema = test_schema(false);
3999 let partition = create_vec_batches(50);
4000 let input_partitions = vec![partition];
4001 let partitioning = Partitioning::RoundRobinBatch(4);
4002
4003 let runtime = RuntimeEnvBuilder::default()
4005 .with_memory_limit(1, 1.0)
4006 .with_disk_manager_builder(
4007 DiskManagerBuilder::default().with_mode(DiskManagerMode::Disabled),
4008 )
4009 .build_arc()?;
4010
4011 let task_ctx = TaskContext::default().with_runtime(runtime);
4012 let task_ctx = Arc::new(task_ctx);
4013
4014 let exec =
4016 TestMemoryExec::try_new_exec(&input_partitions, Arc::clone(&schema), None)?;
4017 let exec = RepartitionExec::try_new(exec, partitioning)?;
4018
4019 for i in 0..exec.partitioning().partition_count() {
4021 let mut stream = exec.execute(i, Arc::clone(&task_ctx))?;
4022 let err = stream.next().await.unwrap().unwrap_err();
4023 let err = err.find_root();
4024 assert!(
4025 matches!(err, DataFusionError::ResourcesExhausted(_)),
4026 "Wrong error type: {err}",
4027 );
4028 }
4029
4030 Ok(())
4031 }
4032
4033 fn create_vec_batches(n: usize) -> Vec<RecordBatch> {
4035 let batch = create_batch();
4036 std::iter::repeat_n(batch, n).collect()
4037 }
4038
4039 fn create_batch() -> RecordBatch {
4041 let schema = test_schema(false);
4042 RecordBatch::try_new(
4043 schema,
4044 vec![Arc::new(UInt32Array::from(vec![1, 2, 3, 4, 5, 6, 7, 8]))],
4045 )
4046 .unwrap()
4047 }
4048
4049 fn create_ordered_batches(num_batches: usize) -> Vec<RecordBatch> {
4051 let schema = test_schema(false);
4052 (0..num_batches)
4053 .map(|i| {
4054 let start = (i * 8) as u32;
4055 RecordBatch::try_new(
4056 Arc::clone(&schema),
4057 vec![Arc::new(UInt32Array::from(
4058 (start..start + 8).collect::<Vec<_>>(),
4059 ))],
4060 )
4061 .unwrap()
4062 })
4063 .collect()
4064 }
4065
4066 #[tokio::test]
4067 async fn test_repartition_ordering_with_spilling() -> Result<()> {
4068 let schema = test_schema(false);
4073 let partition = create_ordered_batches(20);
4076 let input_partitions = vec![partition];
4077
4078 let partitioning = Partitioning::RoundRobinBatch(2);
4080
4081 let runtime = RuntimeEnvBuilder::default()
4083 .with_memory_limit(1, 1.0)
4084 .build_arc()?;
4085
4086 let task_ctx = TaskContext::default().with_runtime(runtime);
4087 let task_ctx = Arc::new(task_ctx);
4088
4089 let exec =
4091 TestMemoryExec::try_new_exec(&input_partitions, Arc::clone(&schema), None)?;
4092 let exec = RepartitionExec::try_new(exec, partitioning)?;
4093
4094 let mut all_batches = Vec::new();
4096 for i in 0..exec.partitioning().partition_count() {
4097 let mut partition_batches = Vec::new();
4098 let mut stream = exec.execute(i, Arc::clone(&task_ctx))?;
4099 while let Some(result) = stream.next().await {
4100 let batch = result?;
4101 partition_batches.push(batch);
4102 }
4103 all_batches.push(partition_batches);
4104 }
4105
4106 let metrics = exec.metrics().unwrap();
4108 assert!(
4109 metrics.spill_count().unwrap() > 0,
4110 "Expected spilling to occur, but spill_count = 0"
4111 );
4112
4113 for (partition_idx, batches) in all_batches.iter().enumerate() {
4116 let mut last_value = None;
4117 for batch in batches {
4118 let array = batch
4119 .column(0)
4120 .as_any()
4121 .downcast_ref::<UInt32Array>()
4122 .unwrap();
4123
4124 for i in 0..array.len() {
4125 let value = array.value(i);
4126 if let Some(last) = last_value {
4127 assert!(
4128 value > last,
4129 "Ordering violated in partition {partition_idx}: {value} is not greater than {last}"
4130 );
4131 }
4132 last_value = Some(value);
4133 }
4134 }
4135 }
4136
4137 Ok(())
4138 }
4139}
4140
4141#[cfg(test)]
4142mod test {
4143 use super::*;
4144 use crate::test::TestMemoryExec;
4145 use crate::union::UnionExec;
4146 use arrow::array::{UInt32Array, record_batch};
4147 use arrow::compute::SortOptions;
4148 use arrow::datatypes::{DataType, Field, Schema};
4149 use datafusion_common::assert_batches_eq;
4150 use datafusion_common::config::ConfigNonZeroUsize;
4151
4152 use datafusion_physical_expr::expressions::col;
4153
4154 macro_rules! assert_plan {
4159 ($PLAN: expr, @ $EXPECTED: expr) => {
4160 let formatted = crate::displayable($PLAN).indent(true).to_string();
4161
4162 insta::assert_snapshot!(
4163 formatted,
4164 @$EXPECTED
4165 );
4166 };
4167 }
4168
4169 #[tokio::test]
4170 async fn test_preserve_order() -> Result<()> {
4171 let schema = test_schema();
4172 let sort_exprs = sort_exprs(&schema);
4173 let source1 = sorted_memory_exec(&schema, sort_exprs.clone());
4174 let source2 = sorted_memory_exec(&schema, sort_exprs);
4175 let union = UnionExec::try_new(vec![source1, source2])?;
4177 let exec = RepartitionExec::try_new(union, Partitioning::RoundRobinBatch(10))?
4178 .with_preserve_order();
4179
4180 assert_plan!(&exec, @r"
4182 RepartitionExec: partitioning=RoundRobinBatch(10), input_partitions=2, preserve_order=true, sort_exprs=c0@0 ASC
4183 UnionExec
4184 DataSourceExec: partitions=1, partition_sizes=[0], output_ordering=c0@0 ASC
4185 DataSourceExec: partitions=1, partition_sizes=[0], output_ordering=c0@0 ASC
4186 ");
4187 Ok(())
4188 }
4189
4190 #[tokio::test]
4191 async fn test_preserve_order_one_partition() -> Result<()> {
4192 let schema = test_schema();
4193 let sort_exprs = sort_exprs(&schema);
4194 let source = sorted_memory_exec(&schema, sort_exprs);
4195 let exec = RepartitionExec::try_new(source, Partitioning::RoundRobinBatch(10))?
4197 .with_preserve_order();
4198
4199 assert_plan!(&exec, @r"
4201 RepartitionExec: partitioning=RoundRobinBatch(10), input_partitions=1, maintains_sort_order=true
4202 DataSourceExec: partitions=1, partition_sizes=[0], output_ordering=c0@0 ASC
4203 ");
4204
4205 Ok(())
4206 }
4207
4208 #[tokio::test]
4209 async fn test_preserve_order_input_not_sorted() -> Result<()> {
4210 let schema = test_schema();
4211 let source1 = memory_exec(&schema);
4212 let source2 = memory_exec(&schema);
4213 let union = UnionExec::try_new(vec![source1, source2])?;
4215 let exec = RepartitionExec::try_new(union, Partitioning::RoundRobinBatch(10))?
4216 .with_preserve_order();
4217
4218 assert_plan!(&exec, @r"
4220 RepartitionExec: partitioning=RoundRobinBatch(10), input_partitions=2
4221 UnionExec
4222 DataSourceExec: partitions=1, partition_sizes=[0]
4223 DataSourceExec: partitions=1, partition_sizes=[0]
4224 ");
4225 Ok(())
4226 }
4227
4228 #[tokio::test]
4229 async fn test_preserve_order_with_spilling() -> Result<()> {
4230 use datafusion_execution::runtime_env::RuntimeEnvBuilder;
4231
4232 let batch1 = record_batch!(("c0", UInt32, [1, 3])).unwrap();
4236 let batch2 = record_batch!(("c0", UInt32, [2, 4])).unwrap();
4237 let batch3 = record_batch!(("c0", UInt32, [5, 7])).unwrap();
4238 let batch4 = record_batch!(("c0", UInt32, [6, 8])).unwrap();
4239 let batch5 = record_batch!(("c0", UInt32, [9, 11])).unwrap();
4240 let batch6 = record_batch!(("c0", UInt32, [10, 12])).unwrap();
4241 let schema = batch1.schema();
4242 let sort_exprs = LexOrdering::new([PhysicalSortExpr {
4243 expr: col("c0", &schema).unwrap(),
4244 options: SortOptions::default().asc(),
4245 }])
4246 .unwrap();
4247 let partition1 = vec![batch1.clone(), batch3.clone(), batch5.clone()];
4248 let partition2 = vec![batch2.clone(), batch4.clone(), batch6.clone()];
4249 let input_partitions = vec![partition1, partition2];
4250
4251 let runtime = RuntimeEnvBuilder::default()
4254 .with_memory_limit(608, 1.0)
4255 .build_arc()?;
4256
4257 let task_ctx = TaskContext::default().with_runtime(runtime);
4258 let task_ctx = Arc::new(task_ctx);
4259
4260 let exec = TestMemoryExec::try_new(&input_partitions, Arc::clone(&schema), None)?
4262 .try_with_sort_information(vec![sort_exprs.clone(), sort_exprs])?;
4263 let exec = Arc::new(exec);
4264 let exec = Arc::new(TestMemoryExec::update_cache(&exec));
4265 let exec = RepartitionExec::try_new(exec, Partitioning::RoundRobinBatch(3))?
4268 .with_preserve_order();
4269
4270 let mut batches = vec![];
4271
4272 for i in 0..exec.partitioning().partition_count() {
4274 let mut stream = exec.execute(i, Arc::clone(&task_ctx))?;
4275 while let Some(result) = stream.next().await {
4276 let batch = result?;
4277 batches.push(batch);
4278 }
4279 }
4280
4281 #[rustfmt::skip]
4282 let expected = [
4283 [
4284 "+----+",
4285 "| c0 |",
4286 "+----+",
4287 "| 1 |",
4288 "| 2 |",
4289 "| 3 |",
4290 "| 4 |",
4291 "+----+",
4292 ],
4293 [
4294 "+----+",
4295 "| c0 |",
4296 "+----+",
4297 "| 5 |",
4298 "| 6 |",
4299 "| 7 |",
4300 "| 8 |",
4301 "+----+",
4302 ],
4303 [
4304 "+----+",
4305 "| c0 |",
4306 "+----+",
4307 "| 9 |",
4308 "| 10 |",
4309 "| 11 |",
4310 "| 12 |",
4311 "+----+",
4312 ],
4313 ];
4314
4315 for (batch, expected) in batches.iter().zip(expected.iter()) {
4316 assert_batches_eq!(expected, std::slice::from_ref(batch));
4317 }
4318
4319 let metrics = exec.metrics().unwrap();
4321 assert!(
4322 metrics.spill_count().unwrap() > 0,
4323 "Expected spilling to occur for order-preserving repartition at this \
4324 memory limit. If this fails, the memory limit may need adjustment."
4325 );
4326 Ok(())
4327 }
4328
4329 #[tokio::test]
4340 async fn test_preserve_order_with_spill_file_rotation() -> Result<()> {
4341 use datafusion_execution::config::SessionConfig;
4342 use datafusion_execution::runtime_env::RuntimeEnvBuilder;
4343
4344 let batch1 = record_batch!(("c0", UInt32, [1, 3])).unwrap();
4347 let batch2 = record_batch!(("c0", UInt32, [2, 4])).unwrap();
4348 let batch3 = record_batch!(("c0", UInt32, [5, 7])).unwrap();
4349 let batch4 = record_batch!(("c0", UInt32, [6, 8])).unwrap();
4350 let batch5 = record_batch!(("c0", UInt32, [9, 11])).unwrap();
4351 let batch6 = record_batch!(("c0", UInt32, [10, 12])).unwrap();
4352 let schema = batch1.schema();
4353 let sort_exprs = LexOrdering::new([PhysicalSortExpr {
4354 expr: col("c0", &schema).unwrap(),
4355 options: SortOptions::default().asc(),
4356 }])
4357 .unwrap();
4358 let partition1 = vec![batch1, batch3, batch5];
4359 let partition2 = vec![batch2, batch4, batch6];
4360 let input_partitions = vec![partition1, partition2];
4361
4362 let mut session_config = SessionConfig::new();
4364 session_config
4365 .options_mut()
4366 .execution
4367 .max_spill_file_size_bytes = ConfigNonZeroUsize::try_new(1).unwrap();
4368 let runtime = RuntimeEnvBuilder::default()
4371 .with_memory_limit(608, 1.0)
4372 .build_arc()?;
4373 let task_ctx = Arc::new(
4374 TaskContext::default()
4375 .with_session_config(session_config)
4376 .with_runtime(runtime),
4377 );
4378
4379 let exec = TestMemoryExec::try_new(&input_partitions, Arc::clone(&schema), None)?
4380 .try_with_sort_information(vec![sort_exprs.clone(), sort_exprs])?;
4381 let exec = Arc::new(TestMemoryExec::update_cache(&Arc::new(exec)));
4382 let exec = RepartitionExec::try_new(exec, Partitioning::RoundRobinBatch(3))?
4383 .with_preserve_order();
4384
4385 for i in 0..exec.partitioning().partition_count() {
4387 let mut stream = exec.execute(i, Arc::clone(&task_ctx))?;
4388 let mut last: Option<u32> = None;
4389 while let Some(result) = stream.next().await {
4390 let batch = result?;
4391 let col = batch
4392 .column(0)
4393 .as_any()
4394 .downcast_ref::<UInt32Array>()
4395 .unwrap();
4396 for r in 0..col.len() {
4397 let v = col.value(r);
4398 if let Some(prev) = last {
4399 assert!(
4400 prev <= v,
4401 "output partition {i} not sorted: {prev} came before {v}"
4402 );
4403 }
4404 last = Some(v);
4405 }
4406 }
4407 }
4408
4409 let metrics = exec.metrics().unwrap();
4410 assert!(
4411 metrics.spill_count().unwrap() > 0,
4412 "Expected spilling to occur for order-preserving repartition at this \
4413 memory limit. If this fails, the memory limit may need adjustment."
4414 );
4415 Ok(())
4416 }
4417
4418 #[tokio::test]
4419 async fn test_hash_partitioning_with_spilling() -> Result<()> {
4420 use datafusion_execution::runtime_env::RuntimeEnvBuilder;
4421
4422 let batch1 = record_batch!(("c0", UInt32, [1, 3])).unwrap();
4424 let batch2 = record_batch!(("c0", UInt32, [2, 4])).unwrap();
4425 let batch3 = record_batch!(("c0", UInt32, [5, 7])).unwrap();
4426 let batch4 = record_batch!(("c0", UInt32, [6, 8])).unwrap();
4427 let schema = batch1.schema();
4428
4429 let partition1 = vec![batch1.clone(), batch3.clone()];
4430 let partition2 = vec![batch2.clone(), batch4.clone()];
4431 let input_partitions = vec![partition1, partition2];
4432
4433 let runtime = RuntimeEnvBuilder::default()
4435 .with_memory_limit(1, 1.0)
4436 .build_arc()?;
4437
4438 let task_ctx = TaskContext::default().with_runtime(runtime);
4439 let task_ctx = Arc::new(task_ctx);
4440
4441 let exec = TestMemoryExec::try_new(&input_partitions, Arc::clone(&schema), None)?;
4443 let exec = Arc::new(exec);
4444 let exec = Arc::new(TestMemoryExec::update_cache(&exec));
4445 let hash_expr = col("c0", &schema)?;
4447 let exec =
4448 RepartitionExec::try_new(exec, Partitioning::Hash(vec![hash_expr], 2))?;
4449
4450 let mut join_set = tokio::task::JoinSet::new();
4453 for i in 0..exec.partitioning().partition_count() {
4454 let stream = exec.execute(i, Arc::clone(&task_ctx))?;
4455 join_set.spawn(async move {
4456 let mut count = 0;
4457 futures::pin_mut!(stream);
4458 while let Some(result) = stream.next().await {
4459 let batch = result?;
4460 count += batch.num_rows();
4461 }
4462 Ok::<usize, DataFusionError>(count)
4463 });
4464 }
4465
4466 let mut total_rows = 0;
4468 while let Some(result) = join_set.join_next().await {
4469 total_rows += result.unwrap()?;
4470 }
4471
4472 let all_batches = [batch1, batch2, batch3, batch4];
4474 let expected_rows: usize = all_batches.iter().map(|b| b.num_rows()).sum();
4475 assert_eq!(total_rows, expected_rows);
4476
4477 let metrics = exec.metrics().unwrap();
4479 let spill_count = metrics.spill_count().unwrap_or(0);
4481 assert!(spill_count > 0);
4482 let spilled_bytes = metrics.spilled_bytes().unwrap_or(0);
4483 assert!(spilled_bytes > 0);
4484 let spilled_rows = metrics.spilled_rows().unwrap_or(0);
4485 assert!(spilled_rows > 0);
4486
4487 Ok(())
4488 }
4489
4490 #[tokio::test]
4491 async fn test_repartition() -> Result<()> {
4492 let schema = test_schema();
4493 let sort_exprs = sort_exprs(&schema);
4494 let source = sorted_memory_exec(&schema, sort_exprs);
4495 let exec = RepartitionExec::try_new(source, Partitioning::RoundRobinBatch(10))?
4497 .repartitioned(20, &Default::default())?
4498 .unwrap();
4499
4500 assert_plan!(exec.as_ref(), @r"
4502 RepartitionExec: partitioning=RoundRobinBatch(20), input_partitions=1, maintains_sort_order=true
4503 DataSourceExec: partitions=1, partition_sizes=[0], output_ordering=c0@0 ASC
4504 ");
4505 Ok(())
4506 }
4507
4508 #[test]
4509 fn test_range_repartitioned_returns_none() -> Result<()> {
4510 let schema = test_schema();
4511 let source = memory_exec(&schema);
4512 let partitioning = Partitioning::Range(RangePartitioning::try_new(
4513 [PhysicalSortExpr::new(
4514 col("c0", &schema)?,
4515 SortOptions::default(),
4516 )]
4517 .into(),
4518 vec![
4519 SplitPoint::new(vec![ScalarValue::UInt32(Some(10))]),
4520 SplitPoint::new(vec![ScalarValue::UInt32(Some(20))]),
4521 ],
4522 )?);
4523 let exec = RepartitionExec::try_new(source, partitioning)?;
4524
4525 let mut expressions = vec![];
4526 exec.apply_expressions(&mut |expr| {
4527 expressions.push(expr.to_string());
4528 Ok(TreeNodeRecursion::Continue)
4529 })?;
4530 assert_eq!(expressions, ["c0@0"]);
4531
4532 let result = exec.repartitioned(10, &Default::default())?;
4535 assert!(
4536 result.is_none(),
4537 "range repartitioning should not support changing partition count"
4538 );
4539 Ok(())
4540 }
4541
4542 fn test_schema() -> Arc<Schema> {
4543 Arc::new(Schema::new(vec![Field::new("c0", DataType::UInt32, false)]))
4544 }
4545
4546 fn sort_exprs(schema: &Schema) -> LexOrdering {
4547 [PhysicalSortExpr {
4548 expr: col("c0", schema).unwrap(),
4549 options: SortOptions::default(),
4550 }]
4551 .into()
4552 }
4553
4554 fn memory_exec(schema: &SchemaRef) -> Arc<dyn ExecutionPlan> {
4555 TestMemoryExec::try_new_exec(&[vec![]], Arc::clone(schema), None).unwrap()
4556 }
4557
4558 fn sorted_memory_exec(
4559 schema: &SchemaRef,
4560 sort_exprs: LexOrdering,
4561 ) -> Arc<dyn ExecutionPlan> {
4562 let exec = TestMemoryExec::try_new(&[vec![]], Arc::clone(schema), None)
4563 .unwrap()
4564 .try_with_sort_information(vec![sort_exprs])
4565 .unwrap();
4566 let exec = Arc::new(exec);
4567 Arc::new(TestMemoryExec::update_cache(&exec))
4568 }
4569
4570 #[tokio::test]
4573 async fn test_preserve_order_output_rows_not_double_counted() -> Result<()> {
4574 use datafusion_execution::TaskContext;
4575
4576 let batch1 = record_batch!(("c0", UInt32, [1, 3])).unwrap();
4578 let batch2 = record_batch!(("c0", UInt32, [2, 4])).unwrap();
4579 let schema = batch1.schema();
4580 let sort_exprs = sort_exprs(&schema);
4581
4582 let input_partitions = vec![vec![batch1], vec![batch2]];
4583 let exec = TestMemoryExec::try_new(&input_partitions, Arc::clone(&schema), None)?
4584 .try_with_sort_information(vec![sort_exprs.clone(), sort_exprs])?;
4585 let exec = Arc::new(exec);
4586 let exec = Arc::new(TestMemoryExec::update_cache(&exec));
4587
4588 let exec = RepartitionExec::try_new(exec, Partitioning::RoundRobinBatch(3))?
4589 .with_preserve_order();
4590
4591 let task_ctx = Arc::new(TaskContext::default());
4592 let mut total_rows = 0;
4593 for i in 0..exec.partitioning().partition_count() {
4594 let mut stream = exec.execute(i, Arc::clone(&task_ctx))?;
4595 while let Some(result) = stream.next().await {
4596 total_rows += result?.num_rows();
4597 }
4598 }
4599
4600 assert_eq!(total_rows, 4, "actual rows collected should be 4");
4601
4602 let metrics = exec.metrics().unwrap();
4603 let reported_output_rows = metrics.output_rows().unwrap();
4604 assert_eq!(
4605 reported_output_rows, total_rows,
4606 "metrics output_rows ({reported_output_rows}) should match \
4607 actual rows collected ({total_rows}), not double-count"
4608 );
4609
4610 Ok(())
4611 }
4612}