1use std::fmt::Debug;
55use std::pin::Pin;
56use std::sync::Arc;
57use std::task::{Context, Poll};
58
59use crate::metrics::{BaselineMetrics, ExecutionPlanMetricsSet, MetricsSet};
60use crate::sorts::sort::sort_batch;
61use crate::statistics::{ChildStats, StatisticsArgs};
62use crate::stream::EmptyRecordBatchStream;
63use crate::{
64 ChildrenPropertiesMode, DisplayAs, DisplayFormatType, Distribution, ExecutionPlan,
65 ExecutionPlanProperties, Partitioning, PlanProperties, ReplaceChildrenOptions,
66 SendableRecordBatchStream, Statistics, validate_child_count,
67};
68
69use arrow::compute::concat_batches;
70use arrow::datatypes::SchemaRef;
71use arrow::record_batch::RecordBatch;
72use datafusion_common::Result;
73use datafusion_common::tree_node::TreeNodeRecursion;
74use datafusion_common::utils::evaluate_partition_ranges;
75use datafusion_execution::{RecordBatchStream, TaskContext};
76use datafusion_physical_expr::{LexOrdering, PhysicalExpr};
77
78use futures::{Stream, StreamExt, ready};
79use log::trace;
80
81#[derive(Debug, Clone)]
180pub struct PartialSortExec {
181 pub(crate) input: Arc<dyn ExecutionPlan>,
183 expr: LexOrdering,
185 common_prefix_length: usize,
188 metrics_set: ExecutionPlanMetricsSet,
190 preserve_partitioning: bool,
193 fetch: Option<usize>,
195 cache: Arc<PlanProperties>,
197}
198
199impl PartialSortExec {
200 pub fn new(
202 expr: LexOrdering,
203 input: Arc<dyn ExecutionPlan>,
204 common_prefix_length: usize,
205 ) -> Self {
206 debug_assert!(common_prefix_length > 0);
207 let preserve_partitioning = false;
208 let cache = Self::compute_properties(&input, expr.clone(), preserve_partitioning)
209 .unwrap();
210 Self {
211 input,
212 expr,
213 common_prefix_length,
214 metrics_set: ExecutionPlanMetricsSet::new(),
215 preserve_partitioning,
216 fetch: None,
217 cache: Arc::new(cache),
218 }
219 }
220
221 pub fn preserve_partitioning(&self) -> bool {
223 self.preserve_partitioning
224 }
225
226 pub fn with_preserve_partitioning(mut self, preserve_partitioning: bool) -> Self {
234 self.preserve_partitioning = preserve_partitioning;
235 Arc::make_mut(&mut self.cache).partitioning =
236 Self::output_partitioning_helper(&self.input, self.preserve_partitioning);
237 self
238 }
239
240 pub fn with_fetch(mut self, fetch: Option<usize>) -> Self {
248 self.fetch = fetch;
249 self
250 }
251
252 pub fn input(&self) -> &Arc<dyn ExecutionPlan> {
254 &self.input
255 }
256
257 pub fn expr(&self) -> &LexOrdering {
259 &self.expr
260 }
261
262 pub fn fetch(&self) -> Option<usize> {
264 self.fetch
265 }
266
267 pub fn common_prefix_length(&self) -> usize {
269 self.common_prefix_length
270 }
271
272 fn output_partitioning_helper(
273 input: &Arc<dyn ExecutionPlan>,
274 preserve_partitioning: bool,
275 ) -> Partitioning {
276 if preserve_partitioning {
278 input.output_partitioning().clone()
279 } else {
280 Partitioning::UnknownPartitioning(1)
281 }
282 }
283
284 fn compute_properties(
286 input: &Arc<dyn ExecutionPlan>,
287 sort_exprs: LexOrdering,
288 preserve_partitioning: bool,
289 ) -> Result<PlanProperties> {
290 let mut eq_properties = input.equivalence_properties().clone();
293 eq_properties.reorder(sort_exprs)?;
294
295 let output_partitioning =
297 Self::output_partitioning_helper(input, preserve_partitioning);
298
299 Ok(PlanProperties::new(
300 eq_properties,
301 output_partitioning,
302 input.pipeline_behavior(),
303 input.boundedness(),
304 ))
305 }
306}
307
308impl DisplayAs for PartialSortExec {
309 fn fmt_as(
310 &self,
311 t: DisplayFormatType,
312 f: &mut std::fmt::Formatter,
313 ) -> std::fmt::Result {
314 match t {
315 DisplayFormatType::Default | DisplayFormatType::Verbose => {
316 let common_prefix_length = self.common_prefix_length;
317 match self.fetch {
318 Some(fetch) => {
319 write!(
320 f,
321 "PartialSortExec: TopK(fetch={fetch}), expr=[{}], common_prefix_length=[{common_prefix_length}]",
322 self.expr
323 )
324 }
325 None => write!(
326 f,
327 "PartialSortExec: expr=[{}], common_prefix_length=[{common_prefix_length}]",
328 self.expr
329 ),
330 }
331 }
332 DisplayFormatType::TreeRender => match self.fetch {
333 Some(fetch) => {
334 writeln!(f, "{}", self.expr)?;
335 writeln!(f, "limit={fetch}")
336 }
337 None => {
338 writeln!(f, "{}", self.expr)
339 }
340 },
341 }
342 }
343}
344
345impl ExecutionPlan for PartialSortExec {
346 fn name(&self) -> &'static str {
347 "PartialSortExec"
348 }
349
350 fn properties(&self) -> &Arc<PlanProperties> {
351 &self.cache
352 }
353
354 fn fetch(&self) -> Option<usize> {
355 self.fetch
356 }
357
358 fn required_input_distribution(&self) -> Vec<Distribution> {
359 self.input_distribution_requirements().into_per_child()
360 }
361
362 fn input_distribution_requirements(&self) -> crate::InputDistributionRequirements {
363 crate::InputDistributionRequirements::new(if self.preserve_partitioning {
364 vec![Distribution::UnspecifiedDistribution]
365 } else {
366 vec![Distribution::SinglePartition]
367 })
368 }
369
370 fn benefits_from_input_partitioning(&self) -> Vec<bool> {
371 vec![false]
372 }
373
374 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
375 vec![&self.input]
376 }
377
378 fn apply_expressions(
379 &self,
380 f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
381 ) -> Result<TreeNodeRecursion> {
382 crate::apply_expression_roots(
383 self.expr.iter().map(|sort_expr| &sort_expr.expr),
384 f,
385 )
386 }
387
388 fn replace_children(
389 self: Arc<Self>,
390 mut children: Vec<Arc<dyn ExecutionPlan>>,
391 options: ReplaceChildrenOptions,
392 ) -> Result<Arc<dyn ExecutionPlan>> {
393 validate_child_count!(self, children);
394 match options.children_properties {
395 ChildrenPropertiesMode::Keep => Ok(Arc::new(Self {
396 input: children.swap_remove(0),
397 metrics_set: ExecutionPlanMetricsSet::new(),
398 ..Self::clone(&*self)
399 })),
400 ChildrenPropertiesMode::Recompute => {
401 let new_partial_sort = PartialSortExec::new(
402 self.expr.clone(),
403 Arc::clone(&children[0]),
404 self.common_prefix_length,
405 )
406 .with_fetch(self.fetch)
407 .with_preserve_partitioning(self.preserve_partitioning);
408
409 Ok(Arc::new(new_partial_sort))
410 }
411 }
412 }
413
414 fn with_new_children(
415 self: Arc<Self>,
416 children: Vec<Arc<dyn ExecutionPlan>>,
417 ) -> Result<Arc<dyn ExecutionPlan>> {
418 self.replace_children(
419 children,
420 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
421 )
422 }
423
424 fn with_new_children_and_same_properties(
425 self: Arc<Self>,
426 children: Vec<Arc<dyn ExecutionPlan>>,
427 ) -> Result<Arc<dyn ExecutionPlan>> {
428 self.replace_children(
429 children,
430 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Keep),
431 )
432 }
433
434 fn execute(
435 &self,
436 partition: usize,
437 context: Arc<TaskContext>,
438 ) -> Result<SendableRecordBatchStream> {
439 trace!(
440 "Start PartialSortExec::execute for partition {} of context session_id {} and task_id {:?}",
441 partition,
442 context.session_id(),
443 context.task_id()
444 );
445
446 let input = self.input.execute(partition, Arc::clone(&context))?;
447
448 trace!("End PartialSortExec's input.execute for partition: {partition}");
449
450 debug_assert!(self.common_prefix_length > 0);
453
454 Ok(Box::pin(PartialSortStream {
455 input,
456 expr: self.expr.clone(),
457 common_prefix_length: self.common_prefix_length,
458 in_mem_batch: RecordBatch::new_empty(Arc::clone(&self.schema())),
459 fetch: self.fetch,
460 is_closed: false,
461 baseline_metrics: BaselineMetrics::new(&self.metrics_set, partition),
462 }))
463 }
464
465 fn metrics(&self) -> Option<MetricsSet> {
466 Some(self.metrics_set.clone_inner())
467 }
468
469 fn child_stats_requests(&self, partition: Option<usize>) -> Vec<ChildStats> {
470 vec![ChildStats::At(partition)]
471 }
472
473 fn statistics_from_inputs(
474 &self,
475 input_stats: &[Arc<Statistics>],
476 _args: &StatisticsArgs,
477 ) -> Result<Arc<Statistics>> {
478 Ok(Arc::clone(&input_stats[0]))
479 }
480}
481
482struct PartialSortStream {
483 input: SendableRecordBatchStream,
485 expr: LexOrdering,
487 common_prefix_length: usize,
490 in_mem_batch: RecordBatch,
492 fetch: Option<usize>,
494 is_closed: bool,
496 baseline_metrics: BaselineMetrics,
498}
499
500impl Stream for PartialSortStream {
501 type Item = Result<RecordBatch>;
502
503 fn poll_next(
504 mut self: Pin<&mut Self>,
505 cx: &mut Context<'_>,
506 ) -> Poll<Option<Self::Item>> {
507 let poll = self.poll_next_inner(cx);
508 self.baseline_metrics.record_poll(poll)
509 }
510
511 fn size_hint(&self) -> (usize, Option<usize>) {
512 self.input.size_hint()
514 }
515}
516
517impl RecordBatchStream for PartialSortStream {
518 fn schema(&self) -> SchemaRef {
519 self.input.schema()
520 }
521}
522
523impl PartialSortStream {
524 fn poll_next_inner(
525 self: &mut Pin<&mut Self>,
526 cx: &mut Context<'_>,
527 ) -> Poll<Option<Result<RecordBatch>>> {
528 if self.is_closed {
529 return Poll::Ready(None);
530 }
531 loop {
532 if self.fetch == Some(0) {
534 self.is_closed = true;
535 let input_schema = self.input.schema();
537 self.input = Box::pin(EmptyRecordBatchStream::new(input_schema));
538 return Poll::Ready(None);
539 }
540
541 match ready!(self.input.poll_next_unpin(cx)) {
542 Some(Ok(batch)) => {
543 self.in_mem_batch = concat_batches(
545 &self.schema(),
546 &[self.in_mem_batch.clone(), batch],
547 )?;
548
549 if let Some(slice_point) = self
551 .get_slice_point(self.common_prefix_length, &self.in_mem_batch)?
552 {
553 let sorted = self.in_mem_batch.slice(0, slice_point);
554 self.in_mem_batch = self.in_mem_batch.slice(
555 slice_point,
556 self.in_mem_batch.num_rows() - slice_point,
557 );
558 let sorted_batch = sort_batch(&sorted, &self.expr, self.fetch)?;
559 if let Some(fetch) = self.fetch.as_mut() {
560 *fetch -= sorted_batch.num_rows();
561 }
562
563 if sorted_batch.num_rows() > 0 {
564 return Poll::Ready(Some(Ok(sorted_batch)));
565 }
566 }
567 }
568 Some(Err(e)) => return Poll::Ready(Some(Err(e))),
569 None => {
570 self.is_closed = true;
571 let input_schema = self.input.schema();
573 self.input = Box::pin(EmptyRecordBatchStream::new(input_schema));
574 let remaining_batch = self.sort_in_mem_batch()?;
576 return if remaining_batch.num_rows() > 0 {
577 Poll::Ready(Some(Ok(remaining_batch)))
578 } else {
579 Poll::Ready(None)
580 };
581 }
582 };
583 }
584 }
585
586 fn sort_in_mem_batch(self: &mut Pin<&mut Self>) -> Result<RecordBatch> {
591 let input_batch = self.in_mem_batch.clone();
592 self.in_mem_batch = RecordBatch::new_empty(self.schema());
593 let result = sort_batch(&input_batch, &self.expr, self.fetch)?;
594 if let Some(remaining_fetch) = self.fetch {
595 self.fetch = Some(remaining_fetch - result.num_rows());
599 if remaining_fetch == result.num_rows() {
600 self.is_closed = true;
601 }
602 }
603 Ok(result)
604 }
605
606 fn get_slice_point(
612 &self,
613 common_prefix_len: usize,
614 batch: &RecordBatch,
615 ) -> Result<Option<usize>> {
616 let common_prefix_sort_keys = (0..common_prefix_len)
617 .map(|idx| self.expr[idx].evaluate_to_sort_column(batch))
618 .collect::<Result<Vec<_>>>()?;
619 let partition_points =
620 evaluate_partition_ranges(batch.num_rows(), &common_prefix_sort_keys)?;
621 if partition_points.len() >= 2 {
626 Ok(Some(partition_points[partition_points.len() - 2].end))
627 } else {
628 Ok(None)
629 }
630 }
631}
632
633#[cfg(test)]
634mod tests {
635 use std::collections::HashMap;
636
637 use arrow::array::*;
638 use arrow::compute::SortOptions;
639 use arrow::datatypes::*;
640 use datafusion_common::test_util::batches_to_string;
641 use futures::FutureExt;
642 use insta::allow_duplicates;
643 use insta::assert_snapshot;
644 use itertools::Itertools;
645
646 use crate::collect;
647 use crate::expressions::PhysicalSortExpr;
648 use crate::expressions::col;
649 use crate::sorts::sort::SortExec;
650 use crate::test;
651 use crate::test::TestMemoryExec;
652 use crate::test::assert_is_pending;
653 use crate::test::exec::{BlockingExec, assert_strong_count_converges_to_zero};
654
655 use super::*;
656
657 #[tokio::test]
658 async fn test_partial_sort() -> Result<()> {
659 let task_ctx = Arc::new(TaskContext::default());
660 let source = test::build_table_scan_i32(
661 ("a", &vec![0, 0, 0, 1, 1, 1]),
662 ("b", &vec![1, 1, 2, 2, 3, 3]),
663 ("c", &vec![1, 0, 5, 4, 3, 2]),
664 );
665 let schema = Schema::new(vec![
666 Field::new("a", DataType::Int32, false),
667 Field::new("b", DataType::Int32, false),
668 Field::new("c", DataType::Int32, false),
669 ]);
670 let option_asc = SortOptions {
671 descending: false,
672 nulls_first: false,
673 };
674
675 let partial_sort_exec = Arc::new(PartialSortExec::new(
676 [
677 PhysicalSortExpr {
678 expr: col("a", &schema)?,
679 options: option_asc,
680 },
681 PhysicalSortExpr {
682 expr: col("b", &schema)?,
683 options: option_asc,
684 },
685 PhysicalSortExpr {
686 expr: col("c", &schema)?,
687 options: option_asc,
688 },
689 ]
690 .into(),
691 Arc::clone(&source),
692 2,
693 ));
694
695 let result = collect(partial_sort_exec, Arc::clone(&task_ctx)).await?;
696
697 assert_eq!(2, result.len());
698 allow_duplicates! {
699 assert_snapshot!(batches_to_string(&result), @r"
700 +---+---+---+
701 | a | b | c |
702 +---+---+---+
703 | 0 | 1 | 0 |
704 | 0 | 1 | 1 |
705 | 0 | 2 | 5 |
706 | 1 | 2 | 4 |
707 | 1 | 3 | 2 |
708 | 1 | 3 | 3 |
709 +---+---+---+
710 ");
711 }
712 assert_eq!(
713 task_ctx.runtime_env().memory_pool.reserved(),
714 0,
715 "The sort should have returned all memory used back to the memory manager"
716 );
717
718 Ok(())
719 }
720
721 #[tokio::test]
722 async fn test_partial_sort_with_fetch() -> Result<()> {
723 let task_ctx = Arc::new(TaskContext::default());
724 let source = test::build_table_scan_i32(
725 ("a", &vec![0, 0, 1, 1, 1]),
726 ("b", &vec![1, 2, 2, 3, 3]),
727 ("c", &vec![4, 3, 2, 1, 0]),
728 );
729 let schema = Schema::new(vec![
730 Field::new("a", DataType::Int32, false),
731 Field::new("b", DataType::Int32, false),
732 Field::new("c", DataType::Int32, false),
733 ]);
734 let option_asc = SortOptions {
735 descending: false,
736 nulls_first: false,
737 };
738
739 for common_prefix_length in [1, 2] {
740 let partial_sort_exec = Arc::new(
741 PartialSortExec::new(
742 [
743 PhysicalSortExpr {
744 expr: col("a", &schema)?,
745 options: option_asc,
746 },
747 PhysicalSortExpr {
748 expr: col("b", &schema)?,
749 options: option_asc,
750 },
751 PhysicalSortExpr {
752 expr: col("c", &schema)?,
753 options: option_asc,
754 },
755 ]
756 .into(),
757 Arc::clone(&source),
758 common_prefix_length,
759 )
760 .with_fetch(Some(4)),
761 );
762
763 let result = collect(partial_sort_exec, Arc::clone(&task_ctx)).await?;
764
765 assert_eq!(2, result.len());
766 allow_duplicates! {
767 assert_snapshot!(batches_to_string(&result), @r"
768 +---+---+---+
769 | a | b | c |
770 +---+---+---+
771 | 0 | 1 | 4 |
772 | 0 | 2 | 3 |
773 | 1 | 2 | 2 |
774 | 1 | 3 | 0 |
775 +---+---+---+
776 ");
777 }
778 assert_eq!(
779 task_ctx.runtime_env().memory_pool.reserved(),
780 0,
781 "The sort should have returned all memory used back to the memory manager"
782 );
783 }
784
785 Ok(())
786 }
787
788 #[tokio::test]
789 async fn test_partial_sort2() -> Result<()> {
790 let task_ctx = Arc::new(TaskContext::default());
791 let source_tables = [
792 test::build_table_scan_i32(
793 ("a", &vec![0, 0, 0, 0, 1, 1, 1, 1]),
794 ("b", &vec![1, 1, 3, 3, 4, 4, 2, 2]),
795 ("c", &vec![7, 6, 5, 4, 3, 2, 1, 0]),
796 ),
797 test::build_table_scan_i32(
798 ("a", &vec![0, 0, 0, 0, 1, 1, 1, 1]),
799 ("b", &vec![1, 1, 3, 3, 2, 2, 4, 4]),
800 ("c", &vec![7, 6, 5, 4, 1, 0, 3, 2]),
801 ),
802 ];
803 let schema = Schema::new(vec![
804 Field::new("a", DataType::Int32, false),
805 Field::new("b", DataType::Int32, false),
806 Field::new("c", DataType::Int32, false),
807 ]);
808 let option_asc = SortOptions {
809 descending: false,
810 nulls_first: false,
811 };
812 for (common_prefix_length, source) in
813 [(1, &source_tables[0]), (2, &source_tables[1])]
814 {
815 let partial_sort_exec = Arc::new(PartialSortExec::new(
816 [
817 PhysicalSortExpr {
818 expr: col("a", &schema)?,
819 options: option_asc,
820 },
821 PhysicalSortExpr {
822 expr: col("b", &schema)?,
823 options: option_asc,
824 },
825 PhysicalSortExpr {
826 expr: col("c", &schema)?,
827 options: option_asc,
828 },
829 ]
830 .into(),
831 Arc::clone(source),
832 common_prefix_length,
833 ));
834
835 let result = collect(partial_sort_exec, Arc::clone(&task_ctx)).await?;
836 assert_eq!(2, result.len());
837 assert_eq!(
838 task_ctx.runtime_env().memory_pool.reserved(),
839 0,
840 "The sort should have returned all memory used back to the memory manager"
841 );
842 allow_duplicates! {
843 assert_snapshot!(batches_to_string(&result), @r"
844 +---+---+---+
845 | a | b | c |
846 +---+---+---+
847 | 0 | 1 | 6 |
848 | 0 | 1 | 7 |
849 | 0 | 3 | 4 |
850 | 0 | 3 | 5 |
851 | 1 | 2 | 0 |
852 | 1 | 2 | 1 |
853 | 1 | 4 | 2 |
854 | 1 | 4 | 3 |
855 +---+---+---+
856 ");
857 }
858 }
859 Ok(())
860 }
861
862 fn prepare_partitioned_input() -> Arc<dyn ExecutionPlan> {
863 let batch1 = test::build_table_i32(
864 ("a", &vec![1; 100]),
865 ("b", &(0..100).rev().collect()),
866 ("c", &(0..100).rev().collect()),
867 );
868 let batch2 = test::build_table_i32(
869 ("a", &[&vec![1; 25][..], &vec![2; 75][..]].concat()),
870 ("b", &(100..200).rev().collect()),
871 ("c", &(0..100).collect()),
872 );
873 let batch3 = test::build_table_i32(
874 ("a", &[&vec![3; 50][..], &vec![4; 50][..]].concat()),
875 ("b", &(150..250).rev().collect()),
876 ("c", &(0..100).rev().collect()),
877 );
878 let batch4 = test::build_table_i32(
879 ("a", &vec![4; 100]),
880 ("b", &(50..150).rev().collect()),
881 ("c", &(0..100).rev().collect()),
882 );
883 let schema = batch1.schema();
884
885 TestMemoryExec::try_new_exec(
886 &[vec![batch1, batch2, batch3, batch4]],
887 Arc::clone(&schema),
888 None,
889 )
890 .unwrap() as Arc<dyn ExecutionPlan>
891 }
892
893 #[tokio::test]
894 async fn test_partitioned_input_partial_sort() -> Result<()> {
895 let task_ctx = Arc::new(TaskContext::default());
896 let mem_exec = prepare_partitioned_input();
897 let option_asc = SortOptions {
898 descending: false,
899 nulls_first: false,
900 };
901 let option_desc = SortOptions {
902 descending: false,
903 nulls_first: false,
904 };
905 let schema = mem_exec.schema();
906 let partial_sort_exec = PartialSortExec::new(
907 [
908 PhysicalSortExpr {
909 expr: col("a", &schema)?,
910 options: option_asc,
911 },
912 PhysicalSortExpr {
913 expr: col("b", &schema)?,
914 options: option_desc,
915 },
916 PhysicalSortExpr {
917 expr: col("c", &schema)?,
918 options: option_asc,
919 },
920 ]
921 .into(),
922 Arc::clone(&mem_exec),
923 1,
924 );
925 let sort_exec = Arc::new(SortExec::new(
926 partial_sort_exec.expr.clone(),
927 Arc::clone(&partial_sort_exec.input),
928 ));
929 let result = collect(Arc::new(partial_sort_exec), Arc::clone(&task_ctx)).await?;
930 assert_eq!(
931 result.iter().map(|r| r.num_rows()).collect_vec(),
932 [125, 125, 150]
933 );
934
935 assert_eq!(
936 task_ctx.runtime_env().memory_pool.reserved(),
937 0,
938 "The sort should have returned all memory used back to the memory manager"
939 );
940 let partial_sort_result = concat_batches(&schema, &result).unwrap();
941 let sort_result = collect(sort_exec, Arc::clone(&task_ctx)).await?;
942 assert_eq!(sort_result[0], partial_sort_result);
943
944 Ok(())
945 }
946
947 #[tokio::test]
948 async fn test_partitioned_input_partial_sort_with_fetch() -> Result<()> {
949 let task_ctx = Arc::new(TaskContext::default());
950 let mem_exec = prepare_partitioned_input();
951 let schema = mem_exec.schema();
952 let option_asc = SortOptions {
953 descending: false,
954 nulls_first: false,
955 };
956 let option_desc = SortOptions {
957 descending: false,
958 nulls_first: false,
959 };
960 for (fetch_size, expected_batch_num_rows) in [
961 (Some(50), vec![50]),
962 (Some(120), vec![120]),
963 (Some(150), vec![125, 25]),
964 (Some(250), vec![125, 125]),
965 ] {
966 let partial_sort_exec = PartialSortExec::new(
967 [
968 PhysicalSortExpr {
969 expr: col("a", &schema)?,
970 options: option_asc,
971 },
972 PhysicalSortExpr {
973 expr: col("b", &schema)?,
974 options: option_desc,
975 },
976 PhysicalSortExpr {
977 expr: col("c", &schema)?,
978 options: option_asc,
979 },
980 ]
981 .into(),
982 Arc::clone(&mem_exec),
983 1,
984 )
985 .with_fetch(fetch_size);
986
987 let sort_exec = Arc::new(
988 SortExec::new(
989 partial_sort_exec.expr.clone(),
990 Arc::clone(&partial_sort_exec.input),
991 )
992 .with_fetch(fetch_size),
993 );
994 let result =
995 collect(Arc::new(partial_sort_exec), Arc::clone(&task_ctx)).await?;
996 assert_eq!(
997 result.iter().map(|r| r.num_rows()).collect_vec(),
998 expected_batch_num_rows
999 );
1000
1001 assert_eq!(
1002 task_ctx.runtime_env().memory_pool.reserved(),
1003 0,
1004 "The sort should have returned all memory used back to the memory manager"
1005 );
1006 let partial_sort_result = concat_batches(&schema, &result)?;
1007 let sort_result = collect(sort_exec, Arc::clone(&task_ctx)).await?;
1008 assert_eq!(sort_result[0], partial_sort_result);
1009 }
1010
1011 Ok(())
1012 }
1013
1014 #[tokio::test]
1015 async fn test_partial_sort_no_empty_batches() -> Result<()> {
1016 let task_ctx = Arc::new(TaskContext::default());
1017 let mem_exec = prepare_partitioned_input();
1018 let schema = mem_exec.schema();
1019 let option_asc = SortOptions {
1020 descending: false,
1021 nulls_first: false,
1022 };
1023 let fetch_size = Some(250);
1024 let partial_sort_exec = PartialSortExec::new(
1025 [
1026 PhysicalSortExpr {
1027 expr: col("a", &schema)?,
1028 options: option_asc,
1029 },
1030 PhysicalSortExpr {
1031 expr: col("c", &schema)?,
1032 options: option_asc,
1033 },
1034 ]
1035 .into(),
1036 Arc::clone(&mem_exec),
1037 1,
1038 )
1039 .with_fetch(fetch_size);
1040
1041 let result = collect(Arc::new(partial_sort_exec), Arc::clone(&task_ctx)).await?;
1042 for rb in result {
1043 assert!(rb.num_rows() > 0);
1044 }
1045
1046 Ok(())
1047 }
1048
1049 #[tokio::test]
1050 async fn test_sort_metadata() -> Result<()> {
1051 let task_ctx = Arc::new(TaskContext::default());
1052 let field_metadata: HashMap<String, String> =
1053 vec![("foo".to_string(), "bar".to_string())]
1054 .into_iter()
1055 .collect();
1056 let schema_metadata: HashMap<String, String> =
1057 vec![("baz".to_string(), "barf".to_string())]
1058 .into_iter()
1059 .collect();
1060
1061 let mut field = Field::new("field_name", DataType::UInt64, true);
1062 field.set_metadata(field_metadata.clone());
1063 let schema = Schema::new_with_metadata(vec![field], schema_metadata.clone());
1064 let schema = Arc::new(schema);
1065
1066 let data: ArrayRef =
1067 Arc::new(vec![1, 1, 2].into_iter().map(Some).collect::<UInt64Array>());
1068
1069 let batch = RecordBatch::try_new(Arc::clone(&schema), vec![data])?;
1070 let input =
1071 TestMemoryExec::try_new_exec(&[vec![batch]], Arc::clone(&schema), None)?;
1072
1073 let partial_sort_exec = Arc::new(PartialSortExec::new(
1074 [PhysicalSortExpr {
1075 expr: col("field_name", &schema)?,
1076 options: SortOptions::default(),
1077 }]
1078 .into(),
1079 input,
1080 1,
1081 ));
1082
1083 let result: Vec<RecordBatch> = collect(partial_sort_exec, task_ctx).await?;
1084 let expected_batch = vec![
1085 RecordBatch::try_new(
1086 Arc::clone(&schema),
1087 vec![Arc::new(
1088 vec![1, 1].into_iter().map(Some).collect::<UInt64Array>(),
1089 )],
1090 )?,
1091 RecordBatch::try_new(
1092 Arc::clone(&schema),
1093 vec![Arc::new(
1094 vec![2].into_iter().map(Some).collect::<UInt64Array>(),
1095 )],
1096 )?,
1097 ];
1098
1099 assert_eq!(&expected_batch, &result);
1101
1102 assert_eq!(result[0].schema().fields()[0].metadata(), &field_metadata);
1104 assert_eq!(result[0].schema().metadata(), &schema_metadata);
1105
1106 Ok(())
1107 }
1108
1109 #[tokio::test]
1110 async fn test_lex_sort_by_float() -> Result<()> {
1111 let task_ctx = Arc::new(TaskContext::default());
1112 let schema = Arc::new(Schema::new(vec![
1113 Field::new("a", DataType::Float32, true),
1114 Field::new("b", DataType::Float64, true),
1115 Field::new("c", DataType::Float64, true),
1116 ]));
1117 let option_asc = SortOptions {
1118 descending: false,
1119 nulls_first: true,
1120 };
1121 let option_desc = SortOptions {
1122 descending: true,
1123 nulls_first: true,
1124 };
1125
1126 let batch = RecordBatch::try_new(
1128 Arc::clone(&schema),
1129 vec![
1130 Arc::new(Float32Array::from(vec![
1131 Some(1.0_f32),
1132 Some(1.0_f32),
1133 Some(1.0_f32),
1134 Some(2.0_f32),
1135 Some(2.0_f32),
1136 Some(3.0_f32),
1137 Some(3.0_f32),
1138 Some(3.0_f32),
1139 ])),
1140 Arc::new(Float64Array::from(vec![
1141 Some(20.0_f64),
1142 Some(20.0_f64),
1143 Some(40.0_f64),
1144 Some(40.0_f64),
1145 Some(f64::NAN),
1146 None,
1147 None,
1148 Some(f64::NAN),
1149 ])),
1150 Arc::new(Float64Array::from(vec![
1151 Some(10.0_f64),
1152 Some(20.0_f64),
1153 Some(10.0_f64),
1154 Some(100.0_f64),
1155 Some(f64::NAN),
1156 Some(100.0_f64),
1157 None,
1158 Some(f64::NAN),
1159 ])),
1160 ],
1161 )?;
1162
1163 let partial_sort_exec = Arc::new(PartialSortExec::new(
1164 [
1165 PhysicalSortExpr {
1166 expr: col("a", &schema)?,
1167 options: option_asc,
1168 },
1169 PhysicalSortExpr {
1170 expr: col("b", &schema)?,
1171 options: option_asc,
1172 },
1173 PhysicalSortExpr {
1174 expr: col("c", &schema)?,
1175 options: option_desc,
1176 },
1177 ]
1178 .into(),
1179 TestMemoryExec::try_new_exec(&[vec![batch]], schema, None)?,
1180 2,
1181 ));
1182
1183 assert_eq!(
1184 DataType::Float32,
1185 *partial_sort_exec.schema().field(0).data_type()
1186 );
1187 assert_eq!(
1188 DataType::Float64,
1189 *partial_sort_exec.schema().field(1).data_type()
1190 );
1191 assert_eq!(
1192 DataType::Float64,
1193 *partial_sort_exec.schema().field(2).data_type()
1194 );
1195
1196 let result: Vec<RecordBatch> = collect(
1197 Arc::clone(&partial_sort_exec) as Arc<dyn ExecutionPlan>,
1198 task_ctx,
1199 )
1200 .await?;
1201 assert_snapshot!(batches_to_string(&result), @r"
1202 +-----+------+-------+
1203 | a | b | c |
1204 +-----+------+-------+
1205 | 1.0 | 20.0 | 20.0 |
1206 | 1.0 | 20.0 | 10.0 |
1207 | 1.0 | 40.0 | 10.0 |
1208 | 2.0 | 40.0 | 100.0 |
1209 | 2.0 | NaN | NaN |
1210 | 3.0 | | |
1211 | 3.0 | | 100.0 |
1212 | 3.0 | NaN | NaN |
1213 +-----+------+-------+
1214 ");
1215 assert_eq!(result.len(), 2);
1216 let metrics = partial_sort_exec.metrics().unwrap();
1217 assert!(metrics.elapsed_compute().unwrap() > 0);
1218 assert_eq!(metrics.output_rows().unwrap(), 8);
1219
1220 let columns = result[0].columns();
1221
1222 assert_eq!(DataType::Float32, *columns[0].data_type());
1223 assert_eq!(DataType::Float64, *columns[1].data_type());
1224 assert_eq!(DataType::Float64, *columns[2].data_type());
1225
1226 Ok(())
1227 }
1228
1229 #[tokio::test]
1230 async fn test_drop_cancel() -> Result<()> {
1231 let task_ctx = Arc::new(TaskContext::default());
1232 let schema = Arc::new(Schema::new(vec![
1233 Field::new("a", DataType::Float32, true),
1234 Field::new("b", DataType::Float32, true),
1235 ]));
1236
1237 let blocking_exec = Arc::new(BlockingExec::new(Arc::clone(&schema), 1));
1238 let refs = blocking_exec.refs();
1239 let sort_exec = Arc::new(PartialSortExec::new(
1240 [PhysicalSortExpr {
1241 expr: col("a", &schema)?,
1242 options: SortOptions::default(),
1243 }]
1244 .into(),
1245 blocking_exec,
1246 1,
1247 ));
1248
1249 let fut = collect(sort_exec, Arc::clone(&task_ctx));
1250 let mut fut = fut.boxed();
1251
1252 assert_is_pending(&mut fut);
1253 drop(fut);
1254 assert_strong_count_converges_to_zero(refs).await;
1255
1256 assert_eq!(
1257 task_ctx.runtime_env().memory_pool.reserved(),
1258 0,
1259 "The sort should have returned all memory used back to the memory manager"
1260 );
1261
1262 Ok(())
1263 }
1264
1265 #[tokio::test]
1266 async fn test_partial_sort_with_homogeneous_batches() -> Result<()> {
1267 let task_ctx = Arc::new(TaskContext::default());
1271
1272 let batch1 = test::build_table_i32(
1274 ("a", &vec![1; 3]),
1275 ("b", &vec![1; 3]),
1276 ("c", &vec![3, 2, 1]),
1277 );
1278 let batch2 = test::build_table_i32(
1279 ("a", &vec![2; 3]),
1280 ("b", &vec![2; 3]),
1281 ("c", &vec![4, 6, 4]),
1282 );
1283 let batch3 = test::build_table_i32(
1284 ("a", &vec![3; 3]),
1285 ("b", &vec![3; 3]),
1286 ("c", &vec![9, 7, 8]),
1287 );
1288
1289 let schema = batch1.schema();
1290 let mem_exec = TestMemoryExec::try_new_exec(
1291 &[vec![batch1, batch2, batch3]],
1292 Arc::clone(&schema),
1293 None,
1294 )?;
1295
1296 let option_asc = SortOptions {
1297 descending: false,
1298 nulls_first: false,
1299 };
1300
1301 let partial_sort_exec = Arc::new(PartialSortExec::new(
1303 [
1304 PhysicalSortExpr {
1305 expr: col("a", &schema)?,
1306 options: option_asc,
1307 },
1308 PhysicalSortExpr {
1309 expr: col("b", &schema)?,
1310 options: option_asc,
1311 },
1312 PhysicalSortExpr {
1313 expr: col("c", &schema)?,
1314 options: option_asc,
1315 },
1316 ]
1317 .into(),
1318 mem_exec,
1319 2,
1320 ));
1321
1322 let result = collect(partial_sort_exec, Arc::clone(&task_ctx)).await?;
1323
1324 assert_eq!(result.len(), 3,);
1325
1326 allow_duplicates! {
1327 assert_snapshot!(batches_to_string(&result), @r"
1328 +---+---+---+
1329 | a | b | c |
1330 +---+---+---+
1331 | 1 | 1 | 1 |
1332 | 1 | 1 | 2 |
1333 | 1 | 1 | 3 |
1334 | 2 | 2 | 4 |
1335 | 2 | 2 | 4 |
1336 | 2 | 2 | 6 |
1337 | 3 | 3 | 7 |
1338 | 3 | 3 | 8 |
1339 | 3 | 3 | 9 |
1340 +---+---+---+
1341 ");
1342 }
1343
1344 assert_eq!(task_ctx.runtime_env().memory_pool.reserved(), 0,);
1345 Ok(())
1346 }
1347}