Skip to main content

datafusion_physical_plan/sorts/
sort_preserving_merge.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! [`SortPreservingMergeExec`] merges multiple sorted streams into one sorted stream.
19
20use std::sync::Arc;
21
22use crate::common::spawn_buffered;
23use crate::limit::LimitStream;
24use crate::metrics::{BaselineMetrics, ExecutionPlanMetricsSet, MetricsSet};
25use crate::projection::{ProjectionExec, make_with_child, update_ordering};
26use crate::sorts::streaming_merge::StreamingMergeBuilder;
27use crate::statistics::{ChildStats, StatisticsArgs};
28use crate::{
29    ChildrenPropertiesMode, DisplayAs, DisplayFormatType, Distribution, ExecutionPlan,
30    ExecutionPlanProperties, Partitioning, PlanProperties, ReplaceChildrenOptions,
31    SendableRecordBatchStream, Statistics, validate_child_count,
32};
33
34use datafusion_common::tree_node::TreeNodeRecursion;
35use datafusion_common::{Result, assert_eq_or_internal_err, internal_err};
36use datafusion_execution::TaskContext;
37use datafusion_execution::memory_pool::MemoryConsumer;
38use datafusion_physical_expr::PhysicalExpr;
39use datafusion_physical_expr_common::sort_expr::{LexOrdering, OrderingRequirements};
40
41use crate::execution_plan::{
42    CardinalityEffect, EvaluationType, SchedulingType, replace_children_if_necessary,
43};
44use log::{debug, trace};
45
46/// Sort preserving merge execution plan
47///
48/// # Overview
49///
50/// This operator implements a K-way merge. It is used to merge multiple sorted
51/// streams into a single sorted stream and is highly optimized.
52///
53/// ## Inputs:
54///
55/// 1. A list of sort expressions
56/// 2. An input plan, where each partition is sorted with respect to
57///    these sort expressions.
58///
59/// ## Output:
60///
61/// 1. A single partition that is also sorted with respect to the expressions
62///
63/// ## Diagram
64///
65/// ```text
66/// ┌─────────────────────────┐
67/// │ ┌───┬───┬───┬───┐       │
68/// │ │ A │ B │ C │ D │ ...   │──┐
69/// │ └───┴───┴───┴───┘       │  │
70/// └─────────────────────────┘  │  ┌───────────────────┐    ┌───────────────────────────────┐
71///   Stream 1                   │  │                   │    │ ┌───┬───╦═══╦───┬───╦═══╗     │
72///                              ├─▶│SortPreservingMerge│───▶│ │ A │ B ║ B ║ C │ D ║ E ║ ... │
73///                              │  │                   │    │ └───┴─▲─╩═══╩───┴───╩═══╝     │
74/// ┌─────────────────────────┐  │  └───────────────────┘    └─┬─────┴───────────────────────┘
75/// │ ╔═══╦═══╗               │  │
76/// │ ║ B ║ E ║     ...       │──┘                             │
77/// │ ╚═══╩═══╝               │              Stable sort if `enable_round_robin_repartition=false`:
78/// └─────────────────────────┘              the merged stream places equal rows from stream 1
79///   Stream 2
80///
81///
82///  Input Partitions                                          Output Partition
83///    (sorted)                                                  (sorted)
84/// ```
85///
86/// # Error Handling
87///
88/// If any of the input partitions return an error, the error is propagated to
89/// the output and inputs are not polled again.
90#[derive(Debug, Clone)]
91pub struct SortPreservingMergeExec {
92    /// Input plan with sorted partitions
93    input: Arc<dyn ExecutionPlan>,
94    /// Sort expressions
95    expr: LexOrdering,
96    /// Execution metrics
97    metrics: ExecutionPlanMetricsSet,
98    /// Optional number of rows to fetch. Stops producing rows after this fetch
99    fetch: Option<usize>,
100    /// Cache holding plan properties like equivalences, output partitioning etc.
101    cache: Arc<PlanProperties>,
102    /// Use round-robin selection of tied winners of loser tree
103    ///
104    /// See [`Self::with_round_robin_repartition`] for more information.
105    enable_round_robin_repartition: bool,
106}
107
108impl SortPreservingMergeExec {
109    /// Create a new sort execution plan
110    pub fn new(expr: LexOrdering, input: Arc<dyn ExecutionPlan>) -> Self {
111        let cache = Self::compute_properties(&input, expr.clone());
112        Self {
113            input,
114            expr,
115            metrics: ExecutionPlanMetricsSet::new(),
116            fetch: None,
117            cache: Arc::new(cache),
118            enable_round_robin_repartition: true,
119        }
120    }
121
122    /// Sets the number of rows to fetch
123    pub fn with_fetch(mut self, fetch: Option<usize>) -> Self {
124        self.fetch = fetch;
125        self
126    }
127
128    /// Sets the selection strategy of tied winners of the loser tree algorithm
129    ///
130    /// If true (the default) equal output rows are placed in the merged stream
131    /// in round robin fashion. This approach consumes input streams at more
132    /// even rates when there are many rows with the same sort key.
133    ///
134    /// If false, equal output rows are always placed in the merged stream in
135    /// the order of the inputs, resulting in potentially slower execution but a
136    /// stable output order.
137    pub fn with_round_robin_repartition(
138        mut self,
139        enable_round_robin_repartition: bool,
140    ) -> Self {
141        self.enable_round_robin_repartition = enable_round_robin_repartition;
142        self
143    }
144
145    /// Input schema
146    pub fn input(&self) -> &Arc<dyn ExecutionPlan> {
147        &self.input
148    }
149
150    /// Sort expressions
151    pub fn expr(&self) -> &LexOrdering {
152        &self.expr
153    }
154
155    /// Fetch
156    pub fn fetch(&self) -> Option<usize> {
157        self.fetch
158    }
159
160    /// Creates the cache object that stores the plan properties
161    /// such as schema, equivalence properties, ordering, partitioning, etc.
162    fn compute_properties(
163        input: &Arc<dyn ExecutionPlan>,
164        ordering: LexOrdering,
165    ) -> PlanProperties {
166        let input_partitions = input.output_partitioning().partition_count();
167        let (drive, scheduling) = if input_partitions > 1 {
168            (EvaluationType::Eager, SchedulingType::Cooperative)
169        } else {
170            (
171                input.properties().evaluation_type,
172                input.properties().scheduling_type,
173            )
174        };
175
176        let mut eq_properties = input.equivalence_properties().clone();
177        eq_properties.clear_per_partition_constants();
178        eq_properties.add_ordering(ordering);
179        PlanProperties::new(
180            eq_properties,                        // Equivalence Properties
181            Partitioning::UnknownPartitioning(1), // Output Partitioning
182            input.pipeline_behavior(),            // Pipeline Behavior
183            input.boundedness(),                  // Boundedness
184        )
185        .with_evaluation_type(drive)
186        .with_scheduling_type(scheduling)
187    }
188}
189
190impl DisplayAs for SortPreservingMergeExec {
191    fn fmt_as(
192        &self,
193        t: DisplayFormatType,
194        f: &mut std::fmt::Formatter,
195    ) -> std::fmt::Result {
196        match t {
197            DisplayFormatType::Default | DisplayFormatType::Verbose => {
198                write!(f, "SortPreservingMergeExec: [{}]", self.expr)?;
199                if let Some(fetch) = self.fetch {
200                    write!(f, ", fetch={fetch}")?;
201                };
202
203                Ok(())
204            }
205            DisplayFormatType::TreeRender => {
206                if let Some(fetch) = self.fetch {
207                    writeln!(f, "limit={fetch}")?;
208                };
209
210                for (i, e) in self.expr().iter().enumerate() {
211                    e.fmt_sql(f)?;
212                    if i != self.expr().len() - 1 {
213                        write!(f, ", ")?;
214                    }
215                }
216
217                Ok(())
218            }
219        }
220    }
221}
222
223impl ExecutionPlan for SortPreservingMergeExec {
224    fn name(&self) -> &'static str {
225        "SortPreservingMergeExec"
226    }
227
228    /// Return a reference to Any that can be used for downcasting
229    fn properties(&self) -> &Arc<PlanProperties> {
230        &self.cache
231    }
232
233    fn fetch(&self) -> Option<usize> {
234        self.fetch
235    }
236
237    /// Sets the number of rows to fetch
238    fn with_fetch(&self, limit: Option<usize>) -> Option<Arc<dyn ExecutionPlan>> {
239        Some(Arc::new(Self {
240            input: Arc::clone(&self.input),
241            expr: self.expr.clone(),
242            metrics: self.metrics.clone(),
243            fetch: limit,
244            cache: Arc::clone(&self.cache),
245            enable_round_robin_repartition: self.enable_round_robin_repartition,
246        }))
247    }
248
249    fn with_preserve_order(
250        &self,
251        preserve_order: bool,
252    ) -> Option<Arc<dyn ExecutionPlan>> {
253        self.input
254            .with_preserve_order(preserve_order)
255            .and_then(|new_input| {
256                replace_children_if_necessary(Arc::new(self.clone()), vec![new_input])
257                    .ok()
258            })
259    }
260
261    fn required_input_distribution(&self) -> Vec<Distribution> {
262        self.input_distribution_requirements().into_per_child()
263    }
264
265    fn input_distribution_requirements(&self) -> crate::InputDistributionRequirements {
266        crate::InputDistributionRequirements::new(vec![
267            Distribution::UnspecifiedDistribution,
268        ])
269    }
270
271    fn benefits_from_input_partitioning(&self) -> Vec<bool> {
272        vec![false]
273    }
274
275    fn required_input_ordering(&self) -> Vec<Option<OrderingRequirements>> {
276        vec![Some(OrderingRequirements::from(self.expr.clone()))]
277    }
278
279    fn maintains_input_order(&self) -> Vec<bool> {
280        vec![true]
281    }
282
283    fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
284        vec![&self.input]
285    }
286
287    fn apply_expressions(
288        &self,
289        f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
290    ) -> Result<TreeNodeRecursion> {
291        crate::apply_expression_roots(
292            self.expr.iter().map(|sort_expr| &sort_expr.expr),
293            f,
294        )
295    }
296
297    fn replace_children(
298        self: Arc<Self>,
299        mut children: Vec<Arc<dyn ExecutionPlan>>,
300        options: ReplaceChildrenOptions,
301    ) -> Result<Arc<dyn ExecutionPlan>> {
302        validate_child_count!(self, children);
303        match options.children_properties {
304            ChildrenPropertiesMode::Keep => Ok(Arc::new(Self {
305                input: children.swap_remove(0),
306                metrics: ExecutionPlanMetricsSet::new(),
307                ..Self::clone(&*self)
308            })),
309            ChildrenPropertiesMode::Recompute => Ok(Arc::new(
310                SortPreservingMergeExec::new(self.expr.clone(), children.swap_remove(0))
311                    .with_fetch(self.fetch),
312            )),
313        }
314    }
315
316    fn with_new_children(
317        self: Arc<Self>,
318        children: Vec<Arc<dyn ExecutionPlan>>,
319    ) -> Result<Arc<dyn ExecutionPlan>> {
320        self.replace_children(
321            children,
322            ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
323        )
324    }
325
326    fn with_new_children_and_same_properties(
327        self: Arc<Self>,
328        children: Vec<Arc<dyn ExecutionPlan>>,
329    ) -> Result<Arc<dyn ExecutionPlan>> {
330        self.replace_children(
331            children,
332            ReplaceChildrenOptions::new(ChildrenPropertiesMode::Keep),
333        )
334    }
335
336    fn execute(
337        &self,
338        partition: usize,
339        context: Arc<TaskContext>,
340    ) -> Result<SendableRecordBatchStream> {
341        trace!("Start SortPreservingMergeExec::execute for partition: {partition}");
342        assert_eq_or_internal_err!(
343            partition,
344            0,
345            "SortPreservingMergeExec invalid partition {partition}"
346        );
347
348        let input_partitions = self.input.output_partitioning().partition_count();
349        trace!(
350            "Number of input partitions of  SortPreservingMergeExec::execute: {input_partitions}"
351        );
352        let schema = self.schema();
353
354        let reservation =
355            MemoryConsumer::new(format!("SortPreservingMergeExec[{partition}]"))
356                .register(&context.runtime_env().memory_pool);
357
358        match input_partitions {
359            0 => internal_err!(
360                "SortPreservingMergeExec requires at least one input partition"
361            ),
362            1 => match self.fetch {
363                Some(fetch) => {
364                    let stream = self.input.execute(0, context)?;
365                    debug!(
366                        "Done getting stream for SortPreservingMergeExec::execute with 1 input with {fetch}"
367                    );
368                    Ok(Box::pin(LimitStream::new(
369                        stream,
370                        0,
371                        Some(fetch),
372                        BaselineMetrics::new(&self.metrics, partition),
373                    )))
374                }
375                None => {
376                    let stream = self.input.execute(0, context);
377                    debug!(
378                        "Done getting stream for SortPreservingMergeExec::execute with 1 input without fetch"
379                    );
380                    stream
381                }
382            },
383            _ => {
384                let receivers = (0..input_partitions)
385                    .map(|partition| {
386                        let stream =
387                            self.input.execute(partition, Arc::clone(&context))?;
388                        Ok(spawn_buffered(stream, 1))
389                    })
390                    .collect::<Result<_>>()?;
391
392                debug!(
393                    "Done setting up sender-receiver for SortPreservingMergeExec::execute"
394                );
395
396                let result = StreamingMergeBuilder::new()
397                    .with_streams(receivers)
398                    .with_schema(schema)
399                    .with_expressions(&self.expr)
400                    .with_metrics(BaselineMetrics::new(&self.metrics, partition))
401                    .with_batch_size(context.session_config().batch_size())
402                    .with_fetch(self.fetch)
403                    .with_reservation(reservation)
404                    .with_round_robin_tie_breaker(self.enable_round_robin_repartition)
405                    .build()?;
406
407                debug!(
408                    "Got stream result from SortPreservingMergeStream::new_from_receivers"
409                );
410
411                Ok(result)
412            }
413        }
414    }
415
416    fn metrics(&self) -> Option<MetricsSet> {
417        Some(self.metrics.clone_inner())
418    }
419
420    fn child_stats_requests(&self, _partition: Option<usize>) -> Vec<ChildStats> {
421        vec![ChildStats::At(None)]
422    }
423
424    fn statistics_from_inputs(
425        &self,
426        input_stats: &[Arc<Statistics>],
427        _args: &StatisticsArgs,
428    ) -> Result<Arc<Statistics>> {
429        let stats = input_stats[0].as_ref().clone();
430        Ok(Arc::new(stats.with_fetch(self.fetch, 0, 1)?))
431    }
432
433    fn cardinality_effect(&self) -> CardinalityEffect {
434        if self.fetch.is_none() {
435            CardinalityEffect::Equal
436        } else {
437            CardinalityEffect::LowerEqual
438        }
439    }
440
441    fn supports_limit_pushdown(&self) -> bool {
442        true
443    }
444
445    /// Tries to swap the projection with its input [`SortPreservingMergeExec`].
446    /// If this is possible, it returns the new [`SortPreservingMergeExec`] whose
447    /// child is a projection. Otherwise, it returns None.
448    fn try_swapping_with_projection(
449        &self,
450        projection: &ProjectionExec,
451    ) -> Result<Option<Arc<dyn ExecutionPlan>>> {
452        // If the projection does not narrow the schema, we should not try to push it down.
453        if projection.expr().len() >= projection.input().schema().fields().len() {
454            return Ok(None);
455        }
456
457        let Some(updated_exprs) = update_ordering(self.expr.clone(), projection.expr())?
458        else {
459            return Ok(None);
460        };
461
462        Ok(Some(Arc::new(
463            SortPreservingMergeExec::new(
464                updated_exprs,
465                make_with_child(projection, self.input())?,
466            )
467            .with_fetch(self.fetch()),
468        )))
469    }
470    #[cfg(feature = "proto")]
471    fn try_to_proto(
472        &self,
473        ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
474    ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalPlanNode>> {
475        use datafusion_proto_models::protobuf;
476        let input = ctx.encode_child(self.input())?;
477        let expr = self
478            .expr()
479            .iter()
480            .map(|e| {
481                Ok(protobuf::PhysicalExprNode {
482                    expr_id: None,
483                    expr_type: Some(protobuf::physical_expr_node::ExprType::Sort(
484                        Box::new(protobuf::PhysicalSortExprNode {
485                            expr: Some(Box::new(ctx.encode_expr(&e.expr)?)),
486                            asc: !e.options.descending,
487                            nulls_first: e.options.nulls_first,
488                        }),
489                    )),
490                })
491            })
492            .collect::<Result<Vec<_>>>()?;
493        Ok(Some(protobuf::PhysicalPlanNode {
494            physical_plan_type: Some(
495                protobuf::physical_plan_node::PhysicalPlanType::SortPreservingMerge(
496                    Box::new(protobuf::SortPreservingMergeExecNode {
497                        input: Some(Box::new(input)),
498                        expr,
499                        fetch: self.fetch().map(|f| f as i64).unwrap_or(-1),
500                    }),
501                ),
502            ),
503        }))
504    }
505}
506
507#[cfg(feature = "proto")]
508impl SortPreservingMergeExec {
509    pub fn try_from_proto(
510        node: &datafusion_proto_models::protobuf::PhysicalPlanNode,
511        ctx: &crate::proto::ExecutionPlanDecodeCtx<'_>,
512    ) -> Result<Arc<dyn ExecutionPlan>> {
513        use arrow::compute::SortOptions;
514        use datafusion_physical_expr_common::sort_expr::PhysicalSortExpr;
515        use datafusion_proto_models::protobuf;
516        let spm = crate::expect_plan_variant!(
517            node,
518            protobuf::physical_plan_node::PhysicalPlanType::SortPreservingMerge,
519            "SortPreservingMergeExec",
520        );
521        let input = ctx.decode_required_child(
522            spm.input.as_deref(),
523            "SortPreservingMergeExec",
524            "input",
525        )?;
526        let input_schema = input.schema();
527        let exprs = spm
528            .expr
529            .iter()
530            .map(|e| {
531                let sort = match &e.expr_type {
532                    Some(protobuf::physical_expr_node::ExprType::Sort(s)) => s,
533                    _ => {
534                        return internal_err!(
535                            "SortPreservingMergeExec expression is not a sort expression"
536                        );
537                    }
538                };
539                let expr = ctx.decode_required_expr(
540                    sort.expr.as_deref(),
541                    input_schema.as_ref(),
542                    "SortPreservingMergeExec",
543                    "sort expression",
544                )?;
545                Ok(PhysicalSortExpr {
546                    expr,
547                    options: SortOptions {
548                        descending: !sort.asc,
549                        nulls_first: sort.nulls_first,
550                    },
551                })
552            })
553            .collect::<Result<Vec<_>>>()?;
554        let Some(ordering) = LexOrdering::new(exprs) else {
555            return internal_err!("SortPreservingMergeExec requires an ordering");
556        };
557        let fetch = (spm.fetch >= 0).then_some(spm.fetch as usize);
558        Ok(Arc::new(
559            SortPreservingMergeExec::new(ordering, input).with_fetch(fetch),
560        ))
561    }
562}
563
564#[cfg(test)]
565mod tests {
566    use std::collections::HashSet;
567    use std::fmt::Formatter;
568    use std::pin::Pin;
569    use std::sync::Mutex;
570    use std::task::{Context, Poll, Waker, ready};
571    use std::time::Duration;
572
573    use super::*;
574    use crate::coalesce_partitions::CoalescePartitionsExec;
575    use crate::execution_plan::{Boundedness, EmissionType};
576    use crate::expressions::col;
577    use crate::metrics::{MetricValue, Timestamp};
578    use crate::repartition::RepartitionExec;
579    use crate::sorts::sort::SortExec;
580    use crate::statistics::StatisticsContext;
581    use crate::stream::RecordBatchReceiverStream;
582    use crate::test::TestMemoryExec;
583    use crate::test::exec::{
584        BlockingExec, StatisticsExec, assert_strong_count_converges_to_zero,
585    };
586    use crate::test::{self, assert_is_pending, make_partition};
587    use crate::{collect, common};
588
589    use arrow::array::{
590        ArrayRef, Int32Array, Int64Array, RecordBatch, StringArray,
591        TimestampNanosecondArray,
592    };
593    use arrow::compute::SortOptions;
594    use arrow::datatypes::{DataType, Field, Schema, SchemaRef};
595    use datafusion_common::stats::Precision;
596    use datafusion_common::test_util::batches_to_string;
597    use datafusion_common::{ColumnStatistics, assert_batches_eq, exec_err};
598    use datafusion_common_runtime::SpawnedTask;
599    use datafusion_execution::RecordBatchStream;
600    use datafusion_execution::config::SessionConfig;
601    use datafusion_execution::runtime_env::RuntimeEnvBuilder;
602    use datafusion_physical_expr::EquivalenceProperties;
603    use datafusion_physical_expr::expressions::Column;
604    use datafusion_physical_expr_common::physical_expr::PhysicalExpr;
605    use datafusion_physical_expr_common::sort_expr::PhysicalSortExpr;
606
607    use futures::{FutureExt, Stream, StreamExt};
608    use insta::assert_snapshot;
609    use tokio::time::timeout;
610
611    // The number in the function is highly related to the memory limit we are testing
612    // any change of the constant should be aware of
613    fn generate_task_ctx_for_round_robin_tie_breaker(
614        target_batch_size: usize,
615    ) -> Result<Arc<TaskContext>> {
616        let runtime = RuntimeEnvBuilder::new()
617            .with_memory_limit(20_000_000, 1.0)
618            .build_arc()?;
619        let mut config = SessionConfig::new();
620        config.options_mut().execution.batch_size =
621            datafusion_common::config::ConfigNonZeroUsize::try_new(target_batch_size)?;
622        let task_ctx = TaskContext::default()
623            .with_runtime(runtime)
624            .with_session_config(config);
625        Ok(Arc::new(task_ctx))
626    }
627
628    // The number in the function is highly related to the memory limit we are testing,
629    // any change of the constant should be aware of
630    fn generate_spm_for_round_robin_tie_breaker(
631        enable_round_robin_repartition: bool,
632    ) -> Result<Arc<SortPreservingMergeExec>> {
633        let row_size = 12500;
634        let a: ArrayRef = Arc::new(Int32Array::from(vec![1; row_size]));
635        let b: ArrayRef = Arc::new(StringArray::from_iter(vec![Some("a"); row_size]));
636        let c: ArrayRef = Arc::new(Int64Array::from_iter(vec![0; row_size]));
637        let rb = RecordBatch::try_from_iter(vec![("a", a), ("b", b), ("c", c)])?;
638        let schema = rb.schema();
639
640        let rbs = std::iter::repeat_n(rb, 1024).collect::<Vec<_>>();
641        let sort = [
642            PhysicalSortExpr {
643                expr: col("b", &schema)?,
644                options: Default::default(),
645            },
646            PhysicalSortExpr {
647                expr: col("c", &schema)?,
648                options: Default::default(),
649            },
650        ]
651        .into();
652
653        let repartition_exec = RepartitionExec::try_new(
654            TestMemoryExec::try_new_exec(&[rbs], schema, None)?,
655            Partitioning::RoundRobinBatch(2),
656        )?;
657        let spm = SortPreservingMergeExec::new(sort, Arc::new(repartition_exec))
658            .with_round_robin_repartition(enable_round_robin_repartition);
659        Ok(Arc::new(spm))
660    }
661
662    #[test]
663    fn test_fetch_caps_statistics() -> Result<()> {
664        let schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]);
665        let input = Arc::new(StatisticsExec::new(
666            Statistics {
667                num_rows: Precision::Exact(1_000),
668                total_byte_size: Precision::Exact(8_000),
669                column_statistics: vec![ColumnStatistics::new_unknown()],
670            },
671            schema.clone(),
672        ));
673        let sort = [PhysicalSortExpr::new_default(Arc::new(Column::new("a", 0)))].into();
674
675        let spm = SortPreservingMergeExec::new(sort, input).with_fetch(Some(1));
676        let statistics =
677            StatisticsContext::new().compute(&spm, &StatisticsArgs::new())?;
678
679        assert_eq!(statistics.num_rows, Precision::Exact(1));
680        assert_eq!(statistics.total_byte_size, Precision::Inexact(8));
681        assert!(matches!(
682            spm.cardinality_effect(),
683            CardinalityEffect::LowerEqual
684        ));
685        Ok(())
686    }
687
688    #[test]
689    fn test_no_fetch_preserves_statistics() -> Result<()> {
690        let schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]);
691        let input_stats = Statistics {
692            num_rows: Precision::Absent,
693            total_byte_size: Precision::Exact(8_000),
694            column_statistics: vec![ColumnStatistics::new_unknown()],
695        };
696        let input = Arc::new(StatisticsExec::new(input_stats.clone(), schema.clone()));
697        let sort = [PhysicalSortExpr::new_default(Arc::new(Column::new("a", 0)))].into();
698
699        let spm = SortPreservingMergeExec::new(sort, input);
700        let statistics =
701            StatisticsContext::new().compute(&spm, &StatisticsArgs::new())?;
702
703        assert_eq!(*statistics, input_stats);
704        assert!(matches!(spm.cardinality_effect(), CardinalityEffect::Equal));
705        Ok(())
706    }
707
708    /// This test verifies that memory usage stays within limits when the tie breaker is enabled.
709    /// Any errors here could indicate unintended changes in tie breaker logic.
710    ///
711    /// Note: If you adjust constants in this test, ensure that memory usage differs
712    /// based on whether the tie breaker is enabled or disabled.
713    #[tokio::test(flavor = "multi_thread")]
714    async fn test_round_robin_tie_breaker_success() -> Result<()> {
715        let target_batch_size = 12500;
716        let task_ctx = generate_task_ctx_for_round_robin_tie_breaker(target_batch_size)?;
717        let spm = generate_spm_for_round_robin_tie_breaker(true)?;
718        let _collected = collect(spm, task_ctx).await?;
719        Ok(())
720    }
721
722    /// This test verifies that memory usage stays within limits when the tie breaker is enabled.
723    /// Any errors here could indicate unintended changes in tie breaker logic.
724    ///
725    /// Note: If you adjust constants in this test, ensure that memory usage differs
726    /// based on whether the tie breaker is enabled or disabled.
727    #[tokio::test(flavor = "multi_thread")]
728    async fn test_round_robin_tie_breaker_fail() -> Result<()> {
729        let task_ctx = generate_task_ctx_for_round_robin_tie_breaker(8192)?;
730        let spm = generate_spm_for_round_robin_tie_breaker(false)?;
731        let _err = collect(spm, task_ctx).await.unwrap_err();
732        Ok(())
733    }
734
735    #[tokio::test]
736    async fn test_merge_interleave() {
737        let task_ctx = Arc::new(TaskContext::default());
738        let a: ArrayRef = Arc::new(Int32Array::from(vec![1, 2, 7, 9, 3]));
739        let b: ArrayRef = Arc::new(StringArray::from_iter(vec![
740            Some("a"),
741            Some("c"),
742            Some("e"),
743            Some("g"),
744            Some("j"),
745        ]));
746        let c: ArrayRef = Arc::new(TimestampNanosecondArray::from(vec![8, 7, 6, 5, 8]));
747        let b1 = RecordBatch::try_from_iter(vec![("a", a), ("b", b), ("c", c)]).unwrap();
748
749        let a: ArrayRef = Arc::new(Int32Array::from(vec![10, 20, 70, 90, 30]));
750        let b: ArrayRef = Arc::new(StringArray::from_iter(vec![
751            Some("b"),
752            Some("d"),
753            Some("f"),
754            Some("h"),
755            Some("j"),
756        ]));
757        let c: ArrayRef = Arc::new(TimestampNanosecondArray::from(vec![4, 6, 2, 2, 6]));
758        let b2 = RecordBatch::try_from_iter(vec![("a", a), ("b", b), ("c", c)]).unwrap();
759
760        _test_merge(
761            &[vec![b1], vec![b2]],
762            &[
763                "+----+---+-------------------------------+",
764                "| a  | b | c                             |",
765                "+----+---+-------------------------------+",
766                "| 1  | a | 1970-01-01T00:00:00.000000008 |",
767                "| 10 | b | 1970-01-01T00:00:00.000000004 |",
768                "| 2  | c | 1970-01-01T00:00:00.000000007 |",
769                "| 20 | d | 1970-01-01T00:00:00.000000006 |",
770                "| 7  | e | 1970-01-01T00:00:00.000000006 |",
771                "| 70 | f | 1970-01-01T00:00:00.000000002 |",
772                "| 9  | g | 1970-01-01T00:00:00.000000005 |",
773                "| 90 | h | 1970-01-01T00:00:00.000000002 |",
774                "| 30 | j | 1970-01-01T00:00:00.000000006 |", // input b2 before b1
775                "| 3  | j | 1970-01-01T00:00:00.000000008 |",
776                "+----+---+-------------------------------+",
777            ],
778            task_ctx,
779        )
780        .await;
781    }
782
783    #[tokio::test]
784    async fn test_merge_some_overlap() {
785        let task_ctx = Arc::new(TaskContext::default());
786        let a: ArrayRef = Arc::new(Int32Array::from(vec![1, 2, 7, 9, 3]));
787        let b: ArrayRef = Arc::new(StringArray::from_iter(vec![
788            Some("a"),
789            Some("b"),
790            Some("c"),
791            Some("d"),
792            Some("e"),
793        ]));
794        let c: ArrayRef = Arc::new(TimestampNanosecondArray::from(vec![8, 7, 6, 5, 8]));
795        let b1 = RecordBatch::try_from_iter(vec![("a", a), ("b", b), ("c", c)]).unwrap();
796
797        let a: ArrayRef = Arc::new(Int32Array::from(vec![70, 90, 30, 100, 110]));
798        let b: ArrayRef = Arc::new(StringArray::from_iter(vec![
799            Some("c"),
800            Some("d"),
801            Some("e"),
802            Some("f"),
803            Some("g"),
804        ]));
805        let c: ArrayRef = Arc::new(TimestampNanosecondArray::from(vec![4, 6, 2, 2, 6]));
806        let b2 = RecordBatch::try_from_iter(vec![("a", a), ("b", b), ("c", c)]).unwrap();
807
808        _test_merge(
809            &[vec![b1], vec![b2]],
810            &[
811                "+-----+---+-------------------------------+",
812                "| a   | b | c                             |",
813                "+-----+---+-------------------------------+",
814                "| 1   | a | 1970-01-01T00:00:00.000000008 |",
815                "| 2   | b | 1970-01-01T00:00:00.000000007 |",
816                "| 70  | c | 1970-01-01T00:00:00.000000004 |",
817                "| 7   | c | 1970-01-01T00:00:00.000000006 |",
818                "| 9   | d | 1970-01-01T00:00:00.000000005 |",
819                "| 90  | d | 1970-01-01T00:00:00.000000006 |",
820                "| 30  | e | 1970-01-01T00:00:00.000000002 |",
821                "| 3   | e | 1970-01-01T00:00:00.000000008 |",
822                "| 100 | f | 1970-01-01T00:00:00.000000002 |",
823                "| 110 | g | 1970-01-01T00:00:00.000000006 |",
824                "+-----+---+-------------------------------+",
825            ],
826            task_ctx,
827        )
828        .await;
829    }
830
831    #[tokio::test]
832    async fn test_merge_no_overlap() {
833        let task_ctx = Arc::new(TaskContext::default());
834        let a: ArrayRef = Arc::new(Int32Array::from(vec![1, 2, 7, 9, 3]));
835        let b: ArrayRef = Arc::new(StringArray::from_iter(vec![
836            Some("a"),
837            Some("b"),
838            Some("c"),
839            Some("d"),
840            Some("e"),
841        ]));
842        let c: ArrayRef = Arc::new(TimestampNanosecondArray::from(vec![8, 7, 6, 5, 8]));
843        let b1 = RecordBatch::try_from_iter(vec![("a", a), ("b", b), ("c", c)]).unwrap();
844
845        let a: ArrayRef = Arc::new(Int32Array::from(vec![10, 20, 70, 90, 30]));
846        let b: ArrayRef = Arc::new(StringArray::from_iter(vec![
847            Some("f"),
848            Some("g"),
849            Some("h"),
850            Some("i"),
851            Some("j"),
852        ]));
853        let c: ArrayRef = Arc::new(TimestampNanosecondArray::from(vec![4, 6, 2, 2, 6]));
854        let b2 = RecordBatch::try_from_iter(vec![("a", a), ("b", b), ("c", c)]).unwrap();
855
856        _test_merge(
857            &[vec![b1], vec![b2]],
858            &[
859                "+----+---+-------------------------------+",
860                "| a  | b | c                             |",
861                "+----+---+-------------------------------+",
862                "| 1  | a | 1970-01-01T00:00:00.000000008 |",
863                "| 2  | b | 1970-01-01T00:00:00.000000007 |",
864                "| 7  | c | 1970-01-01T00:00:00.000000006 |",
865                "| 9  | d | 1970-01-01T00:00:00.000000005 |",
866                "| 3  | e | 1970-01-01T00:00:00.000000008 |",
867                "| 10 | f | 1970-01-01T00:00:00.000000004 |",
868                "| 20 | g | 1970-01-01T00:00:00.000000006 |",
869                "| 70 | h | 1970-01-01T00:00:00.000000002 |",
870                "| 90 | i | 1970-01-01T00:00:00.000000002 |",
871                "| 30 | j | 1970-01-01T00:00:00.000000006 |",
872                "+----+---+-------------------------------+",
873            ],
874            task_ctx,
875        )
876        .await;
877    }
878
879    #[tokio::test]
880    async fn test_merge_three_partitions() {
881        let task_ctx = Arc::new(TaskContext::default());
882        let a: ArrayRef = Arc::new(Int32Array::from(vec![1, 2, 7, 9, 3]));
883        let b: ArrayRef = Arc::new(StringArray::from_iter(vec![
884            Some("a"),
885            Some("b"),
886            Some("c"),
887            Some("d"),
888            Some("f"),
889        ]));
890        let c: ArrayRef = Arc::new(TimestampNanosecondArray::from(vec![8, 7, 6, 5, 8]));
891        let b1 = RecordBatch::try_from_iter(vec![("a", a), ("b", b), ("c", c)]).unwrap();
892
893        let a: ArrayRef = Arc::new(Int32Array::from(vec![10, 20, 70, 90, 30]));
894        let b: ArrayRef = Arc::new(StringArray::from_iter(vec![
895            Some("e"),
896            Some("g"),
897            Some("h"),
898            Some("i"),
899            Some("j"),
900        ]));
901        let c: ArrayRef =
902            Arc::new(TimestampNanosecondArray::from(vec![40, 60, 20, 20, 60]));
903        let b2 = RecordBatch::try_from_iter(vec![("a", a), ("b", b), ("c", c)]).unwrap();
904
905        let a: ArrayRef = Arc::new(Int32Array::from(vec![100, 200, 700, 900, 300]));
906        let b: ArrayRef = Arc::new(StringArray::from_iter(vec![
907            Some("f"),
908            Some("g"),
909            Some("h"),
910            Some("i"),
911            Some("j"),
912        ]));
913        let c: ArrayRef = Arc::new(TimestampNanosecondArray::from(vec![4, 6, 2, 2, 6]));
914        let b3 = RecordBatch::try_from_iter(vec![("a", a), ("b", b), ("c", c)]).unwrap();
915
916        _test_merge(
917            &[vec![b1], vec![b2], vec![b3]],
918            &[
919                "+-----+---+-------------------------------+",
920                "| a   | b | c                             |",
921                "+-----+---+-------------------------------+",
922                "| 1   | a | 1970-01-01T00:00:00.000000008 |",
923                "| 2   | b | 1970-01-01T00:00:00.000000007 |",
924                "| 7   | c | 1970-01-01T00:00:00.000000006 |",
925                "| 9   | d | 1970-01-01T00:00:00.000000005 |",
926                "| 10  | e | 1970-01-01T00:00:00.000000040 |",
927                "| 100 | f | 1970-01-01T00:00:00.000000004 |",
928                "| 3   | f | 1970-01-01T00:00:00.000000008 |",
929                "| 200 | g | 1970-01-01T00:00:00.000000006 |",
930                "| 20  | g | 1970-01-01T00:00:00.000000060 |",
931                "| 700 | h | 1970-01-01T00:00:00.000000002 |",
932                "| 70  | h | 1970-01-01T00:00:00.000000020 |",
933                "| 900 | i | 1970-01-01T00:00:00.000000002 |",
934                "| 90  | i | 1970-01-01T00:00:00.000000020 |",
935                "| 300 | j | 1970-01-01T00:00:00.000000006 |",
936                "| 30  | j | 1970-01-01T00:00:00.000000060 |",
937                "+-----+---+-------------------------------+",
938            ],
939            task_ctx,
940        )
941        .await;
942    }
943
944    async fn _test_merge(
945        partitions: &[Vec<RecordBatch>],
946        exp: &[&str],
947        context: Arc<TaskContext>,
948    ) {
949        let schema = partitions[0][0].schema();
950        let sort = [
951            PhysicalSortExpr {
952                expr: col("b", &schema).unwrap(),
953                options: Default::default(),
954            },
955            PhysicalSortExpr {
956                expr: col("c", &schema).unwrap(),
957                options: Default::default(),
958            },
959        ]
960        .into();
961        let exec = TestMemoryExec::try_new_exec(partitions, schema, None).unwrap();
962        let merge = Arc::new(SortPreservingMergeExec::new(sort, exec));
963
964        let collected = collect(merge, context).await.unwrap();
965        assert_batches_eq!(exp, collected.as_slice());
966    }
967
968    async fn sorted_merge(
969        input: Arc<dyn ExecutionPlan>,
970        sort: LexOrdering,
971        context: Arc<TaskContext>,
972    ) -> RecordBatch {
973        let merge = Arc::new(SortPreservingMergeExec::new(sort, input));
974        let mut result = collect(merge, context).await.unwrap();
975        assert_eq!(result.len(), 1);
976        result.remove(0)
977    }
978
979    async fn partition_sort(
980        input: Arc<dyn ExecutionPlan>,
981        sort: LexOrdering,
982        context: Arc<TaskContext>,
983    ) -> RecordBatch {
984        let sort_exec =
985            Arc::new(SortExec::new(sort.clone(), input).with_preserve_partitioning(true));
986        sorted_merge(sort_exec, sort, context).await
987    }
988
989    async fn basic_sort(
990        src: Arc<dyn ExecutionPlan>,
991        sort: LexOrdering,
992        context: Arc<TaskContext>,
993    ) -> RecordBatch {
994        let merge = Arc::new(CoalescePartitionsExec::new(src));
995        let sort_exec = Arc::new(SortExec::new(sort, merge));
996        let mut result = collect(sort_exec, context).await.unwrap();
997        assert_eq!(result.len(), 1);
998        result.remove(0)
999    }
1000
1001    #[tokio::test]
1002    async fn test_partition_sort() -> Result<()> {
1003        let task_ctx = Arc::new(TaskContext::default());
1004        let partitions = 4;
1005        let csv = test::scan_partitioned(partitions);
1006        let schema = csv.schema();
1007
1008        let sort: LexOrdering = [PhysicalSortExpr {
1009            expr: col("i", &schema)?,
1010            options: SortOptions {
1011                descending: true,
1012                nulls_first: true,
1013            },
1014        }]
1015        .into();
1016
1017        let basic =
1018            basic_sort(Arc::clone(&csv), sort.clone(), Arc::clone(&task_ctx)).await;
1019        let partition = partition_sort(csv, sort, Arc::clone(&task_ctx)).await;
1020
1021        let basic = arrow::util::pretty::pretty_format_batches(&[basic])
1022            .unwrap()
1023            .to_string();
1024        let partition = arrow::util::pretty::pretty_format_batches(&[partition])
1025            .unwrap()
1026            .to_string();
1027
1028        assert_eq!(
1029            basic, partition,
1030            "basic:\n\n{basic}\n\npartition:\n\n{partition}\n\n"
1031        );
1032
1033        Ok(())
1034    }
1035
1036    // Split the provided record batch into multiple batch_size record batches
1037    fn split_batch(sorted: &RecordBatch, batch_size: usize) -> Vec<RecordBatch> {
1038        let batches = sorted.num_rows().div_ceil(batch_size);
1039
1040        // Split the sorted RecordBatch into multiple
1041        (0..batches)
1042            .map(|batch_idx| {
1043                let columns = (0..sorted.num_columns())
1044                    .map(|column_idx| {
1045                        let length =
1046                            batch_size.min(sorted.num_rows() - batch_idx * batch_size);
1047
1048                        sorted
1049                            .column(column_idx)
1050                            .slice(batch_idx * batch_size, length)
1051                    })
1052                    .collect();
1053
1054                RecordBatch::try_new(sorted.schema(), columns).unwrap()
1055            })
1056            .collect()
1057    }
1058
1059    async fn sorted_partitioned_input(
1060        sort: LexOrdering,
1061        sizes: &[usize],
1062        context: Arc<TaskContext>,
1063    ) -> Result<Arc<dyn ExecutionPlan>> {
1064        let partitions = 4;
1065        let csv = test::scan_partitioned(partitions);
1066
1067        let sorted = basic_sort(csv, sort, context).await;
1068        let split: Vec<_> = sizes.iter().map(|x| split_batch(&sorted, *x)).collect();
1069
1070        TestMemoryExec::try_new_exec(&split, sorted.schema(), None).map(|e| e as _)
1071    }
1072
1073    #[tokio::test]
1074    async fn test_partition_sort_streaming_input() -> Result<()> {
1075        let task_ctx = Arc::new(TaskContext::default());
1076        let schema = make_partition(11).schema();
1077        let sort: LexOrdering = [PhysicalSortExpr {
1078            expr: col("i", &schema)?,
1079            options: Default::default(),
1080        }]
1081        .into();
1082
1083        let input =
1084            sorted_partitioned_input(sort.clone(), &[10, 3, 11], Arc::clone(&task_ctx))
1085                .await?;
1086        let basic =
1087            basic_sort(Arc::clone(&input), sort.clone(), Arc::clone(&task_ctx)).await;
1088        let partition = sorted_merge(input, sort, Arc::clone(&task_ctx)).await;
1089
1090        assert_eq!(basic.num_rows(), 1200);
1091        assert_eq!(partition.num_rows(), 1200);
1092
1093        let basic = arrow::util::pretty::pretty_format_batches(&[basic])?.to_string();
1094        let partition =
1095            arrow::util::pretty::pretty_format_batches(&[partition])?.to_string();
1096
1097        assert_eq!(basic, partition);
1098
1099        Ok(())
1100    }
1101
1102    #[tokio::test]
1103    async fn test_partition_sort_streaming_input_output() -> Result<()> {
1104        let schema = make_partition(11).schema();
1105        let sort: LexOrdering = [PhysicalSortExpr {
1106            expr: col("i", &schema)?,
1107            options: Default::default(),
1108        }]
1109        .into();
1110
1111        // Test streaming with default batch size
1112        let task_ctx = Arc::new(TaskContext::default());
1113        let input =
1114            sorted_partitioned_input(sort.clone(), &[10, 5, 13], Arc::clone(&task_ctx))
1115                .await?;
1116        let basic = basic_sort(Arc::clone(&input), sort.clone(), task_ctx).await;
1117
1118        // batch size of 23
1119        let task_ctx = TaskContext::default()
1120            .with_session_config(SessionConfig::new().with_batch_size(23));
1121        let task_ctx = Arc::new(task_ctx);
1122
1123        let merge = Arc::new(SortPreservingMergeExec::new(sort, input));
1124        let merged = collect(merge, task_ctx).await?;
1125
1126        assert_eq!(merged.len(), 53);
1127        assert_eq!(basic.num_rows(), 1200);
1128        assert_eq!(merged.iter().map(|x| x.num_rows()).sum::<usize>(), 1200);
1129
1130        let basic = arrow::util::pretty::pretty_format_batches(&[basic])?.to_string();
1131        let partition = arrow::util::pretty::pretty_format_batches(&merged)?.to_string();
1132
1133        assert_eq!(basic, partition);
1134
1135        Ok(())
1136    }
1137
1138    #[tokio::test]
1139    async fn test_nulls() {
1140        let task_ctx = Arc::new(TaskContext::default());
1141        let a: ArrayRef = Arc::new(Int32Array::from(vec![1, 2, 7, 9, 3]));
1142        let b: ArrayRef = Arc::new(StringArray::from_iter(vec![
1143            None,
1144            Some("a"),
1145            Some("b"),
1146            Some("d"),
1147            Some("e"),
1148        ]));
1149        let c: ArrayRef = Arc::new(TimestampNanosecondArray::from(vec![
1150            Some(8),
1151            None,
1152            Some(6),
1153            None,
1154            Some(4),
1155        ]));
1156        let b1 = RecordBatch::try_from_iter(vec![("a", a), ("b", b), ("c", c)]).unwrap();
1157
1158        let a: ArrayRef = Arc::new(Int32Array::from(vec![1, 2, 3, 4, 5]));
1159        let b: ArrayRef = Arc::new(StringArray::from_iter(vec![
1160            None,
1161            Some("b"),
1162            Some("g"),
1163            Some("h"),
1164            Some("i"),
1165        ]));
1166        let c: ArrayRef = Arc::new(TimestampNanosecondArray::from(vec![
1167            Some(8),
1168            None,
1169            Some(5),
1170            None,
1171            Some(4),
1172        ]));
1173        let b2 = RecordBatch::try_from_iter(vec![("a", a), ("b", b), ("c", c)]).unwrap();
1174        let schema = b1.schema();
1175
1176        let sort = [
1177            PhysicalSortExpr {
1178                expr: col("b", &schema).unwrap(),
1179                options: SortOptions {
1180                    descending: false,
1181                    nulls_first: true,
1182                },
1183            },
1184            PhysicalSortExpr {
1185                expr: col("c", &schema).unwrap(),
1186                options: SortOptions {
1187                    descending: false,
1188                    nulls_first: false,
1189                },
1190            },
1191        ]
1192        .into();
1193        let exec =
1194            TestMemoryExec::try_new_exec(&[vec![b1], vec![b2]], schema, None).unwrap();
1195        let merge = Arc::new(SortPreservingMergeExec::new(sort, exec));
1196
1197        let collected = collect(merge, task_ctx).await.unwrap();
1198        assert_eq!(collected.len(), 1);
1199
1200        assert_snapshot!(batches_to_string(collected.as_slice()), @r"
1201        +---+---+-------------------------------+
1202        | a | b | c                             |
1203        +---+---+-------------------------------+
1204        | 1 |   | 1970-01-01T00:00:00.000000008 |
1205        | 1 |   | 1970-01-01T00:00:00.000000008 |
1206        | 2 | a |                               |
1207        | 7 | b | 1970-01-01T00:00:00.000000006 |
1208        | 2 | b |                               |
1209        | 9 | d |                               |
1210        | 3 | e | 1970-01-01T00:00:00.000000004 |
1211        | 3 | g | 1970-01-01T00:00:00.000000005 |
1212        | 4 | h |                               |
1213        | 5 | i | 1970-01-01T00:00:00.000000004 |
1214        +---+---+-------------------------------+
1215        ");
1216    }
1217
1218    #[tokio::test]
1219    async fn test_sort_merge_single_partition_with_fetch() {
1220        let task_ctx = Arc::new(TaskContext::default());
1221        let a: ArrayRef = Arc::new(Int32Array::from(vec![1, 2, 7, 9, 3]));
1222        let b: ArrayRef = Arc::new(StringArray::from(vec!["a", "b", "c", "d", "e"]));
1223        let batch = RecordBatch::try_from_iter(vec![("a", a), ("b", b)]).unwrap();
1224        let schema = batch.schema();
1225
1226        let sort = [PhysicalSortExpr {
1227            expr: col("b", &schema).unwrap(),
1228            options: SortOptions {
1229                descending: false,
1230                nulls_first: true,
1231            },
1232        }]
1233        .into();
1234        let exec = TestMemoryExec::try_new_exec(&[vec![batch]], schema, None).unwrap();
1235        let merge =
1236            Arc::new(SortPreservingMergeExec::new(sort, exec).with_fetch(Some(2)));
1237
1238        let collected = collect(merge, task_ctx).await.unwrap();
1239        assert_eq!(collected.len(), 1);
1240
1241        assert_snapshot!(batches_to_string(collected.as_slice()), @r"
1242        +---+---+
1243        | a | b |
1244        +---+---+
1245        | 1 | a |
1246        | 2 | b |
1247        +---+---+
1248        ");
1249    }
1250
1251    #[tokio::test]
1252    async fn test_sort_merge_single_partition_without_fetch() {
1253        let task_ctx = Arc::new(TaskContext::default());
1254        let a: ArrayRef = Arc::new(Int32Array::from(vec![1, 2, 7, 9, 3]));
1255        let b: ArrayRef = Arc::new(StringArray::from(vec!["a", "b", "c", "d", "e"]));
1256        let batch = RecordBatch::try_from_iter(vec![("a", a), ("b", b)]).unwrap();
1257        let schema = batch.schema();
1258
1259        let sort = [PhysicalSortExpr {
1260            expr: col("b", &schema).unwrap(),
1261            options: SortOptions {
1262                descending: false,
1263                nulls_first: true,
1264            },
1265        }]
1266        .into();
1267        let exec = TestMemoryExec::try_new_exec(&[vec![batch]], schema, None).unwrap();
1268        let merge = Arc::new(SortPreservingMergeExec::new(sort, exec));
1269
1270        let collected = collect(merge, task_ctx).await.unwrap();
1271        assert_eq!(collected.len(), 1);
1272
1273        assert_snapshot!(batches_to_string(collected.as_slice()), @r"
1274        +---+---+
1275        | a | b |
1276        +---+---+
1277        | 1 | a |
1278        | 2 | b |
1279        | 7 | c |
1280        | 9 | d |
1281        | 3 | e |
1282        +---+---+
1283        ");
1284    }
1285
1286    #[tokio::test]
1287    async fn test_async() -> Result<()> {
1288        let task_ctx = Arc::new(TaskContext::default());
1289        let schema = make_partition(11).schema();
1290        let sort: LexOrdering = [PhysicalSortExpr {
1291            expr: col("i", &schema).unwrap(),
1292            options: SortOptions::default(),
1293        }]
1294        .into();
1295
1296        let batches =
1297            sorted_partitioned_input(sort.clone(), &[5, 7, 3], Arc::clone(&task_ctx))
1298                .await?;
1299
1300        let partition_count = batches.output_partitioning().partition_count();
1301        let mut streams = Vec::with_capacity(partition_count);
1302
1303        for partition in 0..partition_count {
1304            let mut builder = RecordBatchReceiverStream::builder(Arc::clone(&schema), 1);
1305
1306            let sender = builder.tx();
1307
1308            let mut stream = batches.execute(partition, Arc::clone(&task_ctx)).unwrap();
1309            builder.spawn(async move {
1310                while let Some(batch) = stream.next().await {
1311                    sender.send(batch).await.unwrap();
1312                    // This causes the MergeStream to wait for more input
1313                    tokio::time::sleep(Duration::from_millis(10)).await;
1314                }
1315
1316                Ok(())
1317            });
1318
1319            streams.push(builder.build());
1320        }
1321
1322        let metrics = ExecutionPlanMetricsSet::new();
1323        let reservation =
1324            MemoryConsumer::new("test").register(&task_ctx.runtime_env().memory_pool);
1325
1326        let fetch = None;
1327        let merge_stream = StreamingMergeBuilder::new()
1328            .with_streams(streams)
1329            .with_schema(batches.schema())
1330            .with_expressions(&sort)
1331            .with_metrics(BaselineMetrics::new(&metrics, 0))
1332            .with_batch_size(task_ctx.session_config().batch_size())
1333            .with_fetch(fetch)
1334            .with_reservation(reservation)
1335            .build()?;
1336
1337        let mut merged = common::collect(merge_stream).await.unwrap();
1338
1339        assert_eq!(merged.len(), 1);
1340        let merged = merged.remove(0);
1341        let basic = basic_sort(batches, sort.clone(), Arc::clone(&task_ctx)).await;
1342
1343        let basic = arrow::util::pretty::pretty_format_batches(&[basic])
1344            .unwrap()
1345            .to_string();
1346        let partition = arrow::util::pretty::pretty_format_batches(&[merged])
1347            .unwrap()
1348            .to_string();
1349
1350        assert_eq!(
1351            basic, partition,
1352            "basic:\n\n{basic}\n\npartition:\n\n{partition}\n\n"
1353        );
1354
1355        Ok(())
1356    }
1357
1358    #[tokio::test]
1359    async fn test_merge_metrics() {
1360        let task_ctx = Arc::new(TaskContext::default());
1361        let a: ArrayRef = Arc::new(Int32Array::from(vec![1, 2]));
1362        let b: ArrayRef = Arc::new(StringArray::from_iter(vec![Some("a"), Some("c")]));
1363        let b1 = RecordBatch::try_from_iter(vec![("a", a), ("b", b)]).unwrap();
1364
1365        let a: ArrayRef = Arc::new(Int32Array::from(vec![10, 20]));
1366        let b: ArrayRef = Arc::new(StringArray::from_iter(vec![Some("b"), Some("d")]));
1367        let b2 = RecordBatch::try_from_iter(vec![("a", a), ("b", b)]).unwrap();
1368
1369        let schema = b1.schema();
1370        let sort = [PhysicalSortExpr {
1371            expr: col("b", &schema).unwrap(),
1372            options: Default::default(),
1373        }]
1374        .into();
1375        let exec =
1376            TestMemoryExec::try_new_exec(&[vec![b1], vec![b2]], schema, None).unwrap();
1377        let merge = Arc::new(SortPreservingMergeExec::new(sort, exec));
1378
1379        let collected = collect(Arc::clone(&merge) as Arc<dyn ExecutionPlan>, task_ctx)
1380            .await
1381            .unwrap();
1382        assert_snapshot!(batches_to_string(collected.as_slice()), @r"
1383        +----+---+
1384        | a  | b |
1385        +----+---+
1386        | 1  | a |
1387        | 10 | b |
1388        | 2  | c |
1389        | 20 | d |
1390        +----+---+
1391        ");
1392
1393        // Now, validate metrics
1394        let metrics = merge.metrics().unwrap();
1395
1396        assert_eq!(metrics.output_rows().unwrap(), 4);
1397        assert!(metrics.elapsed_compute().unwrap() > 0);
1398
1399        let mut saw_start = false;
1400        let mut saw_end = false;
1401        metrics.iter().for_each(|m| match m.value() {
1402            MetricValue::StartTimestamp(ts) => {
1403                saw_start = true;
1404                assert!(nanos_from_timestamp(ts) > 0);
1405            }
1406            MetricValue::EndTimestamp(ts) => {
1407                saw_end = true;
1408                assert!(nanos_from_timestamp(ts) > 0);
1409            }
1410            _ => {}
1411        });
1412
1413        assert!(saw_start);
1414        assert!(saw_end);
1415    }
1416
1417    fn nanos_from_timestamp(ts: &Timestamp) -> i64 {
1418        ts.value().unwrap().timestamp_nanos_opt().unwrap()
1419    }
1420
1421    #[tokio::test]
1422    async fn test_drop_cancel() -> Result<()> {
1423        let task_ctx = Arc::new(TaskContext::default());
1424        let schema =
1425            Arc::new(Schema::new(vec![Field::new("a", DataType::Float32, true)]));
1426
1427        let blocking_exec = Arc::new(BlockingExec::new(Arc::clone(&schema), 2));
1428        let refs = blocking_exec.refs();
1429        let sort_preserving_merge_exec = Arc::new(SortPreservingMergeExec::new(
1430            [PhysicalSortExpr {
1431                expr: col("a", &schema)?,
1432                options: SortOptions::default(),
1433            }]
1434            .into(),
1435            blocking_exec,
1436        ));
1437
1438        let fut = collect(sort_preserving_merge_exec, task_ctx);
1439        let mut fut = fut.boxed();
1440
1441        assert_is_pending(&mut fut);
1442        drop(fut);
1443        assert_strong_count_converges_to_zero(refs).await;
1444
1445        Ok(())
1446    }
1447
1448    #[tokio::test]
1449    async fn test_stable_sort() {
1450        let task_ctx = Arc::new(TaskContext::default());
1451
1452        // Create record batches like:
1453        // batch_number |value
1454        // -------------+------
1455        //    1         | A
1456        //    1         | B
1457        //
1458        // Ensure that the output is in the same order the batches were fed
1459        let partitions: Vec<Vec<RecordBatch>> = (0..10)
1460            .map(|batch_number| {
1461                let batch_number: Int32Array =
1462                    vec![Some(batch_number), Some(batch_number)]
1463                        .into_iter()
1464                        .collect();
1465                let value: StringArray = vec![Some("A"), Some("B")].into_iter().collect();
1466
1467                let batch = RecordBatch::try_from_iter(vec![
1468                    ("batch_number", Arc::new(batch_number) as ArrayRef),
1469                    ("value", Arc::new(value) as ArrayRef),
1470                ])
1471                .unwrap();
1472
1473                vec![batch]
1474            })
1475            .collect();
1476
1477        let schema = partitions[0][0].schema();
1478
1479        let sort = [PhysicalSortExpr {
1480            expr: col("value", &schema).unwrap(),
1481            options: SortOptions {
1482                descending: false,
1483                nulls_first: true,
1484            },
1485        }]
1486        .into();
1487
1488        let exec = TestMemoryExec::try_new_exec(&partitions, schema, None).unwrap();
1489        let merge = Arc::new(SortPreservingMergeExec::new(sort, exec));
1490
1491        let collected = collect(merge, task_ctx).await.unwrap();
1492        assert_eq!(collected.len(), 1);
1493
1494        // Expect the data to be sorted first by "batch_number" (because
1495        // that was the order it was fed in, even though only "value"
1496        // is in the sort key)
1497        assert_snapshot!(batches_to_string(collected.as_slice()), @r"
1498        +--------------+-------+
1499        | batch_number | value |
1500        +--------------+-------+
1501        | 0            | A     |
1502        | 1            | A     |
1503        | 2            | A     |
1504        | 3            | A     |
1505        | 4            | A     |
1506        | 5            | A     |
1507        | 6            | A     |
1508        | 7            | A     |
1509        | 8            | A     |
1510        | 9            | A     |
1511        | 0            | B     |
1512        | 1            | B     |
1513        | 2            | B     |
1514        | 3            | B     |
1515        | 4            | B     |
1516        | 5            | B     |
1517        | 6            | B     |
1518        | 7            | B     |
1519        | 8            | B     |
1520        | 9            | B     |
1521        +--------------+-------+
1522        ");
1523    }
1524
1525    #[derive(Debug)]
1526    struct CongestionState {
1527        wakers: Vec<Waker>,
1528        unpolled_partitions: HashSet<usize>,
1529    }
1530
1531    #[derive(Debug)]
1532    struct Congestion {
1533        congestion_state: Mutex<CongestionState>,
1534    }
1535
1536    impl Congestion {
1537        fn new(partition_count: usize) -> Self {
1538            Congestion {
1539                congestion_state: Mutex::new(CongestionState {
1540                    wakers: vec![],
1541                    unpolled_partitions: (0usize..partition_count).collect(),
1542                }),
1543            }
1544        }
1545
1546        fn check_congested(&self, partition: usize, cx: &mut Context<'_>) -> Poll<()> {
1547            let mut state = self.congestion_state.lock().unwrap();
1548
1549            state.unpolled_partitions.remove(&partition);
1550
1551            if state.unpolled_partitions.is_empty() {
1552                state.wakers.iter().for_each(|w| w.wake_by_ref());
1553                state.wakers.clear();
1554                Poll::Ready(())
1555            } else {
1556                state.wakers.push(cx.waker().clone());
1557                Poll::Pending
1558            }
1559        }
1560    }
1561
1562    /// It returns pending for the 2nd partition until the 3rd partition is polled. The 1st
1563    /// partition is exhausted from the start, and if it is polled more than one, it panics.
1564    #[derive(Debug, Clone)]
1565    struct CongestedExec {
1566        schema: Schema,
1567        cache: Arc<PlanProperties>,
1568        congestion: Arc<Congestion>,
1569    }
1570
1571    impl CongestedExec {
1572        fn compute_properties(schema: SchemaRef) -> PlanProperties {
1573            let columns = schema
1574                .fields
1575                .iter()
1576                .enumerate()
1577                .map(|(i, f)| Arc::new(Column::new(f.name(), i)) as Arc<dyn PhysicalExpr>)
1578                .collect::<Vec<_>>();
1579            let mut eq_properties = EquivalenceProperties::new(schema);
1580            eq_properties.add_ordering(
1581                columns
1582                    .iter()
1583                    .map(|expr| PhysicalSortExpr::new_default(Arc::clone(expr))),
1584            );
1585            PlanProperties::new(
1586                eq_properties,
1587                Partitioning::Hash(columns, 3),
1588                EmissionType::Incremental,
1589                Boundedness::Unbounded {
1590                    requires_infinite_memory: false,
1591                },
1592            )
1593        }
1594    }
1595
1596    impl ExecutionPlan for CongestedExec {
1597        fn name(&self) -> &'static str {
1598            Self::static_name()
1599        }
1600        fn properties(&self) -> &Arc<PlanProperties> {
1601            &self.cache
1602        }
1603        fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
1604            vec![]
1605        }
1606        fn apply_expressions(
1607            &self,
1608            _f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
1609        ) -> Result<TreeNodeRecursion> {
1610            Ok(TreeNodeRecursion::Continue)
1611        }
1612
1613        fn replace_children(
1614            self: Arc<Self>,
1615            _: Vec<Arc<dyn ExecutionPlan>>,
1616            _: ReplaceChildrenOptions,
1617        ) -> Result<Arc<dyn ExecutionPlan>> {
1618            Ok(self)
1619        }
1620        fn with_new_children(
1621            self: Arc<Self>,
1622            children: Vec<Arc<dyn ExecutionPlan>>,
1623        ) -> Result<Arc<dyn ExecutionPlan>> {
1624            self.replace_children(
1625                children,
1626                ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
1627            )
1628        }
1629        fn execute(
1630            &self,
1631            partition: usize,
1632            _context: Arc<TaskContext>,
1633        ) -> Result<SendableRecordBatchStream> {
1634            Ok(Box::pin(CongestedStream {
1635                schema: Arc::new(self.schema.clone()),
1636                none_polled_once: false,
1637                congestion: Arc::clone(&self.congestion),
1638                partition,
1639            }))
1640        }
1641    }
1642
1643    impl DisplayAs for CongestedExec {
1644        fn fmt_as(&self, t: DisplayFormatType, f: &mut Formatter) -> std::fmt::Result {
1645            match t {
1646                DisplayFormatType::Default | DisplayFormatType::Verbose => {
1647                    write!(f, "CongestedExec",).unwrap()
1648                }
1649                DisplayFormatType::TreeRender => {
1650                    // TODO: collect info
1651                    write!(f, "").unwrap()
1652                }
1653            }
1654            Ok(())
1655        }
1656    }
1657
1658    /// It returns pending for the 2nd partition until the 3rd partition is polled. The 1st
1659    /// partition is exhausted from the start, and if it is polled more than once, it panics.
1660    #[derive(Debug)]
1661    pub struct CongestedStream {
1662        schema: SchemaRef,
1663        none_polled_once: bool,
1664        congestion: Arc<Congestion>,
1665        partition: usize,
1666    }
1667
1668    impl Stream for CongestedStream {
1669        type Item = Result<RecordBatch>;
1670        fn poll_next(
1671            mut self: Pin<&mut Self>,
1672            cx: &mut Context<'_>,
1673        ) -> Poll<Option<Self::Item>> {
1674            match self.partition {
1675                0 => {
1676                    let _ = self.congestion.check_congested(self.partition, cx);
1677                    if self.none_polled_once {
1678                        panic!("Exhausted stream is polled more than once")
1679                    } else {
1680                        self.none_polled_once = true;
1681                        Poll::Ready(None)
1682                    }
1683                }
1684                _ => {
1685                    ready!(self.congestion.check_congested(self.partition, cx));
1686                    Poll::Ready(None)
1687                }
1688            }
1689        }
1690    }
1691
1692    impl RecordBatchStream for CongestedStream {
1693        fn schema(&self) -> SchemaRef {
1694            Arc::clone(&self.schema)
1695        }
1696    }
1697
1698    #[tokio::test]
1699    async fn test_spm_congestion() -> Result<()> {
1700        let task_ctx = Arc::new(TaskContext::default());
1701        let schema = Schema::new(vec![Field::new("c1", DataType::UInt64, false)]);
1702        let properties = CongestedExec::compute_properties(Arc::new(schema.clone()));
1703        let partition_count = properties.output_partitioning().partition_count();
1704        let source = CongestedExec {
1705            schema: schema.clone(),
1706            cache: Arc::new(properties),
1707            congestion: Arc::new(Congestion::new(partition_count)),
1708        };
1709        let spm = SortPreservingMergeExec::new(
1710            [PhysicalSortExpr::new_default(Arc::new(Column::new(
1711                "c1", 0,
1712            )))]
1713            .into(),
1714            Arc::new(source),
1715        );
1716        let spm_task = SpawnedTask::spawn(collect(Arc::new(spm), task_ctx));
1717
1718        let result = timeout(Duration::from_secs(3), spm_task.join()).await;
1719        match result {
1720            Ok(Ok(Ok(_batches))) => Ok(()),
1721            Ok(Ok(Err(e))) => Err(e),
1722            Ok(Err(_)) => exec_err!("SortPreservingMerge task panicked or was cancelled"),
1723            Err(_) => exec_err!("SortPreservingMerge caused a deadlock"),
1724        }
1725    }
1726
1727    #[tokio::test]
1728    async fn test_sort_merge_stops_after_error_with_buffered_rows() -> Result<()> {
1729        let task_ctx = Arc::new(TaskContext::default());
1730        let schema = Arc::new(Schema::new(vec![Field::new("i", DataType::Int32, false)]));
1731        let sort: LexOrdering = [PhysicalSortExpr::new_default(Arc::new(Column::new(
1732            "i", 0,
1733        ))
1734            as Arc<dyn PhysicalExpr>)]
1735        .into();
1736
1737        let mut stream0 = RecordBatchReceiverStream::builder(Arc::clone(&schema), 2);
1738        let tx0 = stream0.tx();
1739        let schema0 = Arc::clone(&schema);
1740        stream0.spawn(async move {
1741            let batch =
1742                RecordBatch::try_new(schema0, vec![Arc::new(Int32Array::from(vec![1]))])?;
1743            tx0.send(Ok(batch)).await.unwrap();
1744            tx0.send(exec_err!("stream failure")).await.unwrap();
1745            Ok(())
1746        });
1747
1748        let mut stream1 = RecordBatchReceiverStream::builder(Arc::clone(&schema), 1);
1749        let tx1 = stream1.tx();
1750        let schema1 = Arc::clone(&schema);
1751        stream1.spawn(async move {
1752            let batch =
1753                RecordBatch::try_new(schema1, vec![Arc::new(Int32Array::from(vec![2]))])?;
1754            tx1.send(Ok(batch)).await.unwrap();
1755            Ok(())
1756        });
1757
1758        let metrics = ExecutionPlanMetricsSet::new();
1759        let reservation =
1760            MemoryConsumer::new("test").register(&task_ctx.runtime_env().memory_pool);
1761
1762        let mut merge_stream = StreamingMergeBuilder::new()
1763            .with_streams(vec![stream0.build(), stream1.build()])
1764            .with_schema(Arc::clone(&schema))
1765            .with_expressions(&sort)
1766            .with_metrics(BaselineMetrics::new(&metrics, 0))
1767            .with_batch_size(task_ctx.session_config().batch_size())
1768            .with_fetch(None)
1769            .with_reservation(reservation)
1770            .build()?;
1771
1772        let first = merge_stream.next().await.unwrap();
1773        assert!(first.is_err(), "expected merge stream to surface the error");
1774        assert!(
1775            merge_stream.next().await.is_none(),
1776            "merge stream yielded data after returning an error"
1777        );
1778
1779        Ok(())
1780    }
1781}