1use std::fmt;
23use std::fmt::{Debug, Formatter};
24use std::sync::Arc;
25
26use parking_lot::RwLock;
27
28use crate::common::spawn_buffered;
29use crate::execution_plan::{
30 Boundedness, CardinalityEffect, EmissionType, has_same_children_properties,
31 replace_children_if_necessary,
32};
33use crate::expressions::PhysicalSortExpr;
34use crate::filter::FilterExec;
35use crate::filter_pushdown::{
36 ChildFilterDescription, ChildPushdownResult, FilterDescription, FilterPushdownPhase,
37 FilterPushdownPropagation, PushedDown,
38};
39use crate::limit::LimitStream;
40use crate::metrics::{
41 BaselineMetrics, ExecutionPlanMetricsSet, MetricsSet, SpillMetrics,
42};
43use crate::projection::{ProjectionExec, make_with_child, update_ordering};
44use crate::sorts::IncrementalSortIterator;
45use crate::sorts::streaming_merge::{SortedSpillFile, StreamingMergeBuilder};
46use crate::spill::get_record_batch_memory_size;
47use crate::spill::in_progress_spill_file::InProgressSpillFile;
48use crate::spill::spill_manager::{GetSlicedSize, SpillManager};
49use crate::statistics::{ChildStats, StatisticsArgs};
50use crate::stream::ReservationStream;
51use crate::stream::{ObservedStream, RecordBatchStreamAdapter};
52use crate::topk::TopK;
53use crate::topk::TopKDynamicFilters;
54use crate::{
55 ChildrenPropertiesMode, DisplayAs, DisplayFormatType, Distribution,
56 EmptyRecordBatchStream, ExecutionPlan, ExecutionPlanProperties, Partitioning,
57 PlanProperties, ReplaceChildrenOptions, SendableRecordBatchStream, Statistics,
58};
59
60use arrow::array::{RecordBatch, RecordBatchOptions};
61use arrow::compute::{concat_batches, lexsort_to_indices, take_arrays};
62use arrow::datatypes::SchemaRef;
63use datafusion_common::config::SpillCompression;
64use datafusion_common::tree_node::TreeNodeRecursion;
65use datafusion_common::{
66 DataFusionError, Result, assert_or_internal_err, internal_datafusion_err,
67 unwrap_or_internal_err,
68};
69use datafusion_execution::TaskContext;
70use datafusion_execution::memory_pool::{MemoryConsumer, MemoryReservation};
71use datafusion_execution::runtime_env::RuntimeEnv;
72use datafusion_physical_expr::LexOrdering;
73use datafusion_physical_expr::PhysicalExpr;
74use datafusion_physical_expr::expressions::{DynamicFilterPhysicalExpr, lit};
75
76use futures::{StreamExt, TryStreamExt};
77use log::{debug, trace};
78
79struct ExternalSorterMetrics {
80 baseline: BaselineMetrics,
82
83 spill_metrics: SpillMetrics,
84}
85
86impl ExternalSorterMetrics {
87 fn new(metrics: &ExecutionPlanMetricsSet, partition: usize) -> Self {
88 Self {
89 baseline: BaselineMetrics::new(metrics, partition),
90 spill_metrics: SpillMetrics::new(metrics, partition),
91 }
92 }
93}
94
95struct ExternalSorter {
213 schema: SchemaRef,
219 expr: LexOrdering,
221 batch_size: usize,
223 sort_in_place_threshold_bytes: usize,
227
228 in_mem_batches: Vec<RecordBatch>,
234
235 in_progress_spill_file: Option<(InProgressSpillFile, usize)>,
242 finished_spill_files: Vec<SortedSpillFile>,
247
248 metrics: ExternalSorterMetrics,
254 runtime: Arc<RuntimeEnv>,
256 reservation: MemoryReservation,
258 spill_manager: SpillManager,
259
260 merge_reservation: MemoryReservation,
264 sort_spill_reservation_bytes: usize,
267}
268
269impl ExternalSorter {
270 #[expect(clippy::too_many_arguments)]
273 pub fn new(
274 partition_id: usize,
275 schema: SchemaRef,
276 expr: LexOrdering,
277 batch_size: usize,
278 sort_spill_reservation_bytes: usize,
279 sort_in_place_threshold_bytes: usize,
280 spill_compression: SpillCompression,
282 metrics: &ExecutionPlanMetricsSet,
283 runtime: Arc<RuntimeEnv>,
284 ) -> Result<Self> {
285 let metrics = ExternalSorterMetrics::new(metrics, partition_id);
286 let reservation = MemoryConsumer::new(format!("ExternalSorter[{partition_id}]"))
287 .with_can_spill(true)
288 .register(&runtime.memory_pool);
289
290 let merge_reservation =
291 MemoryConsumer::new(format!("ExternalSorterMerge[{partition_id}]"))
292 .register(&runtime.memory_pool);
293
294 let spill_manager = SpillManager::new(
295 Arc::clone(&runtime),
296 metrics.spill_metrics.clone(),
297 Arc::clone(&schema),
298 )
299 .with_compression_type(spill_compression);
300
301 Ok(Self {
302 schema,
303 in_mem_batches: vec![],
304 in_progress_spill_file: None,
305 finished_spill_files: vec![],
306 expr,
307 metrics,
308 reservation,
309 spill_manager,
310 merge_reservation,
311 runtime,
312 batch_size,
313 sort_spill_reservation_bytes,
314 sort_in_place_threshold_bytes,
315 })
316 }
317
318 async fn insert_batch(&mut self, input: RecordBatch) -> Result<()> {
322 if input.num_rows() == 0 {
323 return Ok(());
324 }
325
326 self.reserve_memory_for_merge()?;
327 self.reserve_memory_for_batch_and_maybe_spill(&input)
328 .await?;
329
330 self.in_mem_batches.push(input);
331 Ok(())
332 }
333
334 fn spilled_before(&self) -> bool {
335 !self.finished_spill_files.is_empty()
336 }
337
338 async fn sort(&mut self) -> Result<SendableRecordBatchStream> {
348 if self.spilled_before() {
349 if !self.in_mem_batches.is_empty() {
353 self.sort_and_spill_in_mem_batches().await?;
354 }
355
356 StreamingMergeBuilder::new()
364 .with_sorted_spill_files(std::mem::take(&mut self.finished_spill_files))
365 .with_spill_manager(self.spill_manager.clone())
366 .with_schema(Arc::clone(&self.schema))
367 .with_expressions(&self.expr.clone())
368 .with_metrics(self.metrics.baseline.clone())
369 .with_batch_size(self.batch_size)
370 .with_fetch(None)
371 .with_reservation(self.merge_reservation.take())
372 .build()
373 } else {
374 self.merge_reservation.free();
379 self.in_mem_sort_stream(true, true)
380 }
381 }
382
383 fn used(&self) -> usize {
385 self.reservation.size()
386 }
387
388 #[cfg(test)]
390 fn merge_reservation_size(&self) -> usize {
391 self.merge_reservation.size()
392 }
393
394 fn spilled_bytes(&self) -> usize {
396 self.metrics.spill_metrics.spilled_bytes.value()
397 }
398
399 fn spilled_rows(&self) -> usize {
401 self.metrics.spill_metrics.spilled_rows.value()
402 }
403
404 fn spill_count(&self) -> usize {
406 self.metrics.spill_metrics.spill_file_count.value()
407 }
408
409 fn consume_and_spill_append(
412 &mut self,
413 globally_sorted_batches: &mut Vec<RecordBatch>,
414 ) -> Result<()> {
415 if globally_sorted_batches.is_empty() {
416 return Ok(());
417 }
418
419 if self.in_progress_spill_file.is_none() {
421 self.in_progress_spill_file =
422 Some((self.spill_manager.create_in_progress_file("Sorting")?, 0));
423 }
424
425 debug!("Spilling sort data of ExternalSorter to disk whilst inserting");
426
427 let batches_to_spill = std::mem::take(globally_sorted_batches);
428 self.reservation.free();
429
430 let (in_progress_file, max_record_batch_size) =
431 self.in_progress_spill_file.as_mut().ok_or_else(|| {
432 internal_datafusion_err!("In-progress spill file should be initialized")
433 })?;
434
435 for batch in batches_to_spill {
436 let gc_sliced_size = in_progress_file.append_batch(&batch)?;
437
438 *max_record_batch_size = (*max_record_batch_size).max(gc_sliced_size);
439 }
440
441 assert_or_internal_err!(
442 globally_sorted_batches.is_empty(),
443 "This function consumes globally_sorted_batches, so it should be empty after taking."
444 );
445
446 Ok(())
447 }
448
449 fn spill_finish(&mut self) -> Result<()> {
451 let (mut in_progress_file, max_record_batch_memory) =
452 self.in_progress_spill_file.take().ok_or_else(|| {
453 internal_datafusion_err!("Should be called after `spill_append`")
454 })?;
455 let spill_file = in_progress_file.finish()?;
456
457 if let Some(spill_file) = spill_file {
458 self.finished_spill_files.push(SortedSpillFile {
459 file: spill_file,
460 max_record_batch_memory,
461 });
462 }
463
464 Ok(())
465 }
466
467 async fn sort_and_spill_in_mem_batches(&mut self) -> Result<()> {
470 assert_or_internal_err!(
471 !self.in_mem_batches.is_empty(),
472 "in_mem_batches must not be empty when attempting to sort and spill"
473 );
474
475 self.merge_reservation.free();
480
481 let mut sorted_stream = self.in_mem_sort_stream(
482 false,
483 false,
485 )?;
486 assert_or_internal_err!(
489 self.in_mem_batches.is_empty(),
490 "in_mem_batches should be empty after constructing sorted stream"
491 );
492 let mut globally_sorted_batches: Vec<RecordBatch> = vec![];
496
497 while let Some(batch) = sorted_stream.next().await {
498 let batch = batch?;
499 let sorted_size = get_reserved_bytes_for_record_batch(&batch)?;
500 if self.reservation.try_grow(sorted_size).is_err() {
501 globally_sorted_batches.push(batch);
505 self.consume_and_spill_append(&mut globally_sorted_batches)?; } else {
507 globally_sorted_batches.push(batch);
508 }
509 }
510
511 drop(sorted_stream);
514
515 self.consume_and_spill_append(&mut globally_sorted_batches)?;
516 self.spill_finish()?;
517
518 let buffers_cleared_property =
520 self.in_mem_batches.is_empty() && globally_sorted_batches.is_empty();
521 assert_or_internal_err!(
522 buffers_cleared_property,
523 "in_mem_batches and globally_sorted_batches should be cleared before"
524 );
525
526 self.reserve_memory_for_merge()?;
528
529 Ok(())
530 }
531
532 fn in_mem_sort_stream(
593 &mut self,
594 is_output_stream: bool,
595 coalesce_runs: bool,
596 ) -> Result<SendableRecordBatchStream> {
597 if self.in_mem_batches.is_empty() {
598 let empty_stream =
599 Box::pin(EmptyRecordBatchStream::new(Arc::clone(&self.schema)));
600 return Ok(self.observe_if_output(empty_stream, is_output_stream));
601 }
602
603 let elapsed_compute = self.metrics.baseline.elapsed_compute().clone();
606 let _timer = elapsed_compute.timer();
607
608 if self.in_mem_batches.len() == 1 {
615 let batch = self.in_mem_batches.swap_remove(0);
616 let reservation = self.reservation.take();
617 let sorted_stream = self.sort_batch_stream(batch, reservation)?;
618 return Ok(self.observe_if_output(sorted_stream, is_output_stream));
619 }
620
621 if self.reservation.size() < self.sort_in_place_threshold_bytes {
623 let batch = concat_batches(&self.schema, &self.in_mem_batches)?;
625 self.in_mem_batches.clear();
626 self.reservation
627 .try_resize(get_reserved_bytes_for_record_batch(&batch)?)
628 .map_err(Self::err_with_oom_context)?;
629 let reservation = self.reservation.take();
630 let sorted_stream = self.sort_batch_stream(batch, reservation)?;
631 return Ok(self.observe_if_output(sorted_stream, is_output_stream));
632 }
633
634 let batches = std::mem::take(&mut self.in_mem_batches);
640 let runs = if coalesce_runs && self.expr.len() == 1 {
641 self.coalesce_in_mem_batches_into_runs(batches)?
642 } else {
643 batches
644 };
645
646 let streams = runs
647 .into_iter()
648 .map(|batch| {
649 let reservation = self
650 .reservation
651 .split(get_reserved_bytes_for_record_batch(&batch)?);
652 let input = self.sort_batch_stream(batch, reservation)?;
653 Ok(spawn_buffered(input, 1))
654 })
655 .collect::<Result<_>>()?;
656
657 StreamingMergeBuilder::new()
658 .with_streams(streams)
659 .with_schema(Arc::clone(&self.schema))
660 .with_expressions(&self.expr.clone())
661 .with_metrics(if is_output_stream {
662 self.metrics.baseline.clone()
663 } else {
664 self.metrics.baseline.intermediate()
665 })
666 .with_batch_size(self.batch_size)
667 .with_fetch(None)
668 .with_reservation(self.merge_reservation.new_empty())
669 .build()
670 }
671
672 fn coalesce_in_mem_batches_into_runs(
677 &mut self,
678 batches: Vec<RecordBatch>,
679 ) -> Result<Vec<RecordBatch>> {
680 let target = self.sort_in_place_threshold_bytes.max(1);
681 let mut runs: Vec<RecordBatch> = Vec::new();
682 let mut group: Vec<RecordBatch> = Vec::new();
683 let mut group_bytes = 0usize;
684
685 let flush = |group: &mut Vec<RecordBatch>,
687 runs: &mut Vec<RecordBatch>,
688 schema: &SchemaRef|
689 -> Result<()> {
690 match group.len() {
691 0 => {}
692 1 => runs.push(group.pop().unwrap()),
693 _ => {
694 runs.push(concat_batches(schema, group.iter())?);
695 group.clear();
696 }
697 }
698 Ok(())
699 };
700
701 for batch in batches {
702 let bytes = get_reserved_bytes_for_record_batch(&batch)?;
703 if !group.is_empty() && group_bytes.saturating_add(bytes) > target {
704 flush(&mut group, &mut runs, &self.schema)?;
705 group_bytes = 0;
706 }
707 group_bytes += bytes;
708 group.push(batch);
709 }
710 flush(&mut group, &mut runs, &self.schema)?;
711
712 let total: usize = runs
714 .iter()
715 .map(get_reserved_bytes_for_record_batch)
716 .sum::<Result<usize>>()?;
717 self.reservation
718 .try_resize(total)
719 .map_err(Self::err_with_oom_context)?;
720
721 Ok(runs)
722 }
723
724 fn sort_batch_stream(
734 &self,
735 batch: RecordBatch,
736 reservation: MemoryReservation,
737 ) -> Result<SendableRecordBatchStream> {
738 assert_eq!(
739 get_reserved_bytes_for_record_batch(&batch)?,
740 reservation.size()
741 );
742
743 let schema = batch.schema();
744 let expressions = self.expr.clone();
745 let batch_size = self.batch_size;
746
747 let stream = futures::stream::once(async move {
748 let schema = batch.schema();
749
750 let sorted_batches = sort_batch_chunked(&batch, &expressions, batch_size)?;
752
753 let total_sorted_size: usize = sorted_batches
758 .iter()
759 .map(get_record_batch_memory_size)
760 .sum();
761 reservation
762 .try_resize(total_sorted_size)
763 .map_err(Self::err_with_oom_context)?;
764
765 Result::<_, DataFusionError>::Ok(Box::pin(ReservationStream::new(
767 Arc::clone(&schema),
768 Box::pin(RecordBatchStreamAdapter::new(
769 Arc::clone(&schema),
770 futures::stream::iter(sorted_batches.into_iter().map(Ok)),
771 )),
772 reservation,
773 )) as SendableRecordBatchStream)
774 })
775 .try_flatten();
776
777 Ok(Box::pin(RecordBatchStreamAdapter::new(schema, stream)))
778 }
779
780 fn reserve_memory_for_merge(&mut self) -> Result<()> {
784 if self.runtime.disk_manager.tmp_files_enabled() {
786 let size = self.sort_spill_reservation_bytes;
787 if self.merge_reservation.size() != size {
788 self.merge_reservation
789 .try_resize(size)
790 .map_err(Self::err_with_oom_context)?;
791 }
792 }
793
794 Ok(())
795 }
796
797 async fn reserve_memory_for_batch_and_maybe_spill(
800 &mut self,
801 input: &RecordBatch,
802 ) -> Result<()> {
803 let size = get_reserved_bytes_for_record_batch(input)?;
804
805 match self.reservation.try_grow(size) {
806 Ok(_) => Ok(()),
807 Err(e) => {
808 if self.in_mem_batches.is_empty() {
809 return Err(Self::err_with_oom_context(e));
810 }
811
812 self.sort_and_spill_in_mem_batches().await?;
814 self.reservation
815 .try_grow(size)
816 .map_err(Self::err_with_oom_context)
817 }
818 }
819 }
820
821 fn err_with_oom_context(e: DataFusionError) -> DataFusionError {
824 match e {
825 DataFusionError::ResourcesExhausted(_) => e.context(
826 "Not enough memory to continue external sort. \
827 Consider increasing the memory limit config: 'datafusion.runtime.memory_limit', \
828 or decreasing the config: 'datafusion.execution.sort_spill_reservation_bytes'."
829 ),
830 _ => e,
832 }
833 }
834
835 fn observe_if_output(
836 &self,
837 mut stream: SendableRecordBatchStream,
838 wrap: bool,
839 ) -> SendableRecordBatchStream {
840 if wrap {
841 stream = Box::pin(ObservedStream::new(
842 stream,
843 self.metrics.baseline.clone(),
844 None,
845 ))
846 }
847
848 stream
849 }
850}
851
852pub(crate) fn get_reserved_bytes_for_record_batch_size(
863 record_batch_size: usize,
864 sliced_size: usize,
865) -> usize {
866 record_batch_size + sliced_size
870}
871
872pub(crate) fn get_reserved_bytes_for_record_batch(batch: &RecordBatch) -> Result<usize> {
876 batch.get_sliced_size().map(|sliced_size| {
877 get_reserved_bytes_for_record_batch_size(
878 get_record_batch_memory_size(batch),
879 sliced_size,
880 )
881 })
882}
883
884impl Debug for ExternalSorter {
885 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
886 f.debug_struct("ExternalSorter")
887 .field("memory_used", &self.used())
888 .field("spilled_bytes", &self.spilled_bytes())
889 .field("spilled_rows", &self.spilled_rows())
890 .field("spill_count", &self.spill_count())
891 .finish()
892 }
893}
894
895pub fn sort_batch(
896 batch: &RecordBatch,
897 expressions: &LexOrdering,
898 fetch: Option<usize>,
899) -> Result<RecordBatch> {
900 let sort_columns = expressions
901 .iter()
902 .map(|expr| expr.evaluate_to_sort_column(batch))
903 .collect::<Result<Vec<_>>>()?;
904
905 let indices = lexsort_to_indices(&sort_columns, fetch)?;
906 let columns = take_arrays(batch.columns(), &indices, None)?;
907
908 let options = RecordBatchOptions::new().with_row_count(Some(indices.len()));
909 Ok(RecordBatch::try_new_with_options(
910 batch.schema(),
911 columns,
912 &options,
913 )?)
914}
915
916pub fn sort_batch_chunked(
920 batch: &RecordBatch,
921 expressions: &LexOrdering,
922 batch_size: usize,
923) -> Result<Vec<RecordBatch>> {
924 IncrementalSortIterator::new(batch.clone(), expressions.clone(), batch_size).collect()
925}
926
927#[derive(Debug, Clone)]
932pub struct SortExec {
933 pub(crate) input: Arc<dyn ExecutionPlan>,
935 expr: LexOrdering,
937 metrics_set: ExecutionPlanMetricsSet,
939 preserve_partitioning: bool,
942 fetch: Option<usize>,
944 common_sort_prefix: Vec<PhysicalSortExpr>,
946 cache: Arc<PlanProperties>,
948 filter: Option<Arc<RwLock<TopKDynamicFilters>>>,
952}
953
954impl SortExec {
955 pub fn new(expr: LexOrdering, input: Arc<dyn ExecutionPlan>) -> Self {
958 let preserve_partitioning = false;
959 let (cache, sort_prefix) =
960 Self::compute_properties(&input, expr.clone(), preserve_partitioning)
961 .unwrap();
962 Self {
963 expr,
964 input,
965 metrics_set: ExecutionPlanMetricsSet::new(),
966 preserve_partitioning,
967 fetch: None,
968 common_sort_prefix: sort_prefix,
969 cache: Arc::new(cache),
970 filter: None,
971 }
972 }
973
974 pub fn preserve_partitioning(&self) -> bool {
976 self.preserve_partitioning
977 }
978
979 pub fn with_preserve_partitioning(mut self, preserve_partitioning: bool) -> Self {
987 self.preserve_partitioning = preserve_partitioning;
988 Arc::make_mut(&mut self.cache).partitioning =
989 Self::output_partitioning_helper(&self.input, self.preserve_partitioning);
990 if self.fetch.is_some() {
991 self.rebuild_filter_for_current_partitioning();
992 }
993 self
994 }
995
996 fn topk_emitter_count(&self) -> usize {
997 self.cache.output_partitioning().partition_count()
998 }
999
1000 fn create_filter(&self) -> Arc<RwLock<TopKDynamicFilters>> {
1002 let children = self
1003 .expr
1004 .iter()
1005 .map(|sort_expr| Arc::clone(&sort_expr.expr))
1006 .collect::<Vec<_>>();
1007 self.create_filter_with_expr(Arc::new(DynamicFilterPhysicalExpr::new(
1008 children,
1009 lit(true),
1010 )))
1011 }
1012
1013 fn create_filter_with_expr(
1014 &self,
1015 expr: Arc<DynamicFilterPhysicalExpr>,
1016 ) -> Arc<RwLock<TopKDynamicFilters>> {
1017 Arc::new(RwLock::new(
1018 TopKDynamicFilters::new_with_topk_emitter_count(
1019 expr,
1020 self.topk_emitter_count(),
1021 ),
1022 ))
1023 }
1024
1025 fn rebuild_filter_for_current_partitioning(&mut self) {
1031 let filter_expr = self.filter.as_ref().map(|filter| filter.read().expr());
1032 if let Some(filter_expr) = filter_expr {
1033 self.filter = Some(self.create_filter_with_expr(filter_expr));
1034 }
1035 }
1036
1037 fn cloned(&self) -> Self {
1038 SortExec {
1039 input: Arc::clone(&self.input),
1040 expr: self.expr.clone(),
1041 metrics_set: self.metrics_set.clone(),
1042 preserve_partitioning: self.preserve_partitioning,
1043 common_sort_prefix: self.common_sort_prefix.clone(),
1044 fetch: self.fetch,
1045 cache: Arc::clone(&self.cache),
1046 filter: self.filter.clone(),
1047 }
1048 }
1049
1050 pub fn with_fetch(&self, fetch: Option<usize>) -> Self {
1058 let mut cache = PlanProperties::clone(&self.cache);
1059 let is_pipeline_friendly = matches!(
1063 cache.emission_type,
1064 EmissionType::Incremental | EmissionType::Both
1065 );
1066 if fetch.is_some() && is_pipeline_friendly {
1067 cache = cache.with_boundedness(Boundedness::Bounded);
1068 }
1069 let mut new_sort = self.cloned();
1070 new_sort.fetch = fetch;
1071 new_sort.cache = cache.into();
1072 if fetch.is_some() {
1073 if new_sort.filter.is_some() {
1074 new_sort.rebuild_filter_for_current_partitioning();
1077 } else {
1078 new_sort.filter = Some(new_sort.create_filter());
1079 }
1080 } else {
1081 new_sort.filter = None;
1082 }
1083 new_sort
1084 }
1085
1086 pub fn input(&self) -> &Arc<dyn ExecutionPlan> {
1088 &self.input
1089 }
1090
1091 pub fn expr(&self) -> &LexOrdering {
1093 &self.expr
1094 }
1095
1096 pub fn fetch(&self) -> Option<usize> {
1098 self.fetch
1099 }
1100
1101 #[deprecated(
1103 since = "55.0.0",
1104 note = "Use ExecutionPlan::dynamic_expressions_produced instead"
1105 )]
1106 pub fn dynamic_filter_expr(&self) -> Option<Arc<DynamicFilterPhysicalExpr>> {
1107 self.filter.as_ref().map(|f| f.read().expr())
1108 }
1109
1110 pub fn with_dynamic_filter_expr(
1118 mut self,
1119 filter: Arc<DynamicFilterPhysicalExpr>,
1120 ) -> Result<Self> {
1121 let input_schema = self.input.schema();
1122 for child in filter.children() {
1123 child.data_type(&input_schema)?;
1124 }
1125 self.filter = Some(self.create_filter_with_expr(filter));
1126 Ok(self)
1127 }
1128
1129 fn output_partitioning_helper(
1130 input: &Arc<dyn ExecutionPlan>,
1131 preserve_partitioning: bool,
1132 ) -> Partitioning {
1133 if preserve_partitioning {
1135 input.output_partitioning().clone()
1136 } else {
1137 Partitioning::UnknownPartitioning(1)
1138 }
1139 }
1140
1141 fn compute_properties(
1144 input: &Arc<dyn ExecutionPlan>,
1145 sort_exprs: LexOrdering,
1146 preserve_partitioning: bool,
1147 ) -> Result<(PlanProperties, Vec<PhysicalSortExpr>)> {
1148 let (sort_prefix, sort_satisfied) = input
1149 .equivalence_properties()
1150 .extract_common_sort_prefix(sort_exprs.clone())?;
1151
1152 let emission_type = if sort_satisfied {
1156 input.pipeline_behavior()
1157 } else {
1158 EmissionType::Final
1159 };
1160
1161 let boundedness = if sort_satisfied {
1167 input.boundedness()
1168 } else {
1169 match input.boundedness() {
1170 Boundedness::Unbounded { .. } => Boundedness::Unbounded {
1171 requires_infinite_memory: true,
1172 },
1173 bounded => bounded,
1174 }
1175 };
1176
1177 let mut eq_properties = input.equivalence_properties().clone();
1180 eq_properties.reorder(sort_exprs)?;
1181
1182 let output_partitioning =
1184 Self::output_partitioning_helper(input, preserve_partitioning);
1185
1186 Ok((
1187 PlanProperties::new(
1188 eq_properties,
1189 output_partitioning,
1190 emission_type,
1191 boundedness,
1192 ),
1193 sort_prefix,
1194 ))
1195 }
1196}
1197
1198impl DisplayAs for SortExec {
1199 fn fmt_as(&self, t: DisplayFormatType, f: &mut Formatter) -> fmt::Result {
1200 match t {
1201 DisplayFormatType::Default | DisplayFormatType::Verbose => {
1202 let preserve_partitioning = self.preserve_partitioning;
1203 match self.fetch {
1204 Some(fetch) => {
1205 write!(
1206 f,
1207 "SortExec: TopK(fetch={fetch}), expr=[{}], preserve_partitioning=[{preserve_partitioning}]",
1208 self.expr
1209 )?;
1210 if let Some(filter) = &self.filter
1211 && let Ok(current) = filter.read().expr().current()
1212 && !current.eq(&lit(true))
1213 {
1214 write!(f, ", filter=[{current}]")?;
1215 }
1216 if !self.common_sort_prefix.is_empty() {
1217 write!(f, ", sort_prefix=[")?;
1218 let mut first = true;
1219 for sort_expr in &self.common_sort_prefix {
1220 if first {
1221 first = false;
1222 } else {
1223 write!(f, ", ")?;
1224 }
1225 write!(f, "{sort_expr}")?;
1226 }
1227 write!(f, "]")
1228 } else {
1229 Ok(())
1230 }
1231 }
1232 None => write!(
1233 f,
1234 "SortExec: expr=[{}], preserve_partitioning=[{preserve_partitioning}]",
1235 self.expr
1236 ),
1237 }
1238 }
1239 DisplayFormatType::TreeRender => match self.fetch {
1240 Some(fetch) => {
1241 writeln!(f, "{}", self.expr)?;
1242 writeln!(f, "limit={fetch}")
1243 }
1244 None => {
1245 writeln!(f, "{}", self.expr)
1246 }
1247 },
1248 }
1249 }
1250}
1251
1252impl ExecutionPlan for SortExec {
1253 fn name(&self) -> &'static str {
1254 match self.fetch {
1255 Some(_) => "SortExec(TopK)",
1256 None => "SortExec",
1257 }
1258 }
1259
1260 fn properties(&self) -> &Arc<PlanProperties> {
1261 &self.cache
1262 }
1263
1264 fn required_input_distribution(&self) -> Vec<Distribution> {
1265 self.input_distribution_requirements().into_per_child()
1266 }
1267
1268 fn input_distribution_requirements(&self) -> crate::InputDistributionRequirements {
1269 crate::InputDistributionRequirements::new(if self.preserve_partitioning {
1270 vec![Distribution::UnspecifiedDistribution]
1271 } else {
1272 vec![Distribution::SinglePartition]
1276 })
1277 }
1278
1279 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
1280 vec![&self.input]
1281 }
1282
1283 fn apply_expressions(
1284 &self,
1285 f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
1286 ) -> Result<TreeNodeRecursion> {
1287 let dynamic_filter = self
1288 .filter
1289 .as_ref()
1290 .map(|filter| filter.read().expr() as Arc<dyn PhysicalExpr>);
1291 crate::apply_expression_roots(
1292 self.expr
1293 .iter()
1294 .map(|sort_expr| &sort_expr.expr)
1295 .chain(dynamic_filter.iter()),
1296 f,
1297 )
1298 }
1299
1300 fn dynamic_expressions_produced(&self) -> Vec<Arc<dyn PhysicalExpr>> {
1301 self.filter
1302 .iter()
1303 .map(|filter| filter.read().expr() as Arc<dyn PhysicalExpr>)
1304 .collect()
1305 }
1306
1307 fn benefits_from_input_partitioning(&self) -> Vec<bool> {
1308 vec![false]
1309 }
1310
1311 fn replace_children(
1312 self: Arc<Self>,
1313 children: Vec<Arc<dyn ExecutionPlan>>,
1314 options: ReplaceChildrenOptions,
1315 ) -> Result<Arc<dyn ExecutionPlan>> {
1316 let mut new_sort = self.cloned();
1317 assert_eq!(children.len(), 1, "SortExec should have exactly one child");
1318 new_sort.input = Arc::clone(&children[0]);
1319
1320 if options.children_properties == ChildrenPropertiesMode::Recompute {
1321 let (cache, sort_prefix) = Self::compute_properties(
1323 &new_sort.input,
1324 new_sort.expr.clone(),
1325 new_sort.preserve_partitioning,
1326 )?;
1327 new_sort.cache = Arc::new(cache);
1328 new_sort.common_sort_prefix = sort_prefix;
1329 if new_sort.fetch.is_some() {
1330 new_sort.rebuild_filter_for_current_partitioning();
1331 }
1332 }
1333
1334 Ok(Arc::new(new_sort))
1335 }
1336
1337 fn with_new_children(
1338 self: Arc<Self>,
1339 children: Vec<Arc<dyn ExecutionPlan>>,
1340 ) -> Result<Arc<dyn ExecutionPlan>> {
1341 match has_same_children_properties(self.as_ref(), &children)? {
1342 true => self.replace_children(
1343 children,
1344 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Keep),
1345 ),
1346 false => self.replace_children(
1347 children,
1348 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
1349 ),
1350 }
1351 }
1352
1353 fn reset_state(self: Arc<Self>) -> Result<Arc<dyn ExecutionPlan>> {
1354 let children = self.children().into_iter().cloned().collect();
1355 let new_sort = replace_children_if_necessary(self, children)?;
1356 let mut new_sort = new_sort
1357 .downcast_ref::<SortExec>()
1358 .expect("rebuilt SortExec with new children")
1359 .clone();
1360 new_sort.filter = Some(new_sort.create_filter());
1362 new_sort.metrics_set = ExecutionPlanMetricsSet::new();
1363
1364 Ok(Arc::new(new_sort))
1365 }
1366
1367 fn execute(
1368 &self,
1369 partition: usize,
1370 context: Arc<TaskContext>,
1371 ) -> Result<SendableRecordBatchStream> {
1372 trace!(
1373 "Start SortExec::execute for partition {} of context session_id {} and task_id {:?}",
1374 partition,
1375 context.session_id(),
1376 context.task_id()
1377 );
1378
1379 let mut input = self.input.execute(partition, Arc::clone(&context))?;
1380
1381 let execution_options = &context.session_config().options().execution;
1382
1383 trace!("End SortExec's input.execute for partition: {partition}");
1384
1385 let sort_satisfied = self
1386 .input
1387 .equivalence_properties()
1388 .ordering_satisfy(self.expr.clone())?;
1389
1390 match (sort_satisfied, self.fetch.as_ref()) {
1391 (true, Some(fetch)) => Ok(Box::pin(LimitStream::new(
1392 input,
1393 0,
1394 Some(*fetch),
1395 BaselineMetrics::new(&self.metrics_set, partition),
1396 ))),
1397 (true, None) => Ok(input),
1398 (false, Some(fetch)) => {
1399 let filter = self.filter.clone();
1400 let mut topk = TopK::try_new(
1401 partition,
1402 input.schema(),
1403 self.common_sort_prefix.clone(),
1404 self.expr.clone(),
1405 *fetch,
1406 context.session_config().batch_size(),
1407 context.runtime_env(),
1408 &self.metrics_set,
1409 Arc::clone(&unwrap_or_internal_err!(filter)),
1410 )?;
1411 Ok(Box::pin(RecordBatchStreamAdapter::new(
1412 self.schema(),
1413 futures::stream::once(async move {
1414 while let Some(batch) = input.next().await {
1415 let batch = batch?;
1416 topk.insert_batch(batch)?;
1417 if topk.finished {
1418 break;
1419 }
1420 }
1421 drop(input);
1422 topk.emit()
1423 })
1424 .try_flatten(),
1425 )))
1426 }
1427 (false, None) => {
1428 let mut sorter = ExternalSorter::new(
1429 partition,
1430 input.schema(),
1431 self.expr.clone(),
1432 context.session_config().batch_size(),
1433 execution_options.sort_spill_reservation_bytes,
1434 execution_options.sort_in_place_threshold_bytes,
1435 context.session_config().spill_compression(),
1436 &self.metrics_set,
1437 context.runtime_env(),
1438 )?;
1439 Ok(Box::pin(RecordBatchStreamAdapter::new(
1440 self.schema(),
1441 futures::stream::once(async move {
1442 while let Some(batch) = input.next().await {
1443 let batch = batch?;
1444 sorter.insert_batch(batch).await?;
1445 }
1446 drop(input);
1447 sorter.sort().await
1448 })
1449 .try_flatten(),
1450 )))
1451 }
1452 }
1453 }
1454
1455 fn metrics(&self) -> Option<MetricsSet> {
1456 Some(self.metrics_set.clone_inner())
1457 }
1458
1459 fn child_stats_requests(&self, partition: Option<usize>) -> Vec<ChildStats> {
1460 let child_partition = if self.preserve_partitioning() {
1461 partition
1462 } else {
1463 None
1464 };
1465 vec![ChildStats::At(child_partition)]
1466 }
1467
1468 fn statistics_from_inputs(
1469 &self,
1470 input_stats: &[Arc<Statistics>],
1471 _args: &StatisticsArgs,
1472 ) -> Result<Arc<Statistics>> {
1473 let stats = input_stats[0].as_ref().clone();
1474 Ok(Arc::new(stats.with_fetch(self.fetch, 0, 1)?))
1475 }
1476
1477 fn with_fetch(&self, limit: Option<usize>) -> Option<Arc<dyn ExecutionPlan>> {
1478 Some(Arc::new(SortExec::with_fetch(self, limit)))
1479 }
1480
1481 fn fetch(&self) -> Option<usize> {
1482 self.fetch
1483 }
1484
1485 fn cardinality_effect(&self) -> CardinalityEffect {
1486 if self.fetch.is_none() {
1487 CardinalityEffect::Equal
1488 } else {
1489 CardinalityEffect::LowerEqual
1490 }
1491 }
1492
1493 fn try_swapping_with_projection(
1497 &self,
1498 projection: &ProjectionExec,
1499 ) -> Result<Option<Arc<dyn ExecutionPlan>>> {
1500 if projection.expr().len() >= projection.input().schema().fields().len() {
1502 return Ok(None);
1503 }
1504
1505 let Some(updated_exprs) = update_ordering(self.expr.clone(), projection.expr())?
1506 else {
1507 return Ok(None);
1508 };
1509
1510 Ok(Some(Arc::new(
1511 SortExec::new(updated_exprs, make_with_child(projection, self.input())?)
1512 .with_fetch(self.fetch())
1513 .with_preserve_partitioning(self.preserve_partitioning()),
1514 )))
1515 }
1516
1517 fn gather_filters_for_pushdown(
1518 &self,
1519 phase: FilterPushdownPhase,
1520 parent_filters: Vec<Arc<dyn PhysicalExpr>>,
1521 config: &datafusion_common::config::ConfigOptions,
1522 ) -> Result<FilterDescription> {
1523 if phase != FilterPushdownPhase::Post {
1524 if self.fetch.is_some() {
1525 return Ok(FilterDescription::all_unsupported(
1526 &parent_filters,
1527 &self.children(),
1528 ));
1529 }
1530 return FilterDescription::from_children(parent_filters, &self.children());
1531 }
1532
1533 let mut child = if self.fetch.is_some() {
1536 ChildFilterDescription::all_unsupported(&parent_filters)
1537 } else {
1538 ChildFilterDescription::from_child(&parent_filters, self.input())?
1539 };
1540
1541 if let Some(filter) = &self.filter
1542 && config.optimizer.enable_topk_dynamic_filter_pushdown
1543 {
1544 child = child.with_self_filter(filter.read().expr());
1545 }
1546
1547 Ok(FilterDescription::new().with_child(child))
1548 }
1549
1550 fn handle_child_pushdown_result(
1551 &self,
1552 _phase: FilterPushdownPhase,
1553 child_pushdown_result: ChildPushdownResult,
1554 _config: &datafusion_common::config::ConfigOptions,
1555 ) -> Result<FilterPushdownPropagation<Arc<dyn ExecutionPlan>>> {
1556 if self.fetch.is_some() {
1568 return Ok(FilterPushdownPropagation::if_all(child_pushdown_result));
1569 }
1570
1571 let unsupported_filters: Vec<Arc<dyn PhysicalExpr>> = child_pushdown_result
1573 .parent_filters
1574 .iter()
1575 .filter(|&f| matches!(f.all(), PushedDown::No))
1576 .map(|f| Arc::clone(&f.filter))
1577 .collect();
1578
1579 if unsupported_filters.is_empty() {
1580 return Ok(FilterPushdownPropagation::if_all(child_pushdown_result));
1582 }
1583
1584 let predicate = datafusion_physical_expr::conjunction(unsupported_filters);
1587 let new_child =
1588 Arc::new(FilterExec::try_new(predicate, Arc::clone(self.input()))?)
1589 as Arc<dyn ExecutionPlan>;
1590 let new_sort = Arc::new(
1591 SortExec::new(self.expr.clone(), new_child)
1592 .with_fetch(self.fetch())
1593 .with_preserve_partitioning(self.preserve_partitioning()),
1594 ) as Arc<dyn ExecutionPlan>;
1595
1596 Ok(FilterPushdownPropagation {
1597 filters: vec![PushedDown::Yes; child_pushdown_result.parent_filters.len()],
1598 updated_node: Some(new_sort),
1599 })
1600 }
1601 #[cfg(feature = "proto")]
1602 fn try_to_proto(
1603 &self,
1604 ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
1605 ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalPlanNode>> {
1606 use datafusion_proto_models::protobuf;
1607 let input = ctx.encode_child(self.input())?;
1608 let expr = self
1609 .expr()
1610 .iter()
1611 .map(|sort_expr| {
1612 let sort_node = Box::new(protobuf::PhysicalSortExprNode {
1613 expr: Some(Box::new(ctx.encode_expr(&sort_expr.expr)?)),
1614 asc: !sort_expr.options.descending,
1615 nulls_first: sort_expr.options.nulls_first,
1616 });
1617 Ok(protobuf::PhysicalExprNode {
1618 expr_id: None,
1619 expr_type: Some(protobuf::physical_expr_node::ExprType::Sort(
1620 sort_node,
1621 )),
1622 })
1623 })
1624 .collect::<Result<Vec<_>>>()?;
1625 let dynamic_filter = self
1626 .dynamic_expressions_produced()
1627 .into_iter()
1628 .next()
1629 .map(|expr| ctx.encode_expr(&expr))
1630 .transpose()?;
1631 Ok(Some(protobuf::PhysicalPlanNode {
1632 physical_plan_type: Some(
1633 protobuf::physical_plan_node::PhysicalPlanType::Sort(Box::new(
1634 protobuf::SortExecNode {
1635 input: Some(Box::new(input)),
1636 expr,
1637 fetch: match self.fetch() {
1638 Some(n) => n as i64,
1639 None => -1,
1640 },
1641 preserve_partitioning: self.preserve_partitioning(),
1642 dynamic_filter,
1643 },
1644 )),
1645 ),
1646 }))
1647 }
1648}
1649
1650#[cfg(feature = "proto")]
1651impl SortExec {
1652 pub fn try_from_proto(
1653 node: &datafusion_proto_models::protobuf::PhysicalPlanNode,
1654 ctx: &crate::proto::ExecutionPlanDecodeCtx<'_>,
1655 ) -> Result<Arc<dyn ExecutionPlan>> {
1656 use datafusion_proto_models::protobuf;
1657 use protobuf::physical_expr_node::ExprType;
1658 let sort = crate::expect_plan_variant!(
1659 node,
1660 protobuf::physical_plan_node::PhysicalPlanType::Sort,
1661 "SortExec",
1662 );
1663 let input =
1664 ctx.decode_required_child(sort.input.as_deref(), "SortExec", "input")?;
1665 let input_schema = input.schema();
1666 let exprs = sort
1667 .expr
1668 .iter()
1669 .map(|expr| {
1670 let Some(ExprType::Sort(sort_expr)) = expr.expr_type.as_ref() else {
1671 return datafusion_common::internal_err!(
1672 "SortExec expr must be a sort expression"
1673 );
1674 };
1675 let expr_node = sort_expr.expr.as_deref().ok_or_else(|| {
1676 internal_datafusion_err!(
1677 "SortExec sort expression is missing its inner expr"
1678 )
1679 })?;
1680 Ok(PhysicalSortExpr {
1681 expr: ctx.decode_expr(expr_node, input_schema.as_ref())?,
1682 options: arrow::compute::SortOptions {
1683 descending: !sort_expr.asc,
1684 nulls_first: sort_expr.nulls_first,
1685 },
1686 })
1687 })
1688 .collect::<Result<Vec<_>>>()?;
1689 let Some(ordering) = LexOrdering::new(exprs) else {
1690 return datafusion_common::internal_err!("SortExec requires an ordering");
1691 };
1692 let fetch = (sort.fetch >= 0).then_some(sort.fetch as usize);
1693 let new_sort = SortExec::new(ordering, input)
1694 .with_fetch(fetch)
1695 .with_preserve_partitioning(sort.preserve_partitioning);
1696
1697 let new_sort = if let Some(df_proto) = &sort.dynamic_filter {
1698 let df_expr =
1699 ctx.decode_expr(df_proto, new_sort.input().schema().as_ref())?;
1700 let df = (df_expr as Arc<dyn std::any::Any + Send + Sync>)
1701 .downcast::<DynamicFilterPhysicalExpr>()
1702 .map_err(|_| {
1703 internal_datafusion_err!(
1704 "SortExec dynamic_filter did not decode to a DynamicFilterPhysicalExpr"
1705 )
1706 })?;
1707 new_sort.with_dynamic_filter_expr(df)?
1708 } else {
1709 new_sort
1710 };
1711
1712 Ok(Arc::new(new_sort))
1713 }
1714}
1715
1716#[cfg(test)]
1717mod tests {
1718 use std::collections::HashMap;
1719 use std::pin::Pin;
1720 use std::task::{Context, Poll};
1721
1722 use super::*;
1723 use crate::coalesce_partitions::CoalescePartitionsExec;
1724 use crate::collect;
1725 use crate::empty::EmptyExec;
1726 use crate::execution_plan::Boundedness;
1727 use crate::expressions::col;
1728 use crate::filter_pushdown::{FilterPushdownPhase, PushedDown};
1729 use crate::test;
1730 use crate::test::TestMemoryExec;
1731 use crate::test::exec::{BlockingExec, assert_strong_count_converges_to_zero};
1732 use crate::test::{assert_is_pending, make_partition};
1733
1734 use arrow::array::*;
1735 use arrow::compute::SortOptions;
1736 use arrow::datatypes::*;
1737 use datafusion_common::ScalarValue;
1738 use datafusion_common::cast::as_primitive_array;
1739 use datafusion_common::config::ConfigOptions;
1740 use datafusion_common::test_util::batches_to_string;
1741 use datafusion_execution::RecordBatchStream;
1742 use datafusion_execution::config::SessionConfig;
1743 use datafusion_execution::memory_pool::{
1744 GreedyMemoryPool, MemoryConsumer, MemoryPool,
1745 };
1746 use datafusion_execution::runtime_env::RuntimeEnvBuilder;
1747 use datafusion_physical_expr::expressions::{Column, Literal};
1748 use datafusion_physical_expr::{DynamicFilterTracking, EquivalenceProperties};
1749
1750 use datafusion_physical_expr_common::metrics::MetricValue;
1751 use futures::{FutureExt, Stream, TryStreamExt};
1752 use insta::assert_snapshot;
1753
1754 #[derive(Debug, Clone)]
1755 pub struct SortedUnboundedExec {
1756 schema: Schema,
1757 batch_size: u64,
1758 cache: Arc<PlanProperties>,
1759 }
1760
1761 impl DisplayAs for SortedUnboundedExec {
1762 fn fmt_as(&self, t: DisplayFormatType, f: &mut Formatter) -> fmt::Result {
1763 match t {
1764 DisplayFormatType::Default
1765 | DisplayFormatType::Verbose
1766 | DisplayFormatType::TreeRender => write!(f, "UnboundableExec",).unwrap(),
1767 }
1768 Ok(())
1769 }
1770 }
1771
1772 impl SortedUnboundedExec {
1773 fn compute_properties(schema: SchemaRef) -> PlanProperties {
1774 let mut eq_properties = EquivalenceProperties::new(schema);
1775 eq_properties.add_ordering([PhysicalSortExpr::new_default(Arc::new(
1776 Column::new("c1", 0),
1777 ))]);
1778 PlanProperties::new(
1779 eq_properties,
1780 Partitioning::UnknownPartitioning(1),
1781 EmissionType::Final,
1782 Boundedness::Unbounded {
1783 requires_infinite_memory: false,
1784 },
1785 )
1786 }
1787 }
1788
1789 impl ExecutionPlan for SortedUnboundedExec {
1790 fn name(&self) -> &'static str {
1791 Self::static_name()
1792 }
1793
1794 fn properties(&self) -> &Arc<PlanProperties> {
1795 &self.cache
1796 }
1797
1798 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
1799 vec![]
1800 }
1801
1802 fn replace_children(
1803 self: Arc<Self>,
1804 _: Vec<Arc<dyn ExecutionPlan>>,
1805 _: ReplaceChildrenOptions,
1806 ) -> Result<Arc<dyn ExecutionPlan>> {
1807 Ok(self)
1808 }
1809
1810 fn apply_expressions(
1811 &self,
1812 _f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
1813 ) -> Result<TreeNodeRecursion> {
1814 Ok(TreeNodeRecursion::Continue)
1815 }
1816
1817 fn with_new_children(
1818 self: Arc<Self>,
1819 children: Vec<Arc<dyn ExecutionPlan>>,
1820 ) -> Result<Arc<dyn ExecutionPlan>> {
1821 self.replace_children(
1822 children,
1823 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
1824 )
1825 }
1826
1827 fn execute(
1828 &self,
1829 _partition: usize,
1830 _context: Arc<TaskContext>,
1831 ) -> Result<SendableRecordBatchStream> {
1832 Ok(Box::pin(SortedUnboundedStream {
1833 schema: Arc::new(self.schema.clone()),
1834 batch_size: self.batch_size,
1835 offset: 0,
1836 }))
1837 }
1838 }
1839
1840 #[derive(Debug)]
1841 pub struct SortedUnboundedStream {
1842 schema: SchemaRef,
1843 batch_size: u64,
1844 offset: u64,
1845 }
1846
1847 impl Stream for SortedUnboundedStream {
1848 type Item = Result<RecordBatch>;
1849
1850 fn poll_next(
1851 mut self: Pin<&mut Self>,
1852 _cx: &mut Context<'_>,
1853 ) -> Poll<Option<Self::Item>> {
1854 let batch = SortedUnboundedStream::create_record_batch(
1855 Arc::clone(&self.schema),
1856 self.offset,
1857 self.batch_size,
1858 );
1859 self.offset += self.batch_size;
1860 Poll::Ready(Some(Ok(batch)))
1861 }
1862 }
1863
1864 impl RecordBatchStream for SortedUnboundedStream {
1865 fn schema(&self) -> SchemaRef {
1866 Arc::clone(&self.schema)
1867 }
1868 }
1869
1870 impl SortedUnboundedStream {
1871 fn create_record_batch(
1872 schema: SchemaRef,
1873 offset: u64,
1874 batch_size: u64,
1875 ) -> RecordBatch {
1876 let values = (0..batch_size).map(|i| offset + i).collect::<Vec<_>>();
1877 let array = UInt64Array::from(values);
1878 let array_ref: ArrayRef = Arc::new(array);
1879 RecordBatch::try_new(schema, vec![array_ref]).unwrap()
1880 }
1881 }
1882
1883 #[tokio::test]
1884 async fn test_in_mem_sort() -> Result<()> {
1885 let task_ctx = Arc::new(TaskContext::default());
1886 let partitions = 4;
1887 let csv = test::scan_partitioned(partitions);
1888 let schema = csv.schema();
1889
1890 let sort_exec = Arc::new(SortExec::new(
1891 [PhysicalSortExpr {
1892 expr: col("i", &schema)?,
1893 options: SortOptions::default(),
1894 }]
1895 .into(),
1896 Arc::new(CoalescePartitionsExec::new(csv)),
1897 ));
1898
1899 let result = collect(sort_exec, Arc::clone(&task_ctx)).await?;
1900
1901 assert_eq!(result.len(), 1);
1902 assert_eq!(result[0].num_rows(), 400);
1903 assert_eq!(
1904 task_ctx.runtime_env().memory_pool.reserved(),
1905 0,
1906 "The sort should have returned all memory used back to the memory manager"
1907 );
1908
1909 Ok(())
1910 }
1911
1912 #[tokio::test]
1916 async fn test_in_mem_sort_coalesced_runs() -> Result<()> {
1917 let task_ctx = Arc::new(
1920 TaskContext::default().with_session_config(
1921 SessionConfig::new()
1922 .with_batch_size(64)
1923 .with_sort_in_place_threshold_bytes(1024),
1924 ),
1925 );
1926
1927 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, true)]));
1928
1929 let num_batches = 40;
1932 let rows_per_batch = 50;
1933 let mut all_values: Vec<Option<i32>> = Vec::new();
1934 let mut batches = Vec::with_capacity(num_batches);
1935 for b in 0..num_batches {
1936 let mut col_values: Vec<Option<i32>> = Vec::with_capacity(rows_per_batch);
1937 for r in 0..rows_per_batch {
1938 let idx = (b * rows_per_batch + r) as i64;
1939 let scrambled = ((idx.wrapping_mul(2_654_435_761)) % 1000) as i32;
1941 let v = if idx % 7 == 0 { None } else { Some(scrambled) };
1942 col_values.push(v);
1943 all_values.push(v);
1944 }
1945 let batch = RecordBatch::try_new(
1946 Arc::clone(&schema),
1947 vec![Arc::new(Int32Array::from(col_values))],
1948 )?;
1949 batches.push(batch);
1950 }
1951 let total_rows = num_batches * rows_per_batch;
1952
1953 let options = SortOptions::default();
1954 let sort_exec = Arc::new(SortExec::new(
1955 [PhysicalSortExpr {
1956 expr: col("a", &schema)?,
1957 options,
1958 }]
1959 .into(),
1960 TestMemoryExec::try_new_exec(
1961 std::slice::from_ref(&batches),
1962 Arc::clone(&schema),
1963 None,
1964 )?,
1965 ));
1966
1967 let result = collect(
1968 Arc::clone(&sort_exec) as Arc<dyn ExecutionPlan>,
1969 Arc::clone(&task_ctx),
1970 )
1971 .await?;
1972
1973 let mut got: Vec<Option<i32>> = Vec::with_capacity(total_rows);
1975 for batch in &result {
1976 let arr = as_primitive_array::<Int32Type>(batch.column(0))?;
1977 for i in 0..arr.len() {
1978 got.push(if arr.is_null(i) {
1979 None
1980 } else {
1981 Some(arr.value(i))
1982 });
1983 }
1984 }
1985 assert_eq!(got.len(), total_rows, "row count must be preserved");
1986
1987 let mut expected = all_values.clone();
1990 expected.sort_by(|a, b| match (a, b) {
1991 (None, None) => std::cmp::Ordering::Equal,
1992 (None, Some(_)) => std::cmp::Ordering::Less, (Some(_), None) => std::cmp::Ordering::Greater,
1994 (Some(x), Some(y)) => x.cmp(y),
1995 });
1996
1997 assert_eq!(
1998 got, expected,
1999 "coalesced-run sort output must be totally ordered"
2000 );
2001 assert_eq!(
2002 task_ctx.runtime_env().memory_pool.reserved(),
2003 0,
2004 "The sort should have returned all memory used back to the memory manager"
2005 );
2006
2007 Ok(())
2008 }
2009
2010 #[tokio::test]
2011 async fn test_sort_spill() -> Result<()> {
2012 let session_config = SessionConfig::new();
2014 let sort_spill_reservation_bytes = session_config
2015 .options()
2016 .execution
2017 .sort_spill_reservation_bytes;
2018 let runtime = RuntimeEnvBuilder::new()
2019 .with_memory_limit(sort_spill_reservation_bytes + 12288, 1.0)
2020 .build_arc()?;
2021 let task_ctx = Arc::new(
2022 TaskContext::default()
2023 .with_session_config(session_config)
2024 .with_runtime(runtime),
2025 );
2026
2027 let partitions = 100;
2031 let input = test::scan_partitioned(partitions);
2032 let schema = input.schema();
2033
2034 let sort_exec = Arc::new(SortExec::new(
2035 [PhysicalSortExpr {
2036 expr: col("i", &schema)?,
2037 options: SortOptions::default(),
2038 }]
2039 .into(),
2040 Arc::new(CoalescePartitionsExec::new(input)),
2041 ));
2042
2043 let result = collect(
2044 Arc::clone(&sort_exec) as Arc<dyn ExecutionPlan>,
2045 Arc::clone(&task_ctx),
2046 )
2047 .await?;
2048
2049 assert_eq!(result.len(), 2);
2050
2051 let metrics = sort_exec.metrics().unwrap();
2053
2054 assert_eq!(metrics.output_rows().unwrap(), 10000);
2055 assert!(metrics.elapsed_compute().unwrap() > 0);
2056
2057 let spill_count = metrics.spill_count().unwrap();
2058 let spilled_rows = metrics.spilled_rows().unwrap();
2059 let spilled_bytes = metrics.spilled_bytes().unwrap();
2060 assert!((3..=10).contains(&spill_count));
2064 assert!((9000..=10000).contains(&spilled_rows));
2065 assert!((38000..=44000).contains(&spilled_bytes));
2066
2067 let columns = result[0].columns();
2068
2069 let i = as_primitive_array::<Int32Type>(&columns[0])?;
2070 assert_eq!(i.value(0), 0);
2071 assert_eq!(i.value(i.len() - 1), 81);
2072 assert_eq!(
2073 task_ctx.runtime_env().memory_pool.reserved(),
2074 0,
2075 "The sort should have returned all memory used back to the memory manager"
2076 );
2077
2078 Ok(())
2079 }
2080
2081 #[tokio::test]
2082 async fn test_batch_reservation_error() -> Result<()> {
2083 let merge_reservation: usize = 0; let session_config =
2087 SessionConfig::new().with_sort_spill_reservation_bytes(merge_reservation);
2088
2089 let plan = test::scan_partitioned(1);
2090
2091 let expected_batch_reservation = {
2093 let temp_ctx = Arc::new(TaskContext::default());
2094 let mut stream = plan.execute(0, Arc::clone(&temp_ctx))?;
2095 let first_batch = stream.next().await.unwrap()?;
2096 get_reserved_bytes_for_record_batch(&first_batch)?
2097 };
2098
2099 let memory_limit: usize = expected_batch_reservation + merge_reservation - 1;
2101
2102 let runtime = RuntimeEnvBuilder::new()
2103 .with_memory_limit(memory_limit, 1.0)
2104 .build_arc()?;
2105 let task_ctx = Arc::new(
2106 TaskContext::default()
2107 .with_session_config(session_config)
2108 .with_runtime(runtime),
2109 );
2110
2111 {
2113 let mut stream = plan.execute(0, Arc::clone(&task_ctx))?;
2114 let first_batch = stream.next().await.unwrap()?;
2115 let batch_reservation = get_reserved_bytes_for_record_batch(&first_batch)?;
2116
2117 assert_eq!(batch_reservation, expected_batch_reservation);
2118 assert!(memory_limit < (merge_reservation + batch_reservation));
2119 }
2120
2121 let sort_exec = Arc::new(SortExec::new(
2122 [PhysicalSortExpr::new_default(col("i", &plan.schema())?)].into(),
2123 plan,
2124 ));
2125
2126 let result = collect(Arc::clone(&sort_exec) as _, Arc::clone(&task_ctx)).await;
2127
2128 let err = result.unwrap_err();
2129 assert!(
2130 matches!(err, DataFusionError::Context(..)),
2131 "Assertion failed: expected a Context error, but got: {err:?}"
2132 );
2133
2134 assert!(
2136 matches!(err.find_root(), DataFusionError::ResourcesExhausted(_)),
2137 "Assertion failed: expected a ResourcesExhausted error, but got: {err:?}"
2138 );
2139
2140 let config_vector = vec![
2142 "datafusion.runtime.memory_limit",
2143 "datafusion.execution.sort_spill_reservation_bytes",
2144 ];
2145 let error_message = err.message().to_string();
2146 for config in config_vector.into_iter() {
2147 assert!(
2148 error_message.as_str().contains(config),
2149 "Config: '{}' should be contained in error message: {}.",
2150 config,
2151 error_message.as_str()
2152 );
2153 }
2154
2155 Ok(())
2156 }
2157
2158 #[tokio::test]
2159 async fn test_sort_spill_utf8_strings() -> Result<()> {
2160 let session_config = SessionConfig::new()
2161 .with_batch_size(100)
2162 .with_sort_in_place_threshold_bytes(20 * 1024)
2163 .with_sort_spill_reservation_bytes(100 * 1024);
2164 let runtime = RuntimeEnvBuilder::new()
2165 .with_memory_limit(500 * 1024, 1.0)
2166 .build_arc()?;
2167 let task_ctx = Arc::new(
2168 TaskContext::default()
2169 .with_session_config(session_config)
2170 .with_runtime(runtime),
2171 );
2172
2173 let input = test::scan_partitioned_utf8(200);
2177 let schema = input.schema();
2178
2179 let sort_exec = Arc::new(SortExec::new(
2180 [PhysicalSortExpr {
2181 expr: col("i", &schema)?,
2182 options: SortOptions::default(),
2183 }]
2184 .into(),
2185 Arc::new(CoalescePartitionsExec::new(input)),
2186 ));
2187
2188 let result = collect(Arc::clone(&sort_exec) as _, Arc::clone(&task_ctx)).await?;
2189
2190 let num_rows = result.iter().map(|batch| batch.num_rows()).sum::<usize>();
2191 assert_eq!(num_rows, 20000);
2192
2193 let metrics = sort_exec.metrics().unwrap();
2195
2196 assert_eq!(metrics.output_rows().unwrap(), 20000);
2197 assert!(metrics.elapsed_compute().unwrap() > 0);
2198
2199 let spill_count = metrics.spill_count().unwrap();
2200 let spilled_rows = metrics.spilled_rows().unwrap();
2201 let spilled_bytes = metrics.spilled_bytes().unwrap();
2202
2203 assert!((4..=8).contains(&spill_count));
2217 assert!((15000..=20000).contains(&spilled_rows));
2218 assert!((900000..=1000000).contains(&spilled_bytes));
2219
2220 let concated_result = concat_batches(&schema, &result)?;
2222 let columns = concated_result.columns();
2223 let string_array = as_string_array(&columns[0]);
2224 for i in 0..string_array.len() - 1 {
2225 assert!(string_array.value(i) <= string_array.value(i + 1));
2226 }
2227
2228 assert_eq!(
2229 task_ctx.runtime_env().memory_pool.reserved(),
2230 0,
2231 "The sort should have returned all memory used back to the memory manager"
2232 );
2233
2234 Ok(())
2235 }
2236
2237 #[tokio::test]
2238 async fn test_sort_fetch_memory_calculation() -> Result<()> {
2239 let avg_batch_size = 400;
2241 let partitions = 4;
2242
2243 let test_options = vec![
2245 (None, true),
2248 (Some(1), false),
2251 ];
2252
2253 for (fetch, expect_spillage) in test_options {
2254 let session_config = SessionConfig::new();
2255 let sort_spill_reservation_bytes = session_config
2256 .options()
2257 .execution
2258 .sort_spill_reservation_bytes;
2259
2260 let runtime = RuntimeEnvBuilder::new()
2261 .with_memory_limit(
2262 sort_spill_reservation_bytes + avg_batch_size * (partitions - 1),
2263 1.0,
2264 )
2265 .build_arc()?;
2266 let task_ctx = Arc::new(
2267 TaskContext::default()
2268 .with_runtime(runtime)
2269 .with_session_config(session_config),
2270 );
2271
2272 let csv = test::scan_partitioned(partitions);
2273 let schema = csv.schema();
2274
2275 let sort_exec = Arc::new(
2276 SortExec::new(
2277 [PhysicalSortExpr {
2278 expr: col("i", &schema)?,
2279 options: SortOptions::default(),
2280 }]
2281 .into(),
2282 Arc::new(CoalescePartitionsExec::new(csv)),
2283 )
2284 .with_fetch(fetch),
2285 );
2286
2287 let result =
2288 collect(Arc::clone(&sort_exec) as _, Arc::clone(&task_ctx)).await?;
2289 assert_eq!(result.len(), 1);
2290
2291 let metrics = sort_exec.metrics().unwrap();
2292 let did_it_spill = metrics.spill_count().unwrap_or(0) > 0;
2293 assert_eq!(did_it_spill, expect_spillage, "with fetch: {fetch:?}");
2294 }
2295 Ok(())
2296 }
2297
2298 #[tokio::test]
2299 async fn test_sort_memory_reduction_per_batch() -> Result<()> {
2300 let batch_size = 50; let num_rows = 1000; let task_ctx = Arc::new(
2309 TaskContext::default().with_session_config(
2310 SessionConfig::new()
2311 .with_batch_size(batch_size)
2312 .with_sort_in_place_threshold_bytes(usize::MAX), ),
2314 );
2315
2316 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)]));
2317
2318 let mut values: Vec<i32> = (0..num_rows).collect();
2320 values.reverse();
2321
2322 let input_batch = RecordBatch::try_new(
2323 Arc::clone(&schema),
2324 vec![Arc::new(Int32Array::from(values))],
2325 )?;
2326
2327 let batches = vec![input_batch];
2328
2329 let sort_exec = Arc::new(SortExec::new(
2330 [PhysicalSortExpr {
2331 expr: Arc::new(Column::new("a", 0)),
2332 options: SortOptions::default(),
2333 }]
2334 .into(),
2335 TestMemoryExec::try_new_exec(
2336 std::slice::from_ref(&batches),
2337 Arc::clone(&schema),
2338 None,
2339 )?,
2340 ));
2341
2342 let mut stream = sort_exec.execute(0, Arc::clone(&task_ctx))?;
2343
2344 let mut previous_reserved = task_ctx.runtime_env().memory_pool.reserved();
2345 let mut batch_count = 0;
2346
2347 while let Some(result) = stream.next().await {
2349 let batch = result?;
2350 batch_count += 1;
2351
2352 assert!(batch.num_rows() > 0, "Batch should not be empty");
2354
2355 let current_reserved = task_ctx.runtime_env().memory_pool.reserved();
2356
2357 if batch_count > 1 {
2360 assert!(
2361 current_reserved <= previous_reserved,
2362 "Memory reservation should decrease or stay same as batches are emitted. \
2363 Batch {batch_count}: previous={previous_reserved}, current={current_reserved}"
2364 );
2365 }
2366
2367 previous_reserved = current_reserved;
2368 }
2369
2370 assert!(
2371 batch_count > 1,
2372 "Expected multiple batches to be emitted, got {batch_count}"
2373 );
2374
2375 assert_eq!(
2377 task_ctx.runtime_env().memory_pool.reserved(),
2378 0,
2379 "All memory should be returned after consuming all batches"
2380 );
2381
2382 Ok(())
2383 }
2384
2385 #[tokio::test]
2386 async fn test_sort_metadata() -> Result<()> {
2387 let task_ctx = Arc::new(TaskContext::default());
2388 let field_metadata: HashMap<String, String> =
2389 vec![("foo".to_string(), "bar".to_string())]
2390 .into_iter()
2391 .collect();
2392 let schema_metadata: HashMap<String, String> =
2393 vec![("baz".to_string(), "barf".to_string())]
2394 .into_iter()
2395 .collect();
2396
2397 let mut field = Field::new("field_name", DataType::UInt64, true);
2398 field.set_metadata(field_metadata.clone());
2399 let schema = Schema::new_with_metadata(vec![field], schema_metadata.clone());
2400 let schema = Arc::new(schema);
2401
2402 let data: ArrayRef =
2403 Arc::new(vec![3, 2, 1].into_iter().map(Some).collect::<UInt64Array>());
2404
2405 let batch = RecordBatch::try_new(Arc::clone(&schema), vec![data])?;
2406 let input =
2407 TestMemoryExec::try_new_exec(&[vec![batch]], Arc::clone(&schema), None)?;
2408
2409 let sort_exec = Arc::new(SortExec::new(
2410 [PhysicalSortExpr {
2411 expr: col("field_name", &schema)?,
2412 options: SortOptions::default(),
2413 }]
2414 .into(),
2415 input,
2416 ));
2417
2418 let result: Vec<RecordBatch> = collect(sort_exec, task_ctx).await?;
2419
2420 let expected_data: ArrayRef =
2421 Arc::new(vec![1, 2, 3].into_iter().map(Some).collect::<UInt64Array>());
2422 let expected_batch =
2423 RecordBatch::try_new(Arc::clone(&schema), vec![expected_data])?;
2424
2425 assert_eq!(&vec![expected_batch], &result);
2427
2428 assert_eq!(result[0].schema().fields()[0].metadata(), &field_metadata);
2430 assert_eq!(result[0].schema().metadata(), &schema_metadata);
2431
2432 Ok(())
2433 }
2434
2435 #[tokio::test]
2436 async fn test_lex_sort_by_mixed_types() -> Result<()> {
2437 let task_ctx = Arc::new(TaskContext::default());
2438 let schema = Arc::new(Schema::new(vec![
2439 Field::new("a", DataType::Int32, true),
2440 Field::new(
2441 "b",
2442 DataType::List(Arc::new(Field::new_list_field(DataType::Int32, true))),
2443 true,
2444 ),
2445 ]));
2446
2447 let batch = RecordBatch::try_new(
2449 Arc::clone(&schema),
2450 vec![
2451 Arc::new(Int32Array::from(vec![Some(2), None, Some(1), Some(2)])),
2452 Arc::new(ListArray::from_iter_primitive::<Int32Type, _, _>(vec![
2453 Some(vec![Some(3)]),
2454 Some(vec![Some(1)]),
2455 Some(vec![Some(6), None]),
2456 Some(vec![Some(5)]),
2457 ])),
2458 ],
2459 )?;
2460
2461 let sort_exec = Arc::new(SortExec::new(
2462 [
2463 PhysicalSortExpr {
2464 expr: col("a", &schema)?,
2465 options: SortOptions {
2466 descending: false,
2467 nulls_first: true,
2468 },
2469 },
2470 PhysicalSortExpr {
2471 expr: col("b", &schema)?,
2472 options: SortOptions {
2473 descending: true,
2474 nulls_first: false,
2475 },
2476 },
2477 ]
2478 .into(),
2479 TestMemoryExec::try_new_exec(&[vec![batch]], Arc::clone(&schema), None)?,
2480 ));
2481
2482 assert_eq!(DataType::Int32, *sort_exec.schema().field(0).data_type());
2483 assert_eq!(
2484 DataType::List(Arc::new(Field::new_list_field(DataType::Int32, true))),
2485 *sort_exec.schema().field(1).data_type()
2486 );
2487
2488 let result: Vec<RecordBatch> =
2489 collect(Arc::clone(&sort_exec) as Arc<dyn ExecutionPlan>, task_ctx).await?;
2490 let metrics = sort_exec.metrics().unwrap();
2491 assert!(metrics.elapsed_compute().unwrap() > 0);
2492 assert_eq!(metrics.output_rows().unwrap(), 4);
2493 assert_eq!(result.len(), 1);
2494
2495 let expected = RecordBatch::try_new(
2496 schema,
2497 vec![
2498 Arc::new(Int32Array::from(vec![None, Some(1), Some(2), Some(2)])),
2499 Arc::new(ListArray::from_iter_primitive::<Int32Type, _, _>(vec![
2500 Some(vec![Some(1)]),
2501 Some(vec![Some(6), None]),
2502 Some(vec![Some(5)]),
2503 Some(vec![Some(3)]),
2504 ])),
2505 ],
2506 )?;
2507
2508 assert_eq!(expected, result[0]);
2509
2510 Ok(())
2511 }
2512
2513 #[tokio::test]
2514 async fn test_lex_sort_by_float() -> Result<()> {
2515 let task_ctx = Arc::new(TaskContext::default());
2516 let schema = Arc::new(Schema::new(vec![
2517 Field::new("a", DataType::Float32, true),
2518 Field::new("b", DataType::Float64, true),
2519 ]));
2520
2521 let batch = RecordBatch::try_new(
2523 Arc::clone(&schema),
2524 vec![
2525 Arc::new(Float32Array::from(vec![
2526 Some(f32::NAN),
2527 None,
2528 None,
2529 Some(f32::NAN),
2530 Some(1.0_f32),
2531 Some(1.0_f32),
2532 Some(2.0_f32),
2533 Some(3.0_f32),
2534 ])),
2535 Arc::new(Float64Array::from(vec![
2536 Some(200.0_f64),
2537 Some(20.0_f64),
2538 Some(10.0_f64),
2539 Some(100.0_f64),
2540 Some(f64::NAN),
2541 None,
2542 None,
2543 Some(f64::NAN),
2544 ])),
2545 ],
2546 )?;
2547
2548 let sort_exec = Arc::new(SortExec::new(
2549 [
2550 PhysicalSortExpr {
2551 expr: col("a", &schema)?,
2552 options: SortOptions {
2553 descending: true,
2554 nulls_first: true,
2555 },
2556 },
2557 PhysicalSortExpr {
2558 expr: col("b", &schema)?,
2559 options: SortOptions {
2560 descending: false,
2561 nulls_first: false,
2562 },
2563 },
2564 ]
2565 .into(),
2566 TestMemoryExec::try_new_exec(&[vec![batch]], schema, None)?,
2567 ));
2568
2569 assert_eq!(DataType::Float32, *sort_exec.schema().field(0).data_type());
2570 assert_eq!(DataType::Float64, *sort_exec.schema().field(1).data_type());
2571
2572 let result: Vec<RecordBatch> =
2573 collect(Arc::clone(&sort_exec) as Arc<dyn ExecutionPlan>, task_ctx).await?;
2574 let metrics = sort_exec.metrics().unwrap();
2575 assert!(metrics.elapsed_compute().unwrap() > 0);
2576 assert_eq!(metrics.output_rows().unwrap(), 8);
2577 assert_eq!(result.len(), 1);
2578
2579 let columns = result[0].columns();
2580
2581 assert_eq!(DataType::Float32, *columns[0].data_type());
2582 assert_eq!(DataType::Float64, *columns[1].data_type());
2583
2584 let a = as_primitive_array::<Float32Type>(&columns[0])?;
2585 let b = as_primitive_array::<Float64Type>(&columns[1])?;
2586
2587 let result: Vec<(Option<String>, Option<String>)> = (0..result[0].num_rows())
2589 .map(|i| {
2590 let aval = if a.is_valid(i) {
2591 Some(a.value(i).to_string())
2592 } else {
2593 None
2594 };
2595 let bval = if b.is_valid(i) {
2596 Some(b.value(i).to_string())
2597 } else {
2598 None
2599 };
2600 (aval, bval)
2601 })
2602 .collect();
2603
2604 let expected: Vec<(Option<String>, Option<String>)> = vec![
2605 (None, Some("10".to_owned())),
2606 (None, Some("20".to_owned())),
2607 (Some("NaN".to_owned()), Some("100".to_owned())),
2608 (Some("NaN".to_owned()), Some("200".to_owned())),
2609 (Some("3".to_owned()), Some("NaN".to_owned())),
2610 (Some("2".to_owned()), None),
2611 (Some("1".to_owned()), Some("NaN".to_owned())),
2612 (Some("1".to_owned()), None),
2613 ];
2614
2615 assert_eq!(expected, result);
2616
2617 Ok(())
2618 }
2619
2620 #[tokio::test]
2621 async fn test_drop_cancel() -> Result<()> {
2622 let task_ctx = Arc::new(TaskContext::default());
2623 let schema =
2624 Arc::new(Schema::new(vec![Field::new("a", DataType::Float32, true)]));
2625
2626 let blocking_exec = Arc::new(BlockingExec::new(Arc::clone(&schema), 1));
2627 let refs = blocking_exec.refs();
2628 let sort_exec = Arc::new(SortExec::new(
2629 [PhysicalSortExpr {
2630 expr: col("a", &schema)?,
2631 options: SortOptions::default(),
2632 }]
2633 .into(),
2634 blocking_exec,
2635 ));
2636
2637 let fut = collect(sort_exec, Arc::clone(&task_ctx));
2638 let mut fut = fut.boxed();
2639
2640 assert_is_pending(&mut fut);
2641 drop(fut);
2642 assert_strong_count_converges_to_zero(refs).await;
2643
2644 assert_eq!(
2645 task_ctx.runtime_env().memory_pool.reserved(),
2646 0,
2647 "The sort should have returned all memory used back to the memory manager"
2648 );
2649
2650 Ok(())
2651 }
2652
2653 #[test]
2654 fn test_empty_sort_batch() {
2655 let schema = Arc::new(Schema::empty());
2656 let options = RecordBatchOptions::new().with_row_count(Some(1));
2657 let batch =
2658 RecordBatch::try_new_with_options(Arc::clone(&schema), vec![], &options)
2659 .unwrap();
2660
2661 let expressions = [PhysicalSortExpr {
2662 expr: Arc::new(Literal::new(ScalarValue::Int64(Some(1)))),
2663 options: SortOptions::default(),
2664 }]
2665 .into();
2666
2667 let result = sort_batch(&batch, &expressions, None).unwrap();
2668 assert_eq!(result.num_rows(), 1);
2669 }
2670
2671 #[tokio::test]
2672 async fn topk_unbounded_source() -> Result<()> {
2673 let task_ctx = Arc::new(TaskContext::default());
2674 let schema = Schema::new(vec![Field::new("c1", DataType::UInt64, false)]);
2675 let source = SortedUnboundedExec {
2676 schema: schema.clone(),
2677 batch_size: 2,
2678 cache: Arc::new(SortedUnboundedExec::compute_properties(Arc::new(
2679 schema.clone(),
2680 ))),
2681 };
2682 let mut plan = SortExec::new(
2683 [PhysicalSortExpr::new_default(Arc::new(Column::new(
2684 "c1", 0,
2685 )))]
2686 .into(),
2687 Arc::new(source),
2688 );
2689 plan = plan.with_fetch(Some(9));
2690
2691 let batches = collect(Arc::new(plan), task_ctx).await?;
2692 assert_snapshot!(batches_to_string(&batches), @r"
2693 +----+
2694 | c1 |
2695 +----+
2696 | 0 |
2697 | 1 |
2698 | 2 |
2699 | 3 |
2700 | 4 |
2701 | 5 |
2702 | 6 |
2703 | 7 |
2704 | 8 |
2705 +----+
2706 ");
2707 Ok(())
2708 }
2709
2710 #[tokio::test]
2711 async fn should_return_stream_with_batches_in_the_requested_size_and_update_metrics()
2712 -> Result<()> {
2713 let batch_size = 100;
2714
2715 let create_task_ctx = |_: &[RecordBatch]| {
2716 TaskContext::default().with_session_config(
2717 SessionConfig::new()
2718 .with_batch_size(batch_size)
2719 .with_sort_in_place_threshold_bytes(usize::MAX),
2720 )
2721 };
2722
2723 test_sort_output_batch_size_and_base_metrics(10, batch_size / 4, create_task_ctx)
2725 .await?;
2726
2727 test_sort_output_batch_size_and_base_metrics(10, batch_size + 7, create_task_ctx)
2729 .await?;
2730
2731 test_sort_output_batch_size_and_base_metrics(10, batch_size * 3, create_task_ctx)
2733 .await?;
2734
2735 Ok(())
2736 }
2737
2738 #[tokio::test]
2739 async fn should_return_stream_with_batches_in_the_requested_size_and_update_metrics_when_sorting_in_place()
2740 -> Result<()> {
2741 let batch_size = 100;
2742
2743 let create_task_ctx = |_: &[RecordBatch]| {
2744 TaskContext::default().with_session_config(
2745 SessionConfig::new()
2746 .with_batch_size(batch_size)
2747 .with_sort_in_place_threshold_bytes(usize::MAX - 1),
2748 )
2749 };
2750
2751 {
2753 let metrics = test_sort_output_batch_size_and_base_metrics(
2754 10,
2755 batch_size / 4,
2756 create_task_ctx,
2757 )
2758 .await?;
2759
2760 assert_eq!(
2761 metrics.spill_count(),
2762 Some(0),
2763 "Expected no spills when sorting in place"
2764 );
2765 }
2766
2767 {
2769 let metrics = test_sort_output_batch_size_and_base_metrics(
2770 10,
2771 batch_size + 7,
2772 create_task_ctx,
2773 )
2774 .await?;
2775
2776 assert_eq!(
2777 metrics.spill_count(),
2778 Some(0),
2779 "Expected no spills when sorting in place"
2780 );
2781 }
2782
2783 {
2785 let metrics = test_sort_output_batch_size_and_base_metrics(
2786 10,
2787 batch_size * 3,
2788 create_task_ctx,
2789 )
2790 .await?;
2791
2792 assert_eq!(
2793 metrics.spill_count(),
2794 Some(0),
2795 "Expected no spills when sorting in place"
2796 );
2797 }
2798
2799 Ok(())
2800 }
2801
2802 #[tokio::test]
2803 async fn should_return_stream_with_batches_in_the_requested_size_and_update_metrics_when_having_a_single_batch()
2804 -> Result<()> {
2805 let batch_size = 100;
2806
2807 let create_task_ctx = |_: &[RecordBatch]| {
2808 TaskContext::default()
2809 .with_session_config(SessionConfig::new().with_batch_size(batch_size))
2810 };
2811
2812 {
2814 let metrics = test_sort_output_batch_size_and_base_metrics(
2815 1,
2817 batch_size / 4,
2818 create_task_ctx,
2819 )
2820 .await?;
2821
2822 assert_eq!(
2823 metrics.spill_count(),
2824 Some(0),
2825 "Expected no spills when sorting in place"
2826 );
2827 }
2828
2829 {
2831 let metrics = test_sort_output_batch_size_and_base_metrics(
2832 1,
2834 batch_size + 7,
2835 create_task_ctx,
2836 )
2837 .await?;
2838
2839 assert_eq!(
2840 metrics.spill_count(),
2841 Some(0),
2842 "Expected no spills when sorting in place"
2843 );
2844 }
2845
2846 {
2848 let metrics = test_sort_output_batch_size_and_base_metrics(
2849 1,
2851 batch_size * 3,
2852 create_task_ctx,
2853 )
2854 .await?;
2855
2856 assert_eq!(
2857 metrics.spill_count(),
2858 Some(0),
2859 "Expected no spills when sorting in place"
2860 );
2861 }
2862
2863 Ok(())
2864 }
2865
2866 #[tokio::test]
2867 async fn should_return_stream_with_batches_in_the_requested_size_and_update_metrics_when_having_to_spill()
2868 -> Result<()> {
2869 let batch_size = 100;
2870
2871 let create_task_ctx = |generated_batches: &[RecordBatch]| {
2872 let batches_memory = generated_batches
2873 .iter()
2874 .map(|b| b.get_array_memory_size())
2875 .sum::<usize>();
2876
2877 TaskContext::default()
2878 .with_session_config(
2879 SessionConfig::new()
2880 .with_batch_size(batch_size)
2881 .with_sort_in_place_threshold_bytes(1)
2883 .with_sort_spill_reservation_bytes(1),
2884 )
2885 .with_runtime(
2886 RuntimeEnvBuilder::default()
2887 .with_memory_limit(batches_memory, 1.0)
2888 .build_arc()
2889 .unwrap(),
2890 )
2891 };
2892
2893 {
2895 let metrics = test_sort_output_batch_size_and_base_metrics(
2896 10,
2897 batch_size / 4,
2898 create_task_ctx,
2899 )
2900 .await?;
2901
2902 assert_ne!(metrics.spill_count().unwrap(), 0, "expected to spill");
2903 }
2904
2905 {
2907 let metrics = test_sort_output_batch_size_and_base_metrics(
2908 10,
2909 batch_size + 7,
2910 create_task_ctx,
2911 )
2912 .await?;
2913
2914 assert_ne!(metrics.spill_count().unwrap(), 0, "expected to spill");
2915 }
2916
2917 {
2919 let metrics = test_sort_output_batch_size_and_base_metrics(
2920 10,
2921 batch_size * 3,
2922 create_task_ctx,
2923 )
2924 .await?;
2925
2926 assert_ne!(metrics.spill_count().unwrap(), 0, "expected to spill");
2927 }
2928
2929 Ok(())
2930 }
2931
2932 async fn test_sort_output_batch_size_and_base_metrics(
2933 number_of_batches: usize,
2934 batch_size_to_generate: usize,
2935 create_task_ctx: impl Fn(&[RecordBatch]) -> TaskContext,
2936 ) -> Result<MetricsSet> {
2937 let batches = (0..number_of_batches)
2938 .map(|_| make_partition(batch_size_to_generate as i32))
2939 .collect::<Vec<_>>();
2940 let task_ctx = create_task_ctx(batches.as_slice());
2941
2942 let output_rows = batches.iter().map(|item| item.num_rows()).sum();
2943
2944 let expected_batch_size = task_ctx.session_config().batch_size();
2945
2946 let schema = batches[0].schema();
2947 let (mut output_batches, metrics) =
2948 run_sort_on_input(task_ctx, "i", batches, schema).await?;
2949
2950 let last_batch = output_batches.pop().unwrap();
2951
2952 for batch in output_batches {
2953 assert_eq!(batch.num_rows(), expected_batch_size);
2954 }
2955
2956 let mut last_expected_batch_size =
2957 (batch_size_to_generate * number_of_batches) % expected_batch_size;
2958 if last_expected_batch_size == 0 {
2959 last_expected_batch_size = expected_batch_size;
2960 }
2961 assert_eq!(last_batch.num_rows(), last_expected_batch_size);
2962
2963 assert_baseline_metrics_for_non_empty_output(
2964 &metrics,
2965 output_rows,
2966 expected_batch_size,
2967 );
2968
2969 Ok(metrics)
2970 }
2971
2972 #[tokio::test]
2973 async fn empty_sort_stream_should_report_end_time() -> Result<()> {
2974 let schema = Arc::new(Schema::new(vec![Field::new("i", DataType::Int32, false)]));
2975 let task_ctx = TaskContext::default();
2976
2977 let (_, metrics) = run_sort_on_input(task_ctx, "i", vec![], schema).await?;
2978
2979 let end_time = metrics
2980 .iter()
2981 .find_map(|item| match item.value() {
2982 MetricValue::EndTimestamp(end) => Some(end),
2983 _ => None,
2984 })
2985 .expect("Must have end time metric since it exists in the baseline");
2986
2987 assert_eq!(
2988 metrics.spill_count().unwrap_or_default(),
2989 0,
2990 "expected to not have spills"
2991 );
2992 assert_ne!(end_time.value(), None);
2993
2994 Ok(())
2995 }
2996
2997 fn assert_baseline_metrics_for_non_empty_output(
2998 metrics: &MetricsSet,
2999 output_rows: usize,
3000 batch_size: usize,
3001 ) {
3002 let end_time = metrics
3003 .iter()
3004 .find_map(|item| match item.value() {
3005 MetricValue::EndTimestamp(end) => Some(end),
3006 _ => None,
3007 })
3008 .expect("Must have end time metric since it exists in the baseline");
3009
3010 assert_ne!(end_time.value(), None);
3011
3012 assert_eq!(metrics.output_rows(), Some(output_rows));
3013
3014 let output_bytes = metrics
3015 .iter()
3016 .find_map(|item| match item.value() {
3017 MetricValue::OutputBytes(total) => Some(total),
3018 _ => None,
3019 })
3020 .expect("Must have output_bytes metric since it exists in the baseline");
3021
3022 assert_ne!(output_bytes.value(), 0_usize);
3023
3024 let output_batches = metrics
3025 .iter()
3026 .find_map(|item| match item.value() {
3027 MetricValue::OutputBatches(total) => Some(total),
3028 _ => None,
3029 })
3030 .expect("Must have output_batches metric since it exists in the baseline");
3031
3032 assert_eq!(output_batches.value(), output_rows.div_ceil(batch_size));
3033 }
3034
3035 async fn run_sort_on_input(
3036 task_ctx: TaskContext,
3037 order_by_col: &str,
3038 batches: Vec<RecordBatch>,
3039 schema: SchemaRef,
3040 ) -> Result<(Vec<RecordBatch>, MetricsSet)> {
3041 let task_ctx = Arc::new(task_ctx);
3042
3043 let ordering: LexOrdering = [PhysicalSortExpr {
3045 expr: col(order_by_col, &schema)?,
3046 options: SortOptions {
3047 descending: false,
3048 nulls_first: true,
3049 },
3050 }]
3051 .into();
3052 let sort_exec: Arc<dyn ExecutionPlan> = Arc::new(SortExec::new(
3053 ordering.clone(),
3054 TestMemoryExec::try_new_exec(
3055 std::slice::from_ref(&batches),
3056 Arc::clone(&schema),
3057 None,
3058 )?,
3059 ));
3060
3061 let sorted_batches =
3062 collect(Arc::clone(&sort_exec), Arc::clone(&task_ctx)).await?;
3063
3064 let metrics = sort_exec.metrics().expect("sort have metrics");
3065
3066 {
3068 let input_batches_concat = concat_batches(&schema, &batches)?;
3069 let sorted_input_batch = sort_batch(&input_batches_concat, &ordering, None)?;
3070
3071 let sorted_batches_concat = concat_batches(&schema, &sorted_batches)?;
3072
3073 assert_eq!(sorted_input_batch, sorted_batches_concat);
3074 }
3075
3076 Ok((sorted_batches, metrics))
3077 }
3078
3079 #[tokio::test]
3080 async fn test_sort_batch_chunked_basic() -> Result<()> {
3081 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)]));
3082
3083 let mut values: Vec<i32> = (0..1000).collect();
3085 values.reverse();
3087
3088 let batch = RecordBatch::try_new(
3089 Arc::clone(&schema),
3090 vec![Arc::new(Int32Array::from(values))],
3091 )?;
3092
3093 let expressions: LexOrdering =
3094 [PhysicalSortExpr::new_default(Arc::new(Column::new("a", 0)))].into();
3095
3096 let result_batches = sort_batch_chunked(&batch, &expressions, 250)?;
3098
3099 assert_eq!(result_batches.len(), 4);
3101
3102 let mut total_rows = 0;
3104 for (i, batch) in result_batches.iter().enumerate() {
3105 assert!(
3106 batch.num_rows() <= 250,
3107 "Batch {} has {} rows, expected <= 250",
3108 i,
3109 batch.num_rows()
3110 );
3111 total_rows += batch.num_rows();
3112 }
3113
3114 assert_eq!(total_rows, 1000);
3116
3117 let concatenated = concat_batches(&schema, &result_batches)?;
3119 let array = as_primitive_array::<Int32Type>(concatenated.column(0))?;
3120 for i in 0..array.len() - 1 {
3121 assert!(
3122 array.value(i) <= array.value(i + 1),
3123 "Array not sorted at position {}: {} > {}",
3124 i,
3125 array.value(i),
3126 array.value(i + 1)
3127 );
3128 }
3129 assert_eq!(array.value(0), 0);
3130 assert_eq!(array.value(array.len() - 1), 999);
3131
3132 Ok(())
3133 }
3134
3135 #[tokio::test]
3136 async fn test_sort_batch_chunked_smaller_than_batch_size() -> Result<()> {
3137 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)]));
3138
3139 let values: Vec<i32> = (0..50).rev().collect();
3141 let batch = RecordBatch::try_new(
3142 Arc::clone(&schema),
3143 vec![Arc::new(Int32Array::from(values))],
3144 )?;
3145
3146 let expressions: LexOrdering =
3147 [PhysicalSortExpr::new_default(Arc::new(Column::new("a", 0)))].into();
3148
3149 let result_batches = sort_batch_chunked(&batch, &expressions, 100)?;
3151
3152 assert_eq!(result_batches.len(), 1);
3154 assert_eq!(result_batches[0].num_rows(), 50);
3155
3156 let array = as_primitive_array::<Int32Type>(result_batches[0].column(0))?;
3158 for i in 0..array.len() - 1 {
3159 assert!(array.value(i) <= array.value(i + 1));
3160 }
3161 assert_eq!(array.value(0), 0);
3162 assert_eq!(array.value(49), 49);
3163
3164 Ok(())
3165 }
3166
3167 #[tokio::test]
3168 async fn test_sort_batch_chunked_exact_multiple() -> Result<()> {
3169 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)]));
3170
3171 let values: Vec<i32> = (0..1000).rev().collect();
3173 let batch = RecordBatch::try_new(
3174 Arc::clone(&schema),
3175 vec![Arc::new(Int32Array::from(values))],
3176 )?;
3177
3178 let expressions: LexOrdering =
3179 [PhysicalSortExpr::new_default(Arc::new(Column::new("a", 0)))].into();
3180
3181 let result_batches = sort_batch_chunked(&batch, &expressions, 100)?;
3183
3184 assert_eq!(result_batches.len(), 10);
3186 for batch in &result_batches {
3187 assert_eq!(batch.num_rows(), 100);
3188 }
3189
3190 let concatenated = concat_batches(&schema, &result_batches)?;
3192 let array = as_primitive_array::<Int32Type>(concatenated.column(0))?;
3193 for i in 0..array.len() - 1 {
3194 assert!(array.value(i) <= array.value(i + 1));
3195 }
3196
3197 Ok(())
3198 }
3199
3200 #[tokio::test]
3201 async fn test_sort_batch_chunked_empty_batch() -> Result<()> {
3202 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)]));
3203
3204 let batch = RecordBatch::new_empty(Arc::clone(&schema));
3205
3206 let expressions: LexOrdering =
3207 [PhysicalSortExpr::new_default(Arc::new(Column::new("a", 0)))].into();
3208
3209 let result_batches = sort_batch_chunked(&batch, &expressions, 100)?;
3210
3211 assert_eq!(result_batches.len(), 0);
3213
3214 Ok(())
3215 }
3216
3217 #[tokio::test]
3218 async fn test_get_reserved_bytes_for_record_batch_with_sliced_batches() -> Result<()>
3219 {
3220 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)]));
3221
3222 let large_array = Int32Array::from((0..1000).collect::<Vec<i32>>());
3224 let sliced_array = large_array.slice(100, 50); let sliced_batch =
3227 RecordBatch::try_new(Arc::clone(&schema), vec![Arc::new(sliced_array)])?;
3228 let batch =
3229 RecordBatch::try_new(Arc::clone(&schema), vec![Arc::new(large_array)])?;
3230
3231 let sliced_reserved = get_reserved_bytes_for_record_batch(&sliced_batch)?;
3232 let reserved = get_reserved_bytes_for_record_batch(&batch)?;
3233
3234 assert!(reserved > sliced_reserved);
3236
3237 Ok(())
3238 }
3239
3240 #[test]
3241 fn test_with_dynamic_filter() -> Result<()> {
3242 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)]));
3243 let child = Arc::new(EmptyExec::new(Arc::clone(&schema)));
3244
3245 let sort = SortExec::new(
3246 LexOrdering::new(vec![PhysicalSortExpr {
3247 expr: Arc::new(Column::new("a", 0)),
3248 options: SortOptions::default(),
3249 }])
3250 .unwrap(),
3251 child,
3252 )
3253 .with_fetch(Some(10));
3254
3255 let produced = sort.dynamic_expressions_produced();
3257 assert_eq!(produced.len(), 1);
3258 let original_id = produced[0]
3259 .expression_id()
3260 .expect("DynamicFilterPhysicalExpr always has an expression_id");
3261
3262 let new_df = Arc::new(DynamicFilterPhysicalExpr::new(
3264 vec![Arc::new(Column::new("a", 0)) as _],
3265 lit(true),
3266 ));
3267 let new_id = new_df
3268 .expression_id()
3269 .expect("DynamicFilterPhysicalExpr always has an expression_id");
3270 let sort = sort.with_dynamic_filter_expr(Arc::clone(&new_df))?;
3271 let produced = sort.dynamic_expressions_produced();
3272 assert_eq!(produced.len(), 1);
3273 let restored_id = produced[0]
3274 .expression_id()
3275 .expect("DynamicFilterPhysicalExpr always has an expression_id");
3276 assert_eq!(restored_id, new_id);
3277 assert_ne!(restored_id, original_id);
3278 Ok(())
3279 }
3280
3281 async fn emit_sort_partition(
3282 sort: &Arc<SortExec>,
3283 partition: usize,
3284 task_ctx: Arc<TaskContext>,
3285 ) -> Result<()> {
3286 let _batches: Vec<RecordBatch> =
3287 sort.execute(partition, task_ctx)?.try_collect().await?;
3288 Ok(())
3289 }
3290
3291 fn assert_filter_still_waiting(filter: &Arc<DynamicFilterPhysicalExpr>) {
3292 let dynamic_filter_expr: Arc<dyn PhysicalExpr> =
3293 Arc::<DynamicFilterPhysicalExpr>::clone(filter);
3294 assert!(
3295 matches!(
3296 DynamicFilterTracking::classify(&dynamic_filter_expr),
3297 DynamicFilterTracking::Watching(_)
3298 ),
3299 "the shared filter should remain watchable until every partition emits"
3300 );
3301 }
3302
3303 fn dynamic_filter_produced(
3304 plan: &dyn ExecutionPlan,
3305 ) -> Arc<DynamicFilterPhysicalExpr> {
3306 let expr = plan
3307 .dynamic_expressions_produced()
3308 .into_iter()
3309 .next()
3310 .expect("plan should produce a dynamic filter");
3311 (expr as Arc<dyn std::any::Any + Send + Sync>)
3312 .downcast::<DynamicFilterPhysicalExpr>()
3313 .expect("produced expression should be a DynamicFilterPhysicalExpr")
3314 }
3315
3316 #[tokio::test]
3317 async fn test_preserved_topk_filter_waits_for_all_sort_partitions() -> Result<()> {
3318 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)]));
3319 let partitions = vec![
3320 vec![RecordBatch::try_new(
3321 Arc::clone(&schema),
3322 vec![Arc::new(Int32Array::from(vec![3, 1, 2]))],
3323 )?],
3324 vec![RecordBatch::try_new(
3325 Arc::clone(&schema),
3326 vec![Arc::new(Int32Array::from(vec![6, 4, 5]))],
3327 )?],
3328 ];
3329 let input = TestMemoryExec::try_new_exec(&partitions, Arc::clone(&schema), None)?;
3330 let sort = SortExec::new(
3331 [PhysicalSortExpr::new_default(Arc::new(Column::new("a", 0)))].into(),
3332 input,
3333 )
3334 .with_fetch(Some(2))
3337 .with_preserve_partitioning(true);
3338
3339 let dynamic_filter = dynamic_filter_produced(&sort);
3340 let sort = Arc::new(sort);
3341 let task_ctx = Arc::new(TaskContext::default());
3342
3343 emit_sort_partition(&sort, 0, Arc::clone(&task_ctx)).await?;
3344 assert_filter_still_waiting(&dynamic_filter);
3345
3346 emit_sort_partition(&sort, 1, task_ctx).await?;
3347 tokio::time::timeout(
3348 std::time::Duration::from_secs(1),
3349 dynamic_filter.wait_complete(),
3350 )
3351 .await
3352 .expect("the final preserved SortExec partition should complete the filter");
3353
3354 Ok(())
3355 }
3356
3357 #[tokio::test]
3358 async fn test_with_fetch_rebuilds_existing_topk_filter() -> Result<()> {
3359 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)]));
3360 let partitions = vec![
3361 vec![RecordBatch::try_new(
3362 Arc::clone(&schema),
3363 vec![Arc::new(Int32Array::from(vec![3, 1, 2]))],
3364 )?],
3365 vec![RecordBatch::try_new(
3366 Arc::clone(&schema),
3367 vec![Arc::new(Int32Array::from(vec![6, 4, 5]))],
3368 )?],
3369 ];
3370 let input = TestMemoryExec::try_new_exec(&partitions, Arc::clone(&schema), None)?;
3371 let dynamic_filter = Arc::new(DynamicFilterPhysicalExpr::new(
3372 vec![Arc::new(Column::new("a", 0))],
3373 lit(true),
3374 ));
3375 let dynamic_filter_id = dynamic_filter
3376 .expression_id()
3377 .expect("DynamicFilterPhysicalExpr always has an expression_id");
3378 let sort = SortExec::new(
3379 [PhysicalSortExpr::new_default(Arc::new(Column::new("a", 0)))].into(),
3380 input,
3381 )
3382 .with_dynamic_filter_expr(dynamic_filter)?
3383 .with_preserve_partitioning(true)
3384 .with_fetch(Some(2));
3385
3386 let dynamic_filter = dynamic_filter_produced(&sort);
3387 assert_eq!(
3388 dynamic_filter
3389 .expression_id()
3390 .expect("DynamicFilterPhysicalExpr always has an expression_id"),
3391 dynamic_filter_id
3392 );
3393
3394 let sort = Arc::new(sort);
3395 let task_ctx = Arc::new(TaskContext::default());
3396
3397 emit_sort_partition(&sort, 0, Arc::clone(&task_ctx)).await?;
3398 assert_filter_still_waiting(&dynamic_filter);
3399
3400 emit_sort_partition(&sort, 1, task_ctx).await?;
3401 tokio::time::timeout(
3402 std::time::Duration::from_secs(1),
3403 dynamic_filter.wait_complete(),
3404 )
3405 .await
3406 .expect("the final preserved SortExec partition should complete the filter");
3407
3408 Ok(())
3409 }
3410
3411 #[test]
3412 fn test_with_dynamic_filter_rejects_invalid_columns() -> Result<()> {
3413 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)]));
3414 let child = Arc::new(EmptyExec::new(Arc::clone(&schema)));
3415
3416 let sort = SortExec::new(
3417 LexOrdering::new(vec![PhysicalSortExpr {
3418 expr: Arc::new(Column::new("a", 0)),
3419 options: SortOptions::default(),
3420 }])
3421 .unwrap(),
3422 child,
3423 )
3424 .with_fetch(Some(10));
3425
3426 let df = Arc::new(DynamicFilterPhysicalExpr::new(
3428 vec![Arc::new(Column::new("bad", 99)) as _],
3429 lit(true),
3430 ));
3431 assert!(sort.with_dynamic_filter_expr(df).is_err());
3432 Ok(())
3433 }
3434
3435 #[tokio::test]
3452 async fn test_sort_merge_reservation_transferred_not_freed() -> Result<()> {
3453 let sort_spill_reservation_bytes: usize = 10 * 1024; let sort_working_memory: usize = 40 * 1024; let pool_size = sort_spill_reservation_bytes + sort_working_memory;
3459 let pool: Arc<dyn MemoryPool> = Arc::new(GreedyMemoryPool::new(pool_size));
3460
3461 let runtime = RuntimeEnvBuilder::new()
3462 .with_memory_pool(Arc::clone(&pool))
3463 .build_arc()?;
3464
3465 let metrics_set = ExecutionPlanMetricsSet::new();
3466 let schema = Arc::new(Schema::new(vec![Field::new("x", DataType::Int32, false)]));
3467
3468 let mut sorter = ExternalSorter::new(
3469 0,
3470 Arc::clone(&schema),
3471 [PhysicalSortExpr::new_default(Arc::new(Column::new("x", 0)))].into(),
3472 128, sort_spill_reservation_bytes,
3474 usize::MAX, SpillCompression::Uncompressed,
3476 &metrics_set,
3477 Arc::clone(&runtime),
3478 )?;
3479
3480 let num_batches = 200;
3482 for i in 0..num_batches {
3483 let values: Vec<i32> = ((i * 100)..((i + 1) * 100)).rev().collect();
3484 let batch = RecordBatch::try_new(
3485 Arc::clone(&schema),
3486 vec![Arc::new(Int32Array::from(values))],
3487 )?;
3488 sorter.insert_batch(batch).await?;
3489 }
3490
3491 assert!(
3492 sorter.spilled_before(),
3493 "Test requires spilling to exercise the merge path"
3494 );
3495
3496 assert!(
3498 sorter.merge_reservation_size() >= sort_spill_reservation_bytes,
3499 "merge_reservation should hold the pre-reserved bytes before sort()"
3500 );
3501
3502 let merge_stream = sorter.sort().await?;
3508
3509 assert_eq!(
3514 sorter.merge_reservation_size(),
3515 0,
3516 "After sort(), merge_reservation should be 0 (bytes transferred \
3517 to merge stream via take()). If non-zero, the bytes are still \
3518 held by the sorter and will be freed on drop, allowing other \
3519 partitions to steal them."
3520 );
3521
3522 drop(sorter);
3524
3525 let contender = MemoryConsumer::new("CompetingPartition").register(&pool);
3530 let available = pool_size.saturating_sub(pool.reserved());
3531 if available > 0 {
3532 contender.try_grow(available).unwrap();
3533 }
3534
3535 let batches: Vec<RecordBatch> = merge_stream.try_collect().await?;
3540 let total_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
3541 assert_eq!(
3542 total_rows,
3543 (num_batches * 100) as usize,
3544 "Merge stream should produce all rows even under memory contention"
3545 );
3546
3547 let merged = concat_batches(&schema, &batches)?;
3549 let col = merged.column(0).as_primitive::<Int32Type>();
3550 for i in 1..col.len() {
3551 assert!(
3552 col.value(i - 1) <= col.value(i),
3553 "Output should be sorted, but found {} > {} at index {}",
3554 col.value(i - 1),
3555 col.value(i),
3556 i
3557 );
3558 }
3559
3560 drop(contender);
3561 Ok(())
3562 }
3563
3564 fn make_sort_exec_with_fetch(fetch: Option<usize>) -> SortExec {
3565 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)]));
3566 let input = Arc::new(EmptyExec::new(schema));
3567 SortExec::new(
3568 [PhysicalSortExpr::new_default(Arc::new(Column::new("a", 0)))].into(),
3569 input,
3570 )
3571 .with_fetch(fetch)
3572 }
3573
3574 #[test]
3575 fn test_sort_with_fetch_blocks_filter_pushdown() -> Result<()> {
3576 let sort = make_sort_exec_with_fetch(Some(10));
3577 let desc = sort.gather_filters_for_pushdown(
3578 FilterPushdownPhase::Pre,
3579 vec![Arc::new(Column::new("a", 0))],
3580 &ConfigOptions::new(),
3581 )?;
3582 assert!(matches!(
3584 desc.parent_filters()[0][0].discriminant,
3585 PushedDown::No
3586 ));
3587 Ok(())
3588 }
3589
3590 #[test]
3591 fn test_sort_without_fetch_allows_filter_pushdown() -> Result<()> {
3592 let sort = make_sort_exec_with_fetch(None);
3593 let desc = sort.gather_filters_for_pushdown(
3594 FilterPushdownPhase::Pre,
3595 vec![Arc::new(Column::new("a", 0))],
3596 &ConfigOptions::new(),
3597 )?;
3598 assert!(matches!(
3600 desc.parent_filters()[0][0].discriminant,
3601 PushedDown::Yes
3602 ));
3603 Ok(())
3604 }
3605
3606 #[test]
3607 fn test_sort_with_fetch_allows_topk_self_filter_in_post_phase() -> Result<()> {
3608 let sort = make_sort_exec_with_fetch(Some(10));
3609 assert!(sort.filter.is_some(), "TopK filter should be created");
3610
3611 let mut config = ConfigOptions::new();
3612 config.optimizer.enable_topk_dynamic_filter_pushdown = true;
3613 let desc = sort.gather_filters_for_pushdown(
3614 FilterPushdownPhase::Post,
3615 vec![Arc::new(Column::new("a", 0))],
3616 &config,
3617 )?;
3618 assert!(matches!(
3620 desc.parent_filters()[0][0].discriminant,
3621 PushedDown::No
3622 ));
3623 assert_eq!(desc.self_filters()[0].len(), 1);
3625 Ok(())
3626 }
3627}