1use 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#[derive(Clone, Debug)]
58pub struct MemorySourceConfig {
59 partitions: Vec<Vec<RecordBatch>>,
63 schema: SchemaRef,
65 projected_schema: SchemaRef,
67 projection: Option<Vec<usize>>,
69 sort_information: Vec<LexOrdering>,
71 show_sizes: bool,
73 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 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 {
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 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 Ok(Arc::new(Statistics::new_unknown(&self.projected_schema)))
207 }
208 } else {
209 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 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 let mut new_source = self.clone();
248 new_source.projection = Some(new_projections);
249 new_source.projected_schema = projected_schema;
250 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 #[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 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 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 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 #[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 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 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 #[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 pub fn with_limit(mut self, limit: Option<usize>) -> Self {
446 self.fetch = limit;
447 self
448 }
449
450 pub fn with_show_sizes(mut self, show_sizes: bool) -> Self {
452 self.show_sizes = show_sizes;
453 self
454 }
455
456 pub fn partitions(&self) -> &[Vec<RecordBatch>] {
458 &self.partitions
459 }
460
461 pub fn projection(&self) -> &Option<Vec<usize>> {
463 &self.projection
464 }
465
466 pub fn show_sizes(&self) -> bool {
468 self.show_sizes
469 }
470
471 pub fn sort_information(&self) -> &[LexOrdering] {
473 &self.sort_information
474 }
475
476 pub fn try_with_sort_information(
496 mut self,
497 mut sort_information: Vec<LexOrdering>,
498 ) -> Result<Self> {
499 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 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 pub fn original_schema(&self) -> SchemaRef {
529 Arc::clone(&self.schema)
530 }
531
532 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 return Ok(None);
550 }
551
552 let cnt_to_repartition = target_partitions - self.partitions.len();
553
554 let to_repartition = self
557 .partitions
558 .iter()
559 .enumerate()
560 .map(|(idx, batches)| RePartition {
561 idx: idx + (cnt_to_repartition * idx), row_count: batches.iter().map(|batch| batch.num_rows()).sum(),
563 batches: batches.clone(),
564 })
565 .collect_vec();
566
567 let mut max_heap = BinaryHeap::with_capacity(target_partitions);
570 for rep in to_repartition {
571 max_heap.push(CompareByRowCount(rep));
572 }
573
574 let mut cannot_split_further = Vec::with_capacity(target_partitions);
577 for _ in 0..cnt_to_repartition {
578 loop {
580 let Some(to_split) = max_heap.pop() else {
582 break;
584 };
585
586 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 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 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 fn repartition_evenly_by_size(
623 &self,
624 target_partitions: usize,
625 ) -> Result<Option<Vec<Vec<RecordBatch>>>> {
626 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 let total_num_rows = flatten_batches.iter().map(|b| b.num_rows()).sum::<usize>();
635 flatten_batches.sort_by_key(|b| std::cmp::Reverse(b.num_rows()));
637
638 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 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 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 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#[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#[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
760struct RePartition {
764 idx: usize,
766 row_count: usize,
769 batches: Vec<RecordBatch>,
771}
772
773impl RePartition {
774 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, row_count: 0,
785 batches: vec![],
786 };
787 let new_1 = RePartition {
788 idx: self.idx + 1, 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 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
856pub type PartitionData = Arc<RwLock<Vec<RecordBatch>>>;
858
859pub struct MemSink {
863 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 write!(f, "")
886 }
887 }
888 }
889}
890
891impl MemSink {
892 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 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 for (target, mut batches) in self.batches.iter().zip(new_batches) {
929 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]
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 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]
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 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), column_statistics: vec![ColumnStatistics {
1144 null_count: Precision::Exact(rows), 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 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 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 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 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 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 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 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 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 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 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 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 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 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 assert_eq!(p1.len(), 1);
1400 assert_eq!(p1[0].num_rows(), 100_000);
1401 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 run_all_test_scenarios(no_output_ordering.clone(), no_sort.clone())?;
1417
1418 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 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 assert_eq!(p1.len(), 1);
1441 assert_eq!(p1[0].num_rows(), 100_000);
1442 assert_eq!(p2.len(), 1);
1444 assert_eq!(p2[0].num_rows(), 10_000);
1445 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 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 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 assert_eq!(p1.len(), 1);
1492 assert_eq!(p1[0].num_rows(), 100_000);
1493 assert_eq!(p2.len(), 1);
1495 assert_eq!(p2[0].num_rows(), 100);
1496 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 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 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 run_all_test_scenarios(output_ordering.clone(), has_sort.clone())?;
1526
1527 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 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 assert_eq!(p1.len(), 1);
1551 assert_eq!(p1[0].num_rows(), 100_000);
1552 assert_eq!(p2.len(), 2);
1554 assert_eq!(p2[0].num_rows(), 10_000);
1555 assert_eq!(p2[1].num_rows(), 1_000);
1556 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 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 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 assert_eq!(p1.len(), 1);
1598 assert_eq!(p1[0].num_rows(), 100_000);
1599 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}