Skip to main content

datafusion_datasource/
memory.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
18use std::cmp::Ordering;
19use std::collections::BinaryHeap;
20use std::fmt;
21use std::fmt::Debug;
22use std::ops::Deref;
23use std::slice::from_ref;
24use std::sync::Arc;
25
26use crate::sink::DataSink;
27use crate::source::{DataSource, DataSourceExec};
28
29use arrow::array::{RecordBatch, RecordBatchOptions};
30use arrow::datatypes::{Schema, SchemaRef};
31use datafusion_common::tree_node::TreeNodeRecursion;
32use datafusion_common::{
33    Result, ScalarValue, assert_or_internal_err, plan_err, project_schema,
34};
35use datafusion_execution::TaskContext;
36use datafusion_physical_expr::equivalence::project_orderings;
37use datafusion_physical_expr::projection::ProjectionExprs;
38use datafusion_physical_expr::utils::collect_columns;
39use datafusion_physical_expr::{EquivalenceProperties, LexOrdering};
40use datafusion_physical_plan::memory::MemoryStream;
41use datafusion_physical_plan::projection::{
42    all_alias_free_columns, new_projections_for_columns,
43};
44use datafusion_physical_plan::{
45    ColumnarValue, DisplayAs, DisplayFormatType, Partitioning, PhysicalExpr,
46    SendableRecordBatchStream, Statistics, common,
47};
48
49use async_trait::async_trait;
50use datafusion_physical_plan::coop::cooperative;
51use datafusion_physical_plan::execution_plan::SchedulingType;
52use futures::StreamExt;
53use itertools::Itertools;
54use tokio::sync::RwLock;
55
56/// Data source configuration for reading in-memory batches of data
57#[derive(Clone, Debug)]
58pub struct MemorySourceConfig {
59    /// The partitions to query.
60    ///
61    /// Each partition is a `Vec<RecordBatch>`.
62    partitions: Vec<Vec<RecordBatch>>,
63    /// Schema representing the data before projection
64    schema: SchemaRef,
65    /// Schema representing the data after the optional projection is applied
66    projected_schema: SchemaRef,
67    /// Optional projection
68    projection: Option<Vec<usize>>,
69    /// Sort information: one or more equivalent orderings
70    sort_information: Vec<LexOrdering>,
71    /// if partition sizes should be displayed
72    show_sizes: bool,
73    /// The maximum number of records to read from this plan. If `None`,
74    /// all records after filtering are returned.
75    fetch: Option<usize>,
76}
77
78impl DataSource for MemorySourceConfig {
79    fn open(
80        &self,
81        partition: usize,
82        _context: Arc<TaskContext>,
83    ) -> Result<SendableRecordBatchStream> {
84        Ok(Box::pin(cooperative(
85            MemoryStream::try_new(
86                self.partitions[partition].clone(),
87                Arc::clone(&self.projected_schema),
88                self.projection.clone(),
89            )?
90            .with_fetch(self.fetch),
91        )))
92    }
93
94    fn fmt_as(&self, t: DisplayFormatType, f: &mut fmt::Formatter) -> fmt::Result {
95        match t {
96            DisplayFormatType::Default | DisplayFormatType::Verbose => {
97                let partition_sizes: Vec<_> =
98                    self.partitions.iter().map(|b| b.len()).collect();
99
100                let output_ordering = self
101                    .sort_information
102                    .first()
103                    .map(|output_ordering| format!(", output_ordering={output_ordering}"))
104                    .unwrap_or_default();
105
106                let eq_properties = self.eq_properties();
107                let constraints = eq_properties.constraints();
108                let constraints = if constraints.is_empty() {
109                    String::new()
110                } else {
111                    format!(", {constraints}")
112                };
113
114                let limit = self
115                    .fetch
116                    .map_or(String::new(), |limit| format!(", fetch={limit}"));
117                if self.show_sizes {
118                    write!(
119                        f,
120                        "partitions={}, partition_sizes={partition_sizes:?}{limit}{output_ordering}{constraints}",
121                        partition_sizes.len(),
122                    )
123                } else {
124                    write!(
125                        f,
126                        "partitions={}{limit}{output_ordering}{constraints}",
127                        partition_sizes.len(),
128                    )
129                }
130            }
131            DisplayFormatType::TreeRender => {
132                let total_rows = self.partitions.iter().map(|b| b.len()).sum::<usize>();
133                let total_bytes: usize = self
134                    .partitions
135                    .iter()
136                    .flatten()
137                    .map(|batch| batch.get_array_memory_size())
138                    .sum();
139                writeln!(f, "format=memory")?;
140                writeln!(f, "rows={total_rows}")?;
141                writeln!(f, "bytes={total_bytes}")?;
142                Ok(())
143            }
144        }
145    }
146
147    /// If possible, redistribute batches across partitions according to their size.
148    ///
149    /// Returns `Ok(None)` if unable to repartition. Preserve output ordering if exists.
150    /// Refer to [`DataSource::repartitioned`] for further details.
151    fn repartitioned(
152        &self,
153        target_partitions: usize,
154        _repartition_file_min_size: usize,
155        output_ordering: Option<LexOrdering>,
156    ) -> Result<Option<Arc<dyn DataSource>>> {
157        if self.partitions.is_empty() || self.partitions.len() >= target_partitions
158        // if have no partitions, or already have more partitions than desired, do not repartition
159        {
160            return Ok(None);
161        }
162
163        let maybe_repartitioned = if let Some(output_ordering) = output_ordering {
164            self.repartition_preserving_order(target_partitions, output_ordering)?
165        } else {
166            self.repartition_evenly_by_size(target_partitions)?
167        };
168
169        if let Some(repartitioned) = maybe_repartitioned {
170            Ok(Some(Arc::new(Self::try_new(
171                &repartitioned,
172                self.original_schema(),
173                self.projection.clone(),
174            )?)))
175        } else {
176            Ok(None)
177        }
178    }
179
180    fn output_partitioning(&self) -> Partitioning {
181        Partitioning::UnknownPartitioning(self.partitions.len())
182    }
183
184    fn eq_properties(&self) -> EquivalenceProperties {
185        EquivalenceProperties::new_with_orderings(
186            Arc::clone(&self.projected_schema),
187            self.sort_information.clone(),
188        )
189    }
190
191    fn scheduling_type(&self) -> SchedulingType {
192        SchedulingType::Cooperative
193    }
194
195    fn partition_statistics(&self, partition: Option<usize>) -> Result<Arc<Statistics>> {
196        if let Some(partition) = partition {
197            // Compute statistics for a specific partition
198            if let Some(batches) = self.partitions.get(partition) {
199                Ok(Arc::new(common::compute_record_batch_statistics(
200                    from_ref(batches),
201                    &self.schema,
202                    self.projection.clone(),
203                )))
204            } else {
205                // Invalid partition index
206                Ok(Arc::new(Statistics::new_unknown(&self.projected_schema)))
207            }
208        } else {
209            // Compute statistics across all partitions
210            Ok(Arc::new(common::compute_record_batch_statistics(
211                &self.partitions,
212                &self.schema,
213                self.projection.clone(),
214            )))
215        }
216    }
217
218    fn with_fetch(&self, limit: Option<usize>) -> Option<Arc<dyn DataSource>> {
219        let source = self.clone();
220        Some(Arc::new(source.with_limit(limit)))
221    }
222
223    fn fetch(&self) -> Option<usize> {
224        self.fetch
225    }
226
227    fn try_swapping_with_projection(
228        &self,
229        projection: &ProjectionExprs,
230    ) -> Result<Option<Arc<dyn DataSource>>> {
231        // If there is any non-column or alias-carrier expression, Projection should not be removed.
232        // This process can be moved into MemoryExec, but it would be an overlap of their responsibility.
233        let exprs = projection.iter().cloned().collect_vec();
234        all_alias_free_columns(exprs.as_slice())
235            .then(|| {
236                let all_projections = (0..self.schema.fields().len()).collect();
237                let new_projections = new_projections_for_columns(
238                    &exprs,
239                    self.projection().as_ref().unwrap_or(&all_projections),
240                );
241                let projected_schema =
242                    project_schema(&self.schema, Some(&new_projections));
243
244                projected_schema.map(|projected_schema| {
245                    // Clone self to preserve all metadata (fetch, sort_information,
246                    // show_sizes, etc.) then update only the projection-related fields.
247                    let mut new_source = self.clone();
248                    new_source.projection = Some(new_projections);
249                    new_source.projected_schema = projected_schema;
250                    // Project sort information to match the new projection
251                    new_source.sort_information = project_orderings(
252                        &new_source.sort_information,
253                        &new_source.projected_schema,
254                    );
255                    Arc::new(new_source) as Arc<dyn DataSource>
256                })
257            })
258            .transpose()
259    }
260
261    fn apply_expressions(
262        &self,
263        _f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
264    ) -> Result<TreeNodeRecursion> {
265        Ok(TreeNodeRecursion::Continue)
266    }
267
268    /// Serialize this `MemorySourceConfig` as a `MemoryScanExecNode` wrapped
269    /// in a [`PhysicalPlanNode`]. Byte-compatible with the former central
270    /// `MemoryScan` arm in `datafusion-proto`.
271    ///
272    /// [`PhysicalPlanNode`]: datafusion_proto_models::protobuf::PhysicalPlanNode
273    #[cfg(feature = "proto")]
274    fn try_to_proto(
275        &self,
276        ctx: &datafusion_physical_plan::proto::ExecutionPlanEncodeCtx<'_>,
277    ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalPlanNode>> {
278        use datafusion_physical_expr_common::sort_expr::sort_exprs_try_to_proto;
279        use datafusion_proto_models::protobuf;
280
281        let partitions = self
282            .partitions
283            .iter()
284            .map(|batches| record_batches_to_ipc_bytes(batches))
285            .collect::<Result<Vec<_>>>()?;
286
287        // Proto3 can't tell `None` from `Some(vec![])`; encode the latter
288        // as the `[u32::MAX]` sentinel, matching the join/filter nodes.
289        let projection = match self.projection.as_ref() {
290            None => Vec::new(),
291            Some(v) if v.is_empty() => vec![u32::MAX],
292            Some(v) => v.iter().map(|x| *x as u32).collect(),
293        };
294
295        let mut sort_information = Vec::with_capacity(self.sort_information.len());
296        for ordering in &self.sort_information {
297            let physical_sort_expr_nodes =
298                sort_exprs_try_to_proto(ordering.iter(), &ctx.expr_ctx())?;
299            sort_information.push(protobuf::PhysicalSortExprNodeCollection {
300                physical_sort_expr_nodes,
301            });
302        }
303
304        Ok(Some(protobuf::PhysicalPlanNode {
305            physical_plan_type: Some(
306                protobuf::physical_plan_node::PhysicalPlanType::MemoryScan(
307                    protobuf::MemoryScanExecNode {
308                        partitions,
309                        schema: Some(self.schema.as_ref().try_into()?),
310                        projection,
311                        sort_information,
312                        show_sizes: self.show_sizes,
313                        fetch: self.fetch.map(|f| f as u32),
314                    },
315                ),
316            ),
317        }))
318    }
319}
320
321impl MemorySourceConfig {
322    /// Create a new `MemorySourceConfig` for reading in-memory record batches
323    /// The provided `schema` should not have the projection applied.
324    pub fn try_new(
325        partitions: &[Vec<RecordBatch>],
326        schema: SchemaRef,
327        projection: Option<Vec<usize>>,
328    ) -> Result<Self> {
329        let projected_schema = project_schema(&schema, projection.as_ref())?;
330        Ok(Self {
331            partitions: partitions.to_vec(),
332            schema,
333            projected_schema,
334            projection,
335            sort_information: vec![],
336            show_sizes: true,
337            fetch: None,
338        })
339    }
340
341    /// Create a new `DataSourceExec` plan for reading in-memory record batches
342    /// The provided `schema` should not have the projection applied.
343    pub fn try_new_exec(
344        partitions: &[Vec<RecordBatch>],
345        schema: SchemaRef,
346        projection: Option<Vec<usize>>,
347    ) -> Result<Arc<DataSourceExec>> {
348        let source = Self::try_new(partitions, schema, projection)?;
349        Ok(DataSourceExec::from_data_source(source))
350    }
351
352    /// Create a new execution plan from a list of constant values (`ValuesExec`)
353    #[expect(clippy::needless_pass_by_value)]
354    pub fn try_new_as_values(
355        schema: SchemaRef,
356        data: Vec<Vec<Arc<dyn PhysicalExpr>>>,
357    ) -> Result<Arc<DataSourceExec>> {
358        if data.is_empty() {
359            return plan_err!("Values list cannot be empty");
360        }
361
362        let n_row = data.len();
363        let n_col = schema.fields().len();
364
365        // We have this single row batch as a placeholder to satisfy evaluation argument
366        // and generate a single output row
367        let placeholder_schema = Arc::new(Schema::empty());
368        let placeholder_batch = RecordBatch::try_new_with_options(
369            Arc::clone(&placeholder_schema),
370            vec![],
371            &RecordBatchOptions::new().with_row_count(Some(1)),
372        )?;
373
374        // Evaluate each column
375        let arrays = (0..n_col)
376            .map(|j| {
377                (0..n_row)
378                    .map(|i| {
379                        let expr = &data[i][j];
380                        let result = expr.evaluate(&placeholder_batch)?;
381
382                        match result {
383                            ColumnarValue::Scalar(scalar) => Ok(scalar),
384                            ColumnarValue::Array(array) if array.len() == 1 => {
385                                ScalarValue::try_from_array(&array, 0)
386                            }
387                            ColumnarValue::Array(_) => {
388                                plan_err!("Cannot have array values in a values list")
389                            }
390                        }
391                    })
392                    .collect::<Result<Vec<_>>>()
393                    .and_then(ScalarValue::iter_to_array)
394            })
395            .collect::<Result<Vec<_>>>()?;
396
397        let batch = RecordBatch::try_new_with_options(
398            Arc::clone(&schema),
399            arrays,
400            &RecordBatchOptions::new().with_row_count(Some(n_row)),
401        )?;
402
403        let partitions = vec![batch];
404        Self::try_new_from_batches(Arc::clone(&schema), partitions)
405    }
406
407    /// Create a new plan using the provided schema and batches.
408    ///
409    /// Errors if any of the batches don't match the provided schema, or if no
410    /// batches are provided.
411    #[expect(clippy::needless_pass_by_value)]
412    pub fn try_new_from_batches(
413        schema: SchemaRef,
414        batches: Vec<RecordBatch>,
415    ) -> Result<Arc<DataSourceExec>> {
416        if batches.is_empty() {
417            return plan_err!("Values list cannot be empty");
418        }
419
420        for batch in &batches {
421            let batch_schema = batch.schema();
422            if batch_schema != schema {
423                return plan_err!(
424                    "Batch has invalid schema. Expected: {}, got: {}",
425                    schema,
426                    batch_schema
427                );
428            }
429        }
430
431        let partitions = vec![batches];
432        let source = Self {
433            partitions,
434            schema: Arc::clone(&schema),
435            projected_schema: Arc::clone(&schema),
436            projection: None,
437            sort_information: vec![],
438            show_sizes: true,
439            fetch: None,
440        };
441        Ok(DataSourceExec::from_data_source(source))
442    }
443
444    /// Set the limit of the files
445    pub fn with_limit(mut self, limit: Option<usize>) -> Self {
446        self.fetch = limit;
447        self
448    }
449
450    /// Set `show_sizes` to determine whether to display partition sizes
451    pub fn with_show_sizes(mut self, show_sizes: bool) -> Self {
452        self.show_sizes = show_sizes;
453        self
454    }
455
456    /// Ref to partitions
457    pub fn partitions(&self) -> &[Vec<RecordBatch>] {
458        &self.partitions
459    }
460
461    /// Ref to projection
462    pub fn projection(&self) -> &Option<Vec<usize>> {
463        &self.projection
464    }
465
466    /// Show sizes
467    pub fn show_sizes(&self) -> bool {
468        self.show_sizes
469    }
470
471    /// Ref to sort information
472    pub fn sort_information(&self) -> &[LexOrdering] {
473        &self.sort_information
474    }
475
476    /// A memory table can be ordered by multiple expressions simultaneously.
477    /// [`EquivalenceProperties`] keeps track of expressions that describe the
478    /// global ordering of the schema. These columns are not necessarily same; e.g.
479    /// ```text
480    /// ┌-------┐
481    /// | a | b |
482    /// |---|---|
483    /// | 1 | 9 |
484    /// | 2 | 8 |
485    /// | 3 | 7 |
486    /// | 5 | 5 |
487    /// └---┴---┘
488    /// ```
489    /// where both `a ASC` and `b DESC` can describe the table ordering. With
490    /// [`EquivalenceProperties`], we can keep track of these equivalences
491    /// and treat `a ASC` and `b DESC` as the same ordering requirement.
492    ///
493    /// Note that if there is an internal projection, that projection will be
494    /// also applied to the given `sort_information`.
495    pub fn try_with_sort_information(
496        mut self,
497        mut sort_information: Vec<LexOrdering>,
498    ) -> Result<Self> {
499        // All sort expressions must refer to the original schema
500        let fields = self.schema.fields();
501        let ambiguous_column = sort_information
502            .iter()
503            .flat_map(|ordering| ordering.clone())
504            .flat_map(|expr| collect_columns(&expr.expr))
505            .find(|col| {
506                fields
507                    .get(col.index())
508                    .map(|field| field.name() != col.name())
509                    .unwrap_or(true)
510            });
511        assert_or_internal_err!(
512            ambiguous_column.is_none(),
513            "Column {:?} is not found in the original schema of the MemorySourceConfig",
514            ambiguous_column.as_ref().unwrap()
515        );
516
517        // If there is a projection on the source, we also need to project orderings
518        if self.projection.is_some() {
519            sort_information =
520                project_orderings(&sort_information, &self.projected_schema);
521        }
522
523        self.sort_information = sort_information;
524        Ok(self)
525    }
526
527    /// Arc clone of ref to original schema
528    pub fn original_schema(&self) -> SchemaRef {
529        Arc::clone(&self.schema)
530    }
531
532    /// Repartition while preserving order.
533    ///
534    /// Returns `Ok(None)` if cannot fulfill the requested repartitioning, such
535    /// as having too few batches to fulfill the `target_partitions` or if unable
536    /// to preserve output ordering.
537    fn repartition_preserving_order(
538        &self,
539        target_partitions: usize,
540        output_ordering: LexOrdering,
541    ) -> Result<Option<Vec<Vec<RecordBatch>>>> {
542        if !self.eq_properties().ordering_satisfy(output_ordering)? {
543            Ok(None)
544        } else {
545            let total_num_batches =
546                self.partitions.iter().map(|b| b.len()).sum::<usize>();
547            if total_num_batches < target_partitions {
548                // no way to create the desired repartitioning
549                return Ok(None);
550            }
551
552            let cnt_to_repartition = target_partitions - self.partitions.len();
553
554            // Label the current partitions and their order.
555            // Such that when we later split up the partitions into smaller sizes, we are maintaining the order.
556            let to_repartition = self
557                .partitions
558                .iter()
559                .enumerate()
560                .map(|(idx, batches)| RePartition {
561                    idx: idx + (cnt_to_repartition * idx), // make space in ordering for split partitions
562                    row_count: batches.iter().map(|batch| batch.num_rows()).sum(),
563                    batches: batches.clone(),
564                })
565                .collect_vec();
566
567            // Put all of the partitions into a heap ordered by `RePartition::partial_cmp`, which sizes
568            // by count of rows.
569            let mut max_heap = BinaryHeap::with_capacity(target_partitions);
570            for rep in to_repartition {
571                max_heap.push(CompareByRowCount(rep));
572            }
573
574            // Split the largest partitions into smaller partitions. Maintaining the output
575            // order of the partitions & newly created partitions.
576            let mut cannot_split_further = Vec::with_capacity(target_partitions);
577            for _ in 0..cnt_to_repartition {
578                // triggers loop for the cnt_to_repartition. So if need another 4 partitions, it attempts to split 4 times.
579                loop {
580                    // Take the largest item off the heap, and attempt to split.
581                    let Some(to_split) = max_heap.pop() else {
582                        // Nothing left to attempt repartition. Break inner loop.
583                        break;
584                    };
585
586                    // Split the partition. The new partitions will be ordered with idx and idx+1.
587                    let mut new_partitions = to_split.into_inner().split();
588                    if new_partitions.len() > 1 {
589                        for new_partition in new_partitions {
590                            max_heap.push(CompareByRowCount(new_partition));
591                        }
592                        // Successful repartition. Break inner loop, and return to outer `cnt_to_repartition` loop.
593                        break;
594                    } else {
595                        cannot_split_further.push(new_partitions.remove(0));
596                    }
597                }
598            }
599            let mut partitions = max_heap
600                .drain()
601                .map(CompareByRowCount::into_inner)
602                .collect_vec();
603            partitions.extend(cannot_split_further);
604
605            // Finally, sort all partitions by the output ordering.
606            // This was the original ordering of the batches within the partition. We are maintaining this ordering.
607            partitions.sort_by_key(|p| p.idx);
608            let partitions = partitions.into_iter().map(|rep| rep.batches).collect_vec();
609
610            Ok(Some(partitions))
611        }
612    }
613
614    /// Repartition into evenly sized chunks (as much as possible without batch splitting),
615    /// disregarding any ordering.
616    ///
617    /// Current implementation uses a first-fit-decreasing bin packing, modified to enable
618    /// us to still return the desired count of `target_partitions`.
619    ///
620    /// Returns `Ok(None)` if cannot fulfill the requested repartitioning, such
621    /// as having too few batches to fulfill the `target_partitions`.
622    fn repartition_evenly_by_size(
623        &self,
624        target_partitions: usize,
625    ) -> Result<Option<Vec<Vec<RecordBatch>>>> {
626        // determine if we have enough total batches to fulfill request
627        let mut flatten_batches =
628            self.partitions.clone().into_iter().flatten().collect_vec();
629        if flatten_batches.len() < target_partitions {
630            return Ok(None);
631        }
632
633        // Take all flattened batches (all in 1 partititon/vec) and divide evenly into the desired number of `target_partitions`.
634        let total_num_rows = flatten_batches.iter().map(|b| b.num_rows()).sum::<usize>();
635        // sort by size, so we pack multiple smaller batches into the same partition
636        flatten_batches.sort_by_key(|b| std::cmp::Reverse(b.num_rows()));
637
638        // Divide.
639        let mut partitions =
640            vec![Vec::with_capacity(flatten_batches.len()); target_partitions];
641        let mut target_partition_size = total_num_rows.div_ceil(target_partitions);
642        let mut total_rows_seen = 0;
643        let mut curr_bin_row_count = 0;
644        let mut idx = 0;
645        for batch in flatten_batches {
646            let row_cnt = batch.num_rows();
647            idx = std::cmp::min(idx, target_partitions - 1);
648
649            partitions[idx].push(batch);
650            curr_bin_row_count += row_cnt;
651            total_rows_seen += row_cnt;
652
653            if curr_bin_row_count >= target_partition_size {
654                idx += 1;
655                curr_bin_row_count = 0;
656
657                // update target_partition_size, to handle very lopsided batch distributions
658                // while still returning the count of `target_partitions`
659                if total_rows_seen < total_num_rows {
660                    target_partition_size = (total_num_rows - total_rows_seen)
661                        .div_ceil(target_partitions - idx);
662                }
663            }
664        }
665
666        Ok(Some(partitions))
667    }
668}
669
670#[cfg(feature = "proto")]
671impl MemorySourceConfig {
672    /// Reconstruct a [`DataSourceExec`] wrapping a `MemorySourceConfig` from
673    /// its protobuf representation. Byte-compatible with the former central
674    /// `MemoryScan` arm in `datafusion-proto`.
675    pub fn try_from_proto(
676        node: &datafusion_proto_models::protobuf::PhysicalPlanNode,
677        ctx: &datafusion_physical_plan::proto::ExecutionPlanDecodeCtx<'_>,
678    ) -> Result<Arc<dyn datafusion_physical_plan::ExecutionPlan>> {
679        use datafusion_common::internal_datafusion_err;
680        use datafusion_physical_expr_common::sort_expr::sort_exprs_try_from_proto;
681        use datafusion_proto_models::protobuf;
682
683        let scan = datafusion_physical_plan::expect_plan_variant!(
684            node,
685            protobuf::physical_plan_node::PhysicalPlanType::MemoryScan,
686            "MemorySourceConfig",
687        );
688
689        let partitions = scan
690            .partitions
691            .iter()
692            .map(|buf| record_batches_from_ipc_bytes(buf))
693            .collect::<Result<Vec<_>>>()?;
694
695        let proto_schema = scan.schema.as_ref().ok_or_else(|| {
696            internal_datafusion_err!("schema in MemoryScanExecNode is missing.")
697        })?;
698        let schema: SchemaRef = SchemaRef::new(proto_schema.try_into()?);
699
700        // Preserve the empty-projection sentinel written by `try_to_proto`.
701        let projection = match scan.projection.as_slice() {
702            [] => None,
703            [u32::MAX] => Some(Vec::new()),
704            indices => Some(indices.iter().map(|i| *i as usize).collect()),
705        };
706
707        let mut sort_information = vec![];
708        for ordering in &scan.sort_information {
709            let sort_exprs = sort_exprs_try_from_proto(
710                &ordering.physical_sort_expr_nodes,
711                &ctx.expr_ctx(&schema),
712            )?;
713            sort_information.extend(LexOrdering::new(sort_exprs));
714        }
715
716        let source = Self::try_new(&partitions, schema, projection)?
717            .with_limit(scan.fetch.map(|f| f as usize))
718            .with_show_sizes(scan.show_sizes)
719            .try_with_sort_information(sort_information)?;
720
721        Ok(DataSourceExec::from_data_source(source))
722    }
723}
724
725/// Encode record batches as Arrow IPC stream bytes; an empty slice encodes to
726/// an empty buffer.
727#[cfg(feature = "proto")]
728fn record_batches_to_ipc_bytes(batches: &[RecordBatch]) -> Result<Vec<u8>> {
729    use arrow::ipc::writer::StreamWriter;
730
731    if batches.is_empty() {
732        return Ok(vec![]);
733    }
734    let schema = batches[0].schema();
735    let mut buf = Vec::new();
736    let mut writer = StreamWriter::try_new(&mut buf, &schema)?;
737    for batch in batches {
738        writer.write(batch)?;
739    }
740    writer.finish()?;
741    Ok(buf)
742}
743
744/// Inverse of [`record_batches_to_ipc_bytes`].
745#[cfg(feature = "proto")]
746fn record_batches_from_ipc_bytes(buf: &[u8]) -> Result<Vec<RecordBatch>> {
747    use arrow::ipc::reader::StreamReader;
748
749    if buf.is_empty() {
750        return Ok(vec![]);
751    }
752    let reader = StreamReader::try_new(buf, None)?;
753    let mut batches = Vec::new();
754    for batch in reader {
755        batches.push(batch?);
756    }
757    Ok(batches)
758}
759
760/// For use in repartitioning, track the total size and original partition index.
761///
762/// Do not implement clone, in order to avoid unnecessary copying during repartitioning.
763struct RePartition {
764    /// Original output ordering for the partition.
765    idx: usize,
766    /// Total size of the partition, for use in heap ordering
767    /// (a.k.a. splitting up the largest partitions).
768    row_count: usize,
769    /// A partition containing record batches.
770    batches: Vec<RecordBatch>,
771}
772
773impl RePartition {
774    /// Split [`RePartition`] into 2 pieces, consuming self.
775    ///
776    /// Returns only 1 partition if cannot be split further.
777    fn split(self) -> Vec<Self> {
778        if self.batches.len() == 1 {
779            return vec![self];
780        }
781
782        let new_0 = RePartition {
783            idx: self.idx, // output ordering
784            row_count: 0,
785            batches: vec![],
786        };
787        let new_1 = RePartition {
788            idx: self.idx + 1, // output ordering +1
789            row_count: 0,
790            batches: vec![],
791        };
792        let split_pt = self.row_count / 2;
793
794        let [new_0, new_1] = self.batches.into_iter().fold(
795            [new_0, new_1],
796            |[mut new0, mut new1], batch| {
797                if new0.row_count < split_pt {
798                    new0.add_batch(batch);
799                } else {
800                    new1.add_batch(batch);
801                }
802                [new0, new1]
803            },
804        );
805        vec![new_0, new_1]
806    }
807
808    fn add_batch(&mut self, batch: RecordBatch) {
809        self.row_count += batch.num_rows();
810        self.batches.push(batch);
811    }
812}
813
814impl fmt::Display for RePartition {
815    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
816        write!(
817            f,
818            "{}rows-in-{}batches@{}",
819            self.row_count,
820            self.batches.len(),
821            self.idx
822        )
823    }
824}
825
826struct CompareByRowCount(RePartition);
827impl CompareByRowCount {
828    fn into_inner(self) -> RePartition {
829        self.0
830    }
831}
832impl Ord for CompareByRowCount {
833    fn cmp(&self, other: &Self) -> Ordering {
834        self.0.row_count.cmp(&other.0.row_count)
835    }
836}
837impl PartialOrd for CompareByRowCount {
838    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
839        Some(self.cmp(other))
840    }
841}
842impl PartialEq for CompareByRowCount {
843    fn eq(&self, other: &Self) -> bool {
844        // PartialEq must be consistent with PartialOrd
845        self.cmp(other) == Ordering::Equal
846    }
847}
848impl Eq for CompareByRowCount {}
849impl Deref for CompareByRowCount {
850    type Target = RePartition;
851    fn deref(&self) -> &Self::Target {
852        &self.0
853    }
854}
855
856/// Type alias for partition data
857pub type PartitionData = Arc<RwLock<Vec<RecordBatch>>>;
858
859/// Implements for writing to a [`MemTable`]
860///
861/// [`MemTable`]: <https://docs.rs/datafusion/latest/datafusion/datasource/memory/struct.MemTable.html>
862pub struct MemSink {
863    /// Target locations for writing data
864    batches: Vec<PartitionData>,
865    schema: SchemaRef,
866}
867
868impl Debug for MemSink {
869    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
870        f.debug_struct("MemSink")
871            .field("num_partitions", &self.batches.len())
872            .finish()
873    }
874}
875
876impl DisplayAs for MemSink {
877    fn fmt_as(&self, t: DisplayFormatType, f: &mut fmt::Formatter<'_>) -> fmt::Result {
878        match t {
879            DisplayFormatType::Default | DisplayFormatType::Verbose => {
880                let partition_count = self.batches.len();
881                write!(f, "MemoryTable (partitions={partition_count})")
882            }
883            DisplayFormatType::TreeRender => {
884                // TODO: collect info
885                write!(f, "")
886            }
887        }
888    }
889}
890
891impl MemSink {
892    /// Creates a new [`MemSink`].
893    ///
894    /// The caller is responsible for ensuring that there is at least one partition to insert into.
895    pub fn try_new(batches: Vec<PartitionData>, schema: SchemaRef) -> Result<Self> {
896        if batches.is_empty() {
897            return plan_err!("Cannot insert into MemTable with zero partitions");
898        }
899        Ok(Self { batches, schema })
900    }
901}
902
903#[async_trait]
904impl DataSink for MemSink {
905    fn schema(&self) -> &SchemaRef {
906        &self.schema
907    }
908
909    async fn write_all(
910        &self,
911        mut data: SendableRecordBatchStream,
912        _context: &Arc<TaskContext>,
913    ) -> Result<u64> {
914        let num_partitions = self.batches.len();
915
916        // buffer up the data round robin style into num_partitions
917
918        let mut new_batches = vec![vec![]; num_partitions];
919        let mut i = 0;
920        let mut row_count = 0;
921        while let Some(batch) = data.next().await.transpose()? {
922            row_count += batch.num_rows();
923            new_batches[i].push(batch);
924            i = (i + 1) % num_partitions;
925        }
926
927        // write the outputs into the batches
928        for (target, mut batches) in self.batches.iter().zip(new_batches) {
929            // Append all the new batches in one go to minimize locking overhead
930            target.write().await.append(&mut batches);
931        }
932
933        Ok(row_count as u64)
934    }
935}
936
937#[cfg(test)]
938mod memory_source_tests {
939    use std::sync::Arc;
940
941    use crate::memory::MemorySourceConfig;
942    use crate::source::DataSourceExec;
943
944    use arrow::compute::SortOptions;
945    use arrow::datatypes::{DataType, Field, Schema};
946    use datafusion_common::Result;
947    use datafusion_physical_expr::expressions::col;
948    use datafusion_physical_expr_common::sort_expr::{LexOrdering, PhysicalSortExpr};
949    use datafusion_physical_plan::ExecutionPlan;
950
951    #[test]
952    fn test_memory_order_eq() -> Result<()> {
953        let schema = Arc::new(Schema::new(vec![
954            Field::new("a", DataType::Int64, false),
955            Field::new("b", DataType::Int64, false),
956            Field::new("c", DataType::Int64, false),
957        ]));
958        let sort1: LexOrdering = [
959            PhysicalSortExpr {
960                expr: col("a", &schema)?,
961                options: SortOptions::default(),
962            },
963            PhysicalSortExpr {
964                expr: col("b", &schema)?,
965                options: SortOptions::default(),
966            },
967        ]
968        .into();
969        let sort2: LexOrdering = [PhysicalSortExpr {
970            expr: col("c", &schema)?,
971            options: SortOptions::default(),
972        }]
973        .into();
974        let mut expected_output_order = sort1.clone();
975        expected_output_order.extend(sort2.clone());
976
977        let sort_information = vec![sort1.clone(), sort2.clone()];
978        let mem_exec = DataSourceExec::from_data_source(
979            MemorySourceConfig::try_new(&[vec![]], schema, None)?
980                .try_with_sort_information(sort_information)?,
981        );
982
983        assert_eq!(
984            mem_exec.properties().output_ordering().unwrap(),
985            &expected_output_order
986        );
987        let eq_properties = mem_exec.properties().equivalence_properties();
988        assert!(eq_properties.oeq_class().contains(&sort1));
989        assert!(eq_properties.oeq_class().contains(&sort2));
990        Ok(())
991    }
992}
993
994#[cfg(test)]
995mod tests {
996    use super::*;
997    use crate::test_util::col;
998    use crate::tests::{aggr_test_schema, make_partition};
999
1000    use arrow::array::{ArrayRef, Int32Array, Int64Array, StringArray};
1001    use arrow::datatypes::{DataType, Field};
1002    use datafusion_common::assert_batches_eq;
1003    use datafusion_common::stats::{ColumnStatistics, Precision};
1004    use datafusion_physical_expr::PhysicalSortExpr;
1005    use datafusion_physical_plan::expressions::lit;
1006    use datafusion_physical_plan::statistics::{StatisticsArgs, StatisticsContext};
1007
1008    use datafusion_physical_plan::ExecutionPlan;
1009
1010    #[tokio::test]
1011    async fn exec_with_limit() -> Result<()> {
1012        let task_ctx = Arc::new(TaskContext::default());
1013        let batch = make_partition(7);
1014        let schema = batch.schema();
1015        let batches = vec![batch.clone(), batch];
1016
1017        let exec = MemorySourceConfig::try_new_from_batches(schema, batches).unwrap();
1018        assert_eq!(exec.fetch(), None);
1019
1020        let exec = exec.with_fetch(Some(4)).unwrap();
1021        assert_eq!(exec.fetch(), Some(4));
1022
1023        let mut it = exec.execute(0, task_ctx)?;
1024        let mut results = vec![];
1025        while let Some(batch) = it.next().await {
1026            results.push(batch?);
1027        }
1028
1029        let expected = [
1030            "+---+", "| i |", "+---+", "| 0 |", "| 1 |", "| 2 |", "| 3 |", "+---+",
1031        ];
1032        assert_batches_eq!(expected, &results);
1033        Ok(())
1034    }
1035
1036    /// Test that `try_swapping_with_projection` preserves the `fetch` limit.
1037    /// Regression test for <https://github.com/apache/datafusion/issues/21176>
1038    #[test]
1039    fn try_swapping_with_projection_preserves_fetch() {
1040        use datafusion_physical_expr::projection::ProjectionExprs;
1041
1042        let schema = Arc::new(Schema::new(vec![
1043            Field::new("a", DataType::Int32, false),
1044            Field::new("b", DataType::Utf8, false),
1045            Field::new("c", DataType::Int64, false),
1046        ]));
1047        let partitions: Vec<Vec<RecordBatch>> = vec![vec![batch(10)]];
1048        let source = MemorySourceConfig::try_new(&partitions, schema.clone(), None)
1049            .unwrap()
1050            .with_limit(Some(5));
1051
1052        assert_eq!(source.fetch, Some(5));
1053
1054        // Create a projection that reorders columns: [c, a] (indices 2, 0)
1055        let projection = ProjectionExprs::from_indices(&[2, 0], &schema);
1056        let swapped = source
1057            .try_swapping_with_projection(&projection)
1058            .unwrap()
1059            .unwrap();
1060        let new_source = swapped.downcast_ref::<MemorySourceConfig>().unwrap();
1061
1062        assert_eq!(
1063            new_source.fetch,
1064            Some(5),
1065            "fetch limit must be preserved after projection pushdown"
1066        );
1067    }
1068
1069    #[tokio::test]
1070    async fn values_empty_case() -> Result<()> {
1071        let schema = aggr_test_schema();
1072        let empty = MemorySourceConfig::try_new_as_values(schema, vec![]);
1073        assert!(empty.is_err());
1074        Ok(())
1075    }
1076
1077    #[test]
1078    fn new_exec_with_batches() {
1079        let batch = make_partition(7);
1080        let schema = batch.schema();
1081        let batches = vec![batch.clone(), batch];
1082        let _exec = MemorySourceConfig::try_new_from_batches(schema, batches).unwrap();
1083    }
1084
1085    #[test]
1086    fn new_exec_with_batches_empty() {
1087        let batch = make_partition(7);
1088        let schema = batch.schema();
1089        let _ = MemorySourceConfig::try_new_from_batches(schema, Vec::new()).unwrap_err();
1090    }
1091
1092    #[test]
1093    fn new_exec_with_batches_invalid_schema() {
1094        let batch = make_partition(7);
1095        let batches = vec![batch.clone(), batch];
1096
1097        let invalid_schema = Arc::new(Schema::new(vec![
1098            Field::new("col0", DataType::UInt32, false),
1099            Field::new("col1", DataType::Utf8, false),
1100        ]));
1101        let _ = MemorySourceConfig::try_new_from_batches(invalid_schema, batches)
1102            .unwrap_err();
1103    }
1104
1105    // Test issue: https://github.com/apache/datafusion/issues/8763
1106    #[test]
1107    fn new_exec_with_non_nullable_schema() {
1108        let schema = Arc::new(Schema::new(vec![Field::new(
1109            "col0",
1110            DataType::UInt32,
1111            false,
1112        )]));
1113        let _ = MemorySourceConfig::try_new_as_values(
1114            Arc::clone(&schema),
1115            vec![vec![lit(1u32)]],
1116        )
1117        .unwrap();
1118        // Test that a null value is rejected
1119        let _ = MemorySourceConfig::try_new_as_values(
1120            schema,
1121            vec![vec![lit(ScalarValue::UInt32(None))]],
1122        )
1123        .unwrap_err();
1124    }
1125
1126    #[test]
1127    fn values_stats_with_nulls_only() -> Result<()> {
1128        let data = vec![
1129            vec![lit(ScalarValue::Null)],
1130            vec![lit(ScalarValue::Null)],
1131            vec![lit(ScalarValue::Null)],
1132        ];
1133        let rows = data.len();
1134        let schema =
1135            Arc::new(Schema::new(vec![Field::new("col0", DataType::Null, true)]));
1136        let values = MemorySourceConfig::try_new_as_values(schema, data)?;
1137
1138        assert_eq!(
1139            *StatisticsContext::new().compute(values.as_ref(), &StatisticsArgs::new())?,
1140            Statistics {
1141                num_rows: Precision::Exact(rows),
1142                total_byte_size: Precision::Exact(8), // not important
1143                column_statistics: vec![ColumnStatistics {
1144                    null_count: Precision::Exact(rows), // there are only nulls
1145                    distinct_count: Precision::Absent,
1146                    max_value: Precision::Absent,
1147                    min_value: Precision::Absent,
1148                    sum_value: Precision::Absent,
1149                    byte_size: Precision::Absent,
1150                },],
1151            }
1152        );
1153
1154        Ok(())
1155    }
1156
1157    fn batch(row_size: usize) -> RecordBatch {
1158        let a: ArrayRef = Arc::new(Int32Array::from(vec![1; row_size]));
1159        let b: ArrayRef = Arc::new(StringArray::from_iter(vec![Some("foo"); row_size]));
1160        let c: ArrayRef = Arc::new(Int64Array::from_iter(vec![1; row_size]));
1161        RecordBatch::try_from_iter(vec![("a", a), ("b", b), ("c", c)]).unwrap()
1162    }
1163
1164    fn schema() -> SchemaRef {
1165        batch(1).schema()
1166    }
1167
1168    fn memorysrcconfig_no_partitions(
1169        sort_information: Vec<LexOrdering>,
1170    ) -> Result<MemorySourceConfig> {
1171        let partitions = vec![];
1172        MemorySourceConfig::try_new(&partitions, schema(), None)?
1173            .try_with_sort_information(sort_information)
1174    }
1175
1176    fn memorysrcconfig_1_partition_1_batch(
1177        sort_information: Vec<LexOrdering>,
1178    ) -> Result<MemorySourceConfig> {
1179        let partitions = vec![vec![batch(100)]];
1180        MemorySourceConfig::try_new(&partitions, schema(), None)?
1181            .try_with_sort_information(sort_information)
1182    }
1183
1184    fn memorysrcconfig_3_partitions_1_batch_each(
1185        sort_information: Vec<LexOrdering>,
1186    ) -> Result<MemorySourceConfig> {
1187        let partitions = vec![vec![batch(100)], vec![batch(100)], vec![batch(100)]];
1188        MemorySourceConfig::try_new(&partitions, schema(), None)?
1189            .try_with_sort_information(sort_information)
1190    }
1191
1192    fn memorysrcconfig_3_partitions_with_2_batches_each(
1193        sort_information: Vec<LexOrdering>,
1194    ) -> Result<MemorySourceConfig> {
1195        let partitions = vec![
1196            vec![batch(100), batch(100)],
1197            vec![batch(100), batch(100)],
1198            vec![batch(100), batch(100)],
1199        ];
1200        MemorySourceConfig::try_new(&partitions, schema(), None)?
1201            .try_with_sort_information(sort_information)
1202    }
1203
1204    /// Batches of different sizes, with batches ordered by size (100_000, 10_000, 100, 1)
1205    /// in the Memtable partition (a.k.a. vector of batches).
1206    fn memorysrcconfig_1_partition_with_different_sized_batches(
1207        sort_information: Vec<LexOrdering>,
1208    ) -> Result<MemorySourceConfig> {
1209        let partitions = vec![vec![batch(100_000), batch(10_000), batch(100), batch(1)]];
1210        MemorySourceConfig::try_new(&partitions, schema(), None)?
1211            .try_with_sort_information(sort_information)
1212    }
1213
1214    /// Same as [`memorysrcconfig_1_partition_with_different_sized_batches`],
1215    /// but the batches are ordered differently (not by size)
1216    /// in the Memtable partition (a.k.a. vector of batches).
1217    fn memorysrcconfig_1_partition_with_ordering_not_matching_size(
1218        sort_information: Vec<LexOrdering>,
1219    ) -> Result<MemorySourceConfig> {
1220        let partitions = vec![vec![batch(100_000), batch(1), batch(100), batch(10_000)]];
1221        MemorySourceConfig::try_new(&partitions, schema(), None)?
1222            .try_with_sort_information(sort_information)
1223    }
1224
1225    fn memorysrcconfig_2_partition_with_different_sized_batches(
1226        sort_information: Vec<LexOrdering>,
1227    ) -> Result<MemorySourceConfig> {
1228        let partitions = vec![
1229            vec![batch(100_000), batch(10_000), batch(1_000)],
1230            vec![batch(2_000), batch(20)],
1231        ];
1232        MemorySourceConfig::try_new(&partitions, schema(), None)?
1233            .try_with_sort_information(sort_information)
1234    }
1235
1236    fn memorysrcconfig_2_partition_with_extreme_sized_batches(
1237        sort_information: Vec<LexOrdering>,
1238    ) -> Result<MemorySourceConfig> {
1239        let partitions = vec![
1240            vec![
1241                batch(100_000),
1242                batch(1),
1243                batch(1),
1244                batch(1),
1245                batch(1),
1246                batch(0),
1247            ],
1248            vec![batch(1), batch(1), batch(1), batch(1), batch(0), batch(100)],
1249        ];
1250        MemorySourceConfig::try_new(&partitions, schema(), None)?
1251            .try_with_sort_information(sort_information)
1252    }
1253
1254    /// Assert that we get the expected count of partitions after repartitioning.
1255    ///
1256    /// If None, then we expected the [`DataSource::repartitioned`] to return None.
1257    fn assert_partitioning(
1258        partitioned_datasrc: Option<Arc<dyn DataSource>>,
1259        partition_cnt: Option<usize>,
1260    ) {
1261        let should_exist = if let Some(partition_cnt) = partition_cnt {
1262            format!("new datasource should exist and have {partition_cnt:?} partitions")
1263        } else {
1264            "new datasource should not exist".into()
1265        };
1266
1267        let actual = partitioned_datasrc
1268            .map(|datasrc| datasrc.output_partitioning().partition_count());
1269        assert_eq!(
1270            actual, partition_cnt,
1271            "partitioned datasrc does not match expected, we expected {should_exist}, instead found {actual:?}"
1272        );
1273    }
1274
1275    fn run_all_test_scenarios(
1276        output_ordering: Option<LexOrdering>,
1277        sort_information_on_config: Vec<LexOrdering>,
1278    ) -> Result<()> {
1279        let not_used = usize::MAX;
1280
1281        // src has no partitions
1282        let mem_src_config =
1283            memorysrcconfig_no_partitions(sort_information_on_config.clone())?;
1284        let partitioned_datasrc =
1285            mem_src_config.repartitioned(1, not_used, output_ordering.clone())?;
1286        assert_partitioning(partitioned_datasrc, None);
1287
1288        // src has partitions == target partitions (=1)
1289        let target_partitions = 1;
1290        let mem_src_config =
1291            memorysrcconfig_1_partition_1_batch(sort_information_on_config.clone())?;
1292        let partitioned_datasrc = mem_src_config.repartitioned(
1293            target_partitions,
1294            not_used,
1295            output_ordering.clone(),
1296        )?;
1297        assert_partitioning(partitioned_datasrc, None);
1298
1299        // src has partitions == target partitions (=3)
1300        let target_partitions = 3;
1301        let mem_src_config = memorysrcconfig_3_partitions_1_batch_each(
1302            sort_information_on_config.clone(),
1303        )?;
1304        let partitioned_datasrc = mem_src_config.repartitioned(
1305            target_partitions,
1306            not_used,
1307            output_ordering.clone(),
1308        )?;
1309        assert_partitioning(partitioned_datasrc, None);
1310
1311        // src has partitions > target partitions, but we don't merge them
1312        let target_partitions = 2;
1313        let mem_src_config = memorysrcconfig_3_partitions_1_batch_each(
1314            sort_information_on_config.clone(),
1315        )?;
1316        let partitioned_datasrc = mem_src_config.repartitioned(
1317            target_partitions,
1318            not_used,
1319            output_ordering.clone(),
1320        )?;
1321        assert_partitioning(partitioned_datasrc, None);
1322
1323        // src has partitions < target partitions, but not enough batches (per partition) to split into more partitions
1324        let target_partitions = 4;
1325        let mem_src_config = memorysrcconfig_3_partitions_1_batch_each(
1326            sort_information_on_config.clone(),
1327        )?;
1328        let partitioned_datasrc = mem_src_config.repartitioned(
1329            target_partitions,
1330            not_used,
1331            output_ordering.clone(),
1332        )?;
1333        assert_partitioning(partitioned_datasrc, None);
1334
1335        // src has partitions < target partitions, and can split to sufficient amount
1336        // has 6 batches across 3 partitions. Will need to split 2 of it's partitions.
1337        let target_partitions = 5;
1338        let mem_src_config = memorysrcconfig_3_partitions_with_2_batches_each(
1339            sort_information_on_config.clone(),
1340        )?;
1341        let partitioned_datasrc = mem_src_config.repartitioned(
1342            target_partitions,
1343            not_used,
1344            output_ordering.clone(),
1345        )?;
1346        assert_partitioning(partitioned_datasrc, Some(5));
1347
1348        // src has partitions < target partitions, and can split to sufficient amount
1349        // has 6 batches across 3 partitions. Will need to split all of it's partitions.
1350        let target_partitions = 6;
1351        let mem_src_config = memorysrcconfig_3_partitions_with_2_batches_each(
1352            sort_information_on_config.clone(),
1353        )?;
1354        let partitioned_datasrc = mem_src_config.repartitioned(
1355            target_partitions,
1356            not_used,
1357            output_ordering.clone(),
1358        )?;
1359        assert_partitioning(partitioned_datasrc, Some(6));
1360
1361        // src has partitions < target partitions, but not enough total batches to fulfill the split (desired target_partitions)
1362        let target_partitions = 3 * 2 + 1;
1363        let mem_src_config = memorysrcconfig_3_partitions_with_2_batches_each(
1364            sort_information_on_config.clone(),
1365        )?;
1366        let partitioned_datasrc = mem_src_config.repartitioned(
1367            target_partitions,
1368            not_used,
1369            output_ordering.clone(),
1370        )?;
1371        assert_partitioning(partitioned_datasrc, None);
1372
1373        // src has 1 partition with many batches of lopsided sizes
1374        // make sure it handles the split properly
1375        let target_partitions = 2;
1376        let mem_src_config = memorysrcconfig_1_partition_with_different_sized_batches(
1377            sort_information_on_config,
1378        )?;
1379        let partitioned_datasrc = mem_src_config.clone().repartitioned(
1380            target_partitions,
1381            not_used,
1382            output_ordering,
1383        )?;
1384        assert_partitioning(partitioned_datasrc.clone(), Some(2));
1385        // Starting = batch(100_000), batch(10_000), batch(100), batch(1).
1386        // It should have split as p1=batch(100_000), p2=[batch(10_000), batch(100), batch(1)]
1387        let partitioned_datasrc = partitioned_datasrc.unwrap();
1388        let Some(mem_src_config) =
1389            partitioned_datasrc.downcast_ref::<MemorySourceConfig>()
1390        else {
1391            unreachable!()
1392        };
1393        let repartitioned_raw_batches = mem_src_config.partitions.clone();
1394        assert_eq!(repartitioned_raw_batches.len(), 2);
1395        let [ref p1, ref p2] = repartitioned_raw_batches[..] else {
1396            unreachable!()
1397        };
1398        // p1=batch(100_000)
1399        assert_eq!(p1.len(), 1);
1400        assert_eq!(p1[0].num_rows(), 100_000);
1401        // p2=[batch(10_000), batch(100), batch(1)]
1402        assert_eq!(p2.len(), 3);
1403        assert_eq!(p2[0].num_rows(), 10_000);
1404        assert_eq!(p2[1].num_rows(), 100);
1405        assert_eq!(p2[2].num_rows(), 1);
1406
1407        Ok(())
1408    }
1409
1410    #[test]
1411    fn test_repartition_no_sort_information_no_output_ordering() -> Result<()> {
1412        let no_sort = vec![];
1413        let no_output_ordering = None;
1414
1415        // Test: common set of functionality
1416        run_all_test_scenarios(no_output_ordering.clone(), no_sort.clone())?;
1417
1418        // Test: how no-sort-order divides differently.
1419        //    * does not preserve separate partitions (with own internal ordering) on even split,
1420        //    * nor does it preserve ordering (re-orders batch(2_000) vs batch(1_000)).
1421        let target_partitions = 3;
1422        let mem_src_config =
1423            memorysrcconfig_2_partition_with_different_sized_batches(no_sort)?;
1424        let partitioned_datasrc = mem_src_config.clone().repartitioned(
1425            target_partitions,
1426            usize::MAX,
1427            no_output_ordering,
1428        )?;
1429        assert_partitioning(partitioned_datasrc.clone(), Some(3));
1430        // Starting = batch(100_000), batch(10_000), batch(1_000), batch(2_000), batch(20)
1431        // It should have split as p1=batch(100_000), p2=batch(10_000),  p3=rest(mixed across original partitions)
1432        let repartitioned_raw_batches = mem_src_config
1433            .repartition_evenly_by_size(target_partitions)?
1434            .unwrap();
1435        assert_eq!(repartitioned_raw_batches.len(), 3);
1436        let [ref p1, ref p2, ref p3] = repartitioned_raw_batches[..] else {
1437            unreachable!()
1438        };
1439        // p1=batch(100_000)
1440        assert_eq!(p1.len(), 1);
1441        assert_eq!(p1[0].num_rows(), 100_000);
1442        // p2=batch(10_000)
1443        assert_eq!(p2.len(), 1);
1444        assert_eq!(p2[0].num_rows(), 10_000);
1445        // p3= batch(2_000), batch(1_000), batch(20)
1446        assert_eq!(p3.len(), 3);
1447        assert_eq!(p3[0].num_rows(), 2_000);
1448        assert_eq!(p3[1].num_rows(), 1_000);
1449        assert_eq!(p3[2].num_rows(), 20);
1450
1451        Ok(())
1452    }
1453
1454    #[test]
1455    fn test_repartition_no_sort_information_no_output_ordering_lopsized_batches()
1456    -> Result<()> {
1457        let no_sort = vec![];
1458        let no_output_ordering = None;
1459
1460        // Test: case has two input partitions:
1461        //     b(100_000), b(1), b(1), b(1), b(1), b(0)
1462        //     b(1), b(1), b(1), b(1), b(0), b(100)
1463        //
1464        // We want an output with target_partitions=5, which means the ideal division is:
1465        //     b(100_000)
1466        //     b(100)
1467        //     b(1), b(1), b(1)
1468        //     b(1), b(1), b(1)
1469        //     b(1), b(1), b(0)
1470        let target_partitions = 5;
1471        let mem_src_config =
1472            memorysrcconfig_2_partition_with_extreme_sized_batches(no_sort)?;
1473        let partitioned_datasrc = mem_src_config.clone().repartitioned(
1474            target_partitions,
1475            usize::MAX,
1476            no_output_ordering,
1477        )?;
1478        assert_partitioning(partitioned_datasrc.clone(), Some(5));
1479        // Starting partition 1 = batch(100_000), batch(1), batch(1), batch(1), batch(1), batch(0)
1480        // Starting partition 1 = batch(1), batch(1), batch(1), batch(1), batch(0), batch(100)
1481        // It should have split as p1=batch(100_000), p2=batch(100), p3=[batch(1),batch(1)], p4=[batch(1),batch(1)], p5=[batch(1),batch(1),batch(0),batch(0)]
1482        let repartitioned_raw_batches = mem_src_config
1483            .repartition_evenly_by_size(target_partitions)?
1484            .unwrap();
1485        assert_eq!(repartitioned_raw_batches.len(), 5);
1486        let [ref p1, ref p2, ref p3, ref p4, ref p5] = repartitioned_raw_batches[..]
1487        else {
1488            unreachable!()
1489        };
1490        // p1=batch(100_000)
1491        assert_eq!(p1.len(), 1);
1492        assert_eq!(p1[0].num_rows(), 100_000);
1493        // p2=batch(100)
1494        assert_eq!(p2.len(), 1);
1495        assert_eq!(p2[0].num_rows(), 100);
1496        // p3=[batch(1),batch(1),batch(1)]
1497        assert_eq!(p3.len(), 3);
1498        assert_eq!(p3[0].num_rows(), 1);
1499        assert_eq!(p3[1].num_rows(), 1);
1500        assert_eq!(p3[2].num_rows(), 1);
1501        // p4=[batch(1),batch(1),batch(1)]
1502        assert_eq!(p4.len(), 3);
1503        assert_eq!(p4[0].num_rows(), 1);
1504        assert_eq!(p4[1].num_rows(), 1);
1505        assert_eq!(p4[2].num_rows(), 1);
1506        // p5=[batch(1),batch(1),batch(0),batch(0)]
1507        assert_eq!(p5.len(), 4);
1508        assert_eq!(p5[0].num_rows(), 1);
1509        assert_eq!(p5[1].num_rows(), 1);
1510        assert_eq!(p5[2].num_rows(), 0);
1511        assert_eq!(p5[3].num_rows(), 0);
1512
1513        Ok(())
1514    }
1515
1516    #[test]
1517    fn test_repartition_with_sort_information() -> Result<()> {
1518        let schema = schema();
1519        let sort_key: LexOrdering =
1520            [PhysicalSortExpr::new_default(col("c", &schema)?)].into();
1521        let has_sort = vec![sort_key.clone()];
1522        let output_ordering = Some(sort_key);
1523
1524        // Test: common set of functionality
1525        run_all_test_scenarios(output_ordering.clone(), has_sort.clone())?;
1526
1527        // Test: DOES preserve separate partitions (with own internal ordering)
1528        let target_partitions = 3;
1529        let mem_src_config =
1530            memorysrcconfig_2_partition_with_different_sized_batches(has_sort)?;
1531        let partitioned_datasrc = mem_src_config.clone().repartitioned(
1532            target_partitions,
1533            usize::MAX,
1534            output_ordering.clone(),
1535        )?;
1536        assert_partitioning(partitioned_datasrc.clone(), Some(3));
1537        // Starting = batch(100_000), batch(10_000), batch(1_000), batch(2_000), batch(20)
1538        // It should have split as p1=batch(100_000), p2=[batch(10_000),batch(1_000)],  p3=<other_partition>
1539        let Some(output_ord) = output_ordering else {
1540            unreachable!()
1541        };
1542        let repartitioned_raw_batches = mem_src_config
1543            .repartition_preserving_order(target_partitions, output_ord)?
1544            .unwrap();
1545        assert_eq!(repartitioned_raw_batches.len(), 3);
1546        let [ref p1, ref p2, ref p3] = repartitioned_raw_batches[..] else {
1547            unreachable!()
1548        };
1549        // p1=batch(100_000)
1550        assert_eq!(p1.len(), 1);
1551        assert_eq!(p1[0].num_rows(), 100_000);
1552        // p2=[batch(10_000),batch(1_000)]
1553        assert_eq!(p2.len(), 2);
1554        assert_eq!(p2[0].num_rows(), 10_000);
1555        assert_eq!(p2[1].num_rows(), 1_000);
1556        // p3=batch(2_000), batch(20)
1557        assert_eq!(p3.len(), 2);
1558        assert_eq!(p3[0].num_rows(), 2_000);
1559        assert_eq!(p3[1].num_rows(), 20);
1560
1561        Ok(())
1562    }
1563
1564    #[test]
1565    fn test_repartition_with_batch_ordering_not_matching_sizing() -> Result<()> {
1566        let schema = schema();
1567        let sort_key: LexOrdering =
1568            [PhysicalSortExpr::new_default(col("c", &schema)?)].into();
1569        let has_sort = vec![sort_key.clone()];
1570        let output_ordering = Some(sort_key);
1571
1572        // src has 1 partition with many batches of lopsided sizes
1573        // note that the input vector of batches are not ordered by decreasing size
1574        let target_partitions = 2;
1575        let mem_src_config =
1576            memorysrcconfig_1_partition_with_ordering_not_matching_size(has_sort)?;
1577        let partitioned_datasrc = mem_src_config.clone().repartitioned(
1578            target_partitions,
1579            usize::MAX,
1580            output_ordering,
1581        )?;
1582        assert_partitioning(partitioned_datasrc.clone(), Some(2));
1583        // Starting = batch(100_000), batch(1), batch(100), batch(10_000).
1584        // It should have split as p1=batch(100_000), p2=[batch(1), batch(100), batch(10_000)]
1585        let partitioned_datasrc = partitioned_datasrc.unwrap();
1586        let Some(mem_src_config) =
1587            partitioned_datasrc.downcast_ref::<MemorySourceConfig>()
1588        else {
1589            unreachable!()
1590        };
1591        let repartitioned_raw_batches = mem_src_config.partitions.clone();
1592        assert_eq!(repartitioned_raw_batches.len(), 2);
1593        let [ref p1, ref p2] = repartitioned_raw_batches[..] else {
1594            unreachable!()
1595        };
1596        // p1=batch(100_000)
1597        assert_eq!(p1.len(), 1);
1598        assert_eq!(p1[0].num_rows(), 100_000);
1599        // p2=[batch(1), batch(100), batch(10_000)] -- **this is preserving the partition order**
1600        assert_eq!(p2.len(), 3);
1601        assert_eq!(p2[0].num_rows(), 1);
1602        assert_eq!(p2[1].num_rows(), 100);
1603        assert_eq!(p2[2].num_rows(), 10_000);
1604
1605        Ok(())
1606    }
1607}