1use std::any::Any;
21use std::fmt;
22use std::sync::Arc;
23use std::task::{Context, Poll};
24
25use crate::coop::cooperative;
26use crate::execution_plan::{Boundedness, EmissionType, SchedulingType};
27use crate::metrics::{BaselineMetrics, ExecutionPlanMetricsSet, MetricsSet};
28use crate::{
29 ChildrenPropertiesMode, DisplayAs, DisplayFormatType, ExecutionPlan, Partitioning,
30 PlanProperties, RecordBatchStream, ReplaceChildrenOptions, SendableRecordBatchStream,
31};
32
33use arrow::array::RecordBatch;
34use arrow::datatypes::SchemaRef;
35use datafusion_common::tree_node::TreeNodeRecursion;
36use datafusion_common::{Result, assert_eq_or_internal_err, assert_or_internal_err};
37use datafusion_execution::TaskContext;
38use datafusion_execution::memory_pool::MemoryReservation;
39use datafusion_physical_expr::{EquivalenceProperties, PhysicalExpr};
40
41use datafusion_physical_expr_common::sort_expr::PhysicalSortExpr;
42use futures::Stream;
43use parking_lot::RwLock;
44
45pub struct MemoryStream {
47 data: Vec<RecordBatch>,
49 reservation: Option<MemoryReservation>,
51 schema: SchemaRef,
53 projection: Option<Vec<usize>>,
55 index: usize,
57 fetch: Option<usize>,
59}
60
61impl MemoryStream {
62 pub fn try_new(
64 data: Vec<RecordBatch>,
65 schema: SchemaRef,
66 projection: Option<Vec<usize>>,
67 ) -> Result<Self> {
68 Ok(Self {
69 data,
70 reservation: None,
71 schema,
72 projection,
73 index: 0,
74 fetch: None,
75 })
76 }
77
78 pub fn with_reservation(mut self, reservation: MemoryReservation) -> Self {
80 self.reservation = Some(reservation);
81 self
82 }
83
84 pub fn with_fetch(mut self, fetch: Option<usize>) -> Self {
86 self.fetch = fetch;
87 self
88 }
89}
90
91impl Stream for MemoryStream {
92 type Item = Result<RecordBatch>;
93
94 fn poll_next(
95 mut self: std::pin::Pin<&mut Self>,
96 _: &mut Context<'_>,
97 ) -> Poll<Option<Self::Item>> {
98 if self.index >= self.data.len() {
99 return Poll::Ready(None);
100 }
101 self.index += 1;
102 let batch = &self.data[self.index - 1];
103 let batch = match self.projection.as_ref() {
105 Some(columns) => batch.project(columns)?,
106 None => batch.clone(),
107 };
108
109 let batch = if batch.schema().as_ref() != self.schema.as_ref()
113 && self.schema.contains(batch.schema().as_ref())
114 {
115 datafusion_common::nested_struct::adapt_batch_to_schema(batch, &self.schema)?
116 } else {
117 batch
118 };
119
120 let Some(&fetch) = self.fetch.as_ref() else {
121 return Poll::Ready(Some(Ok(batch)));
122 };
123 if fetch == 0 {
124 return Poll::Ready(None);
125 }
126
127 let batch = if batch.num_rows() > fetch {
128 batch.slice(0, fetch)
129 } else {
130 batch
131 };
132 self.fetch = Some(fetch - batch.num_rows());
133 Poll::Ready(Some(Ok(batch)))
134 }
135
136 fn size_hint(&self) -> (usize, Option<usize>) {
137 (self.data.len(), Some(self.data.len()))
138 }
139}
140
141impl RecordBatchStream for MemoryStream {
142 fn schema(&self) -> SchemaRef {
144 Arc::clone(&self.schema)
145 }
146}
147
148pub trait LazyBatchGenerator: Send + Sync + fmt::Debug + fmt::Display {
149 fn as_any(&self) -> &dyn Any;
152
153 fn boundedness(&self) -> Boundedness {
154 Boundedness::Bounded
155 }
156
157 fn generate_next_batch(&mut self) -> Result<Option<RecordBatch>>;
159
160 fn reset_state(&self) -> Arc<RwLock<dyn LazyBatchGenerator>>;
162}
163
164pub struct LazyMemoryExec {
169 schema: SchemaRef,
171 projection: Option<Vec<usize>>,
173 batch_generators: Vec<Arc<RwLock<dyn LazyBatchGenerator>>>,
175 cache: Arc<PlanProperties>,
177 metrics: ExecutionPlanMetricsSet,
179}
180
181impl LazyMemoryExec {
182 pub fn try_new(
184 schema: SchemaRef,
185 generators: Vec<Arc<RwLock<dyn LazyBatchGenerator>>>,
186 ) -> Result<Self> {
187 let boundedness = generators
188 .iter()
189 .map(|g| g.read().boundedness())
190 .reduce(|acc, b| match acc {
191 Boundedness::Bounded => b,
192 Boundedness::Unbounded {
193 requires_infinite_memory,
194 } => {
195 let acc_infinite_memory = requires_infinite_memory;
196 match b {
197 Boundedness::Bounded => acc,
198 Boundedness::Unbounded {
199 requires_infinite_memory,
200 } => Boundedness::Unbounded {
201 requires_infinite_memory: requires_infinite_memory
202 || acc_infinite_memory,
203 },
204 }
205 }
206 })
207 .unwrap_or(Boundedness::Bounded);
208
209 let cache = PlanProperties::new(
210 EquivalenceProperties::new(Arc::clone(&schema)),
211 Partitioning::RoundRobinBatch(generators.len()),
212 EmissionType::Incremental,
213 boundedness,
214 )
215 .with_scheduling_type(SchedulingType::Cooperative)
216 .into();
217
218 Ok(Self {
219 schema,
220 projection: None,
221 batch_generators: generators,
222 cache,
223 metrics: ExecutionPlanMetricsSet::new(),
224 })
225 }
226
227 pub fn with_projection(mut self, projection: Option<Vec<usize>>) -> Self {
228 match projection.as_ref() {
229 Some(columns) => {
230 let projected = Arc::new(self.schema.project(columns).unwrap());
231 Arc::make_mut(&mut self.cache).set_eq_properties(
232 EquivalenceProperties::new(Arc::clone(&projected)),
233 );
234 self.schema = projected;
235 self.projection = projection;
236 self
237 }
238 _ => self,
239 }
240 }
241
242 pub fn try_set_partitioning(&mut self, partitioning: Partitioning) -> Result<()> {
243 let partition_count = partitioning.partition_count();
244 let generator_count = self.batch_generators.len();
245 assert_eq_or_internal_err!(
246 partition_count,
247 generator_count,
248 "Partition count must match generator count: {} != {}",
249 partition_count,
250 generator_count
251 );
252 Arc::make_mut(&mut self.cache).partitioning = partitioning;
253 Ok(())
254 }
255
256 pub fn add_ordering(&mut self, ordering: impl IntoIterator<Item = PhysicalSortExpr>) {
257 Arc::make_mut(&mut self.cache)
258 .eq_properties
259 .add_orderings(std::iter::once(ordering));
260 }
261
262 pub fn generators(&self) -> &Vec<Arc<RwLock<dyn LazyBatchGenerator>>> {
264 &self.batch_generators
265 }
266}
267
268impl fmt::Debug for LazyMemoryExec {
269 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
270 f.debug_struct("LazyMemoryExec")
271 .field("schema", &self.schema)
272 .field("batch_generators", &self.batch_generators)
273 .finish()
274 }
275}
276
277impl DisplayAs for LazyMemoryExec {
278 fn fmt_as(&self, t: DisplayFormatType, f: &mut fmt::Formatter) -> fmt::Result {
279 match t {
280 DisplayFormatType::Default | DisplayFormatType::Verbose => {
281 write!(
282 f,
283 "LazyMemoryExec: partitions={}, batch_generators=[{}]",
284 self.batch_generators.len(),
285 self.batch_generators
286 .iter()
287 .map(|g| g.read().to_string())
288 .collect::<Vec<_>>()
289 .join(", ")
290 )
291 }
292 DisplayFormatType::TreeRender => {
293 writeln!(
295 f,
296 "batch_generators={}",
297 self.batch_generators
298 .iter()
299 .map(|g| g.read().to_string())
300 .collect::<Vec<String>>()
301 .join(", ")
302 )?;
303 Ok(())
304 }
305 }
306 }
307}
308
309impl ExecutionPlan for LazyMemoryExec {
310 fn name(&self) -> &'static str {
311 "LazyMemoryExec"
312 }
313
314 fn schema(&self) -> SchemaRef {
315 Arc::clone(&self.schema)
316 }
317
318 fn properties(&self) -> &Arc<PlanProperties> {
319 &self.cache
320 }
321
322 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
323 vec![]
324 }
325
326 fn apply_expressions(
327 &self,
328 _f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
329 ) -> Result<TreeNodeRecursion> {
330 Ok(TreeNodeRecursion::Continue)
331 }
332
333 fn replace_children(
334 self: Arc<Self>,
335 children: Vec<Arc<dyn ExecutionPlan>>,
336 _: ReplaceChildrenOptions,
337 ) -> Result<Arc<dyn ExecutionPlan>> {
338 assert_or_internal_err!(
339 children.is_empty(),
340 "Children cannot be replaced in LazyMemoryExec"
341 );
342 Ok(self)
343 }
344
345 fn with_new_children(
346 self: Arc<Self>,
347 children: Vec<Arc<dyn ExecutionPlan>>,
348 ) -> Result<Arc<dyn ExecutionPlan>> {
349 self.replace_children(
350 children,
351 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
352 )
353 }
354
355 fn execute(
356 &self,
357 partition: usize,
358 _context: Arc<TaskContext>,
359 ) -> Result<SendableRecordBatchStream> {
360 assert_or_internal_err!(
361 partition < self.batch_generators.len(),
362 "Invalid partition {} for LazyMemoryExec with {} partitions",
363 partition,
364 self.batch_generators.len()
365 );
366
367 let baseline_metrics = BaselineMetrics::new(&self.metrics, partition);
368
369 let generator = self.batch_generators[partition].read().reset_state();
372
373 let stream = LazyMemoryStream {
374 schema: Arc::clone(&self.schema),
375 projection: self.projection.clone(),
376 generator,
377 baseline_metrics,
378 };
379 Ok(Box::pin(cooperative(stream)))
380 }
381
382 fn metrics(&self) -> Option<MetricsSet> {
383 Some(self.metrics.clone_inner())
384 }
385
386 fn reset_state(self: Arc<Self>) -> Result<Arc<dyn ExecutionPlan>> {
387 let generators = self
388 .generators()
389 .iter()
390 .map(|g| g.read().reset_state())
391 .collect::<Vec<_>>();
392 Ok(Arc::new(LazyMemoryExec {
393 schema: Arc::clone(&self.schema),
394 batch_generators: generators,
395 cache: Arc::clone(&self.cache),
396 metrics: ExecutionPlanMetricsSet::new(),
397 projection: self.projection.clone(),
398 }))
399 }
400}
401
402pub struct LazyMemoryStream {
404 schema: SchemaRef,
405 projection: Option<Vec<usize>>,
407 generator: Arc<RwLock<dyn LazyBatchGenerator>>,
415 baseline_metrics: BaselineMetrics,
417}
418
419impl Stream for LazyMemoryStream {
420 type Item = Result<RecordBatch>;
421
422 fn poll_next(
423 self: std::pin::Pin<&mut Self>,
424 _: &mut Context<'_>,
425 ) -> Poll<Option<Self::Item>> {
426 let _timer_guard = self.baseline_metrics.elapsed_compute().timer();
427 let batch = self.generator.write().generate_next_batch();
428
429 let poll = match batch {
430 Ok(Some(batch)) => {
431 let batch = match self.projection.as_ref() {
433 Some(columns) => batch.project(columns)?,
434 None => batch,
435 };
436 Poll::Ready(Some(Ok(batch)))
437 }
438 Ok(None) => Poll::Ready(None),
439 Err(e) => Poll::Ready(Some(Err(e))),
440 };
441
442 self.baseline_metrics.record_poll(poll)
443 }
444}
445
446impl RecordBatchStream for LazyMemoryStream {
447 fn schema(&self) -> SchemaRef {
448 Arc::clone(&self.schema)
449 }
450}
451
452#[cfg(test)]
453mod lazy_memory_tests {
454 use super::*;
455 use crate::common::collect;
456 use arrow::array::Int64Array;
457 use arrow::datatypes::{DataType, Field, Schema};
458 use futures::StreamExt;
459
460 #[derive(Debug, Clone)]
461 struct TestGenerator {
462 counter: i64,
463 max_batches: i64,
464 batch_size: usize,
465 schema: SchemaRef,
466 }
467
468 impl fmt::Display for TestGenerator {
469 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
470 write!(
471 f,
472 "TestGenerator: counter={}, max_batches={}, batch_size={}",
473 self.counter, self.max_batches, self.batch_size
474 )
475 }
476 }
477
478 impl LazyBatchGenerator for TestGenerator {
479 fn as_any(&self) -> &dyn Any {
480 self
481 }
482
483 fn generate_next_batch(&mut self) -> Result<Option<RecordBatch>> {
484 if self.counter >= self.max_batches {
485 return Ok(None);
486 }
487
488 let array = Int64Array::from_iter_values(
489 (self.counter * self.batch_size as i64)
490 ..(self.counter * self.batch_size as i64 + self.batch_size as i64),
491 );
492 self.counter += 1;
493 Ok(Some(RecordBatch::try_new(
494 Arc::clone(&self.schema),
495 vec![Arc::new(array)],
496 )?))
497 }
498
499 fn reset_state(&self) -> Arc<RwLock<dyn LazyBatchGenerator>> {
500 Arc::new(RwLock::new(TestGenerator {
501 counter: 0,
502 max_batches: self.max_batches,
503 batch_size: self.batch_size,
504 schema: Arc::clone(&self.schema),
505 }))
506 }
507 }
508
509 #[tokio::test]
510 async fn test_lazy_memory_exec() -> Result<()> {
511 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int64, false)]));
512 let generator = TestGenerator {
513 counter: 0,
514 max_batches: 3,
515 batch_size: 2,
516 schema: Arc::clone(&schema),
517 };
518
519 let exec =
520 LazyMemoryExec::try_new(schema, vec![Arc::new(RwLock::new(generator))])?;
521
522 assert_eq!(exec.schema().fields().len(), 1);
524 assert_eq!(exec.schema().field(0).name(), "a");
525
526 let stream = exec.execute(0, Arc::new(TaskContext::default()))?;
528 let batches: Vec<_> = stream.collect::<Vec<_>>().await;
529
530 assert_eq!(batches.len(), 3);
531
532 let batch0 = batches[0].as_ref().unwrap();
534 let array0 = batch0
535 .column(0)
536 .as_any()
537 .downcast_ref::<Int64Array>()
538 .unwrap();
539 assert_eq!(array0.values(), &[0, 1]);
540
541 let batch1 = batches[1].as_ref().unwrap();
542 let array1 = batch1
543 .column(0)
544 .as_any()
545 .downcast_ref::<Int64Array>()
546 .unwrap();
547 assert_eq!(array1.values(), &[2, 3]);
548
549 let batch2 = batches[2].as_ref().unwrap();
550 let array2 = batch2
551 .column(0)
552 .as_any()
553 .downcast_ref::<Int64Array>()
554 .unwrap();
555 assert_eq!(array2.values(), &[4, 5]);
556
557 Ok(())
558 }
559
560 #[tokio::test]
563 async fn test_lazy_memory_exec_multiple_executions_are_independent() -> Result<()> {
564 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int64, false)]));
565 let generator = TestGenerator {
566 counter: 0,
567 max_batches: 3,
568 batch_size: 2,
569 schema: Arc::clone(&schema),
570 };
571
572 let exec =
573 LazyMemoryExec::try_new(schema, vec![Arc::new(RwLock::new(generator))])?;
574 let task_ctx = Arc::new(TaskContext::default());
575
576 let batches_1 = collect(exec.execute(0, Arc::clone(&task_ctx))?).await?;
578 let total_rows_1: usize = batches_1.iter().map(|b| b.num_rows()).sum();
579 assert_eq!(total_rows_1, 6);
580
581 let batches_2 = collect(exec.execute(0, Arc::clone(&task_ctx))?).await?;
584 let total_rows_2: usize = batches_2.iter().map(|b| b.num_rows()).sum();
585 assert_eq!(total_rows_2, 6);
586
587 for (b1, b2) in batches_1.iter().zip(batches_2.iter()) {
589 assert_eq!(b1, b2);
590 }
591
592 Ok(())
593 }
594
595 #[tokio::test]
596 async fn test_lazy_memory_exec_invalid_partition() -> Result<()> {
597 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int64, false)]));
598 let generator = TestGenerator {
599 counter: 0,
600 max_batches: 1,
601 batch_size: 1,
602 schema: Arc::clone(&schema),
603 };
604
605 let exec =
606 LazyMemoryExec::try_new(schema, vec![Arc::new(RwLock::new(generator))])?;
607
608 let result = exec.execute(1, Arc::new(TaskContext::default()));
610
611 assert!(matches!(
613 result,
614 Err(e) if e.to_string().contains("Invalid partition 1 for LazyMemoryExec with 1 partitions")
615 ));
616
617 Ok(())
618 }
619
620 #[tokio::test]
621 async fn test_generate_series_metrics_integration() -> Result<()> {
622 let test_cases = vec![
624 (10, 2, 10), (100, 10, 100), (5, 1, 5), ];
628
629 for (total_rows, batch_size, expected_rows) in test_cases {
630 let schema =
631 Arc::new(Schema::new(vec![Field::new("a", DataType::Int64, false)]));
632 let generator = TestGenerator {
633 counter: 0,
634 max_batches: (total_rows + batch_size - 1) / batch_size, batch_size: batch_size as usize,
636 schema: Arc::clone(&schema),
637 };
638
639 let exec =
640 LazyMemoryExec::try_new(schema, vec![Arc::new(RwLock::new(generator))])?;
641 let task_ctx = Arc::new(TaskContext::default());
642
643 let stream = exec.execute(0, task_ctx)?;
644 let batches = collect(stream).await?;
645
646 let metrics = exec.metrics().unwrap();
648
649 let actual_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
651 assert_eq!(actual_rows, expected_rows);
652
653 assert_eq!(metrics.output_rows().unwrap(), expected_rows);
655 assert!(metrics.elapsed_compute().unwrap() > 0);
656 }
657
658 Ok(())
659 }
660
661 #[tokio::test]
662 async fn test_lazy_memory_exec_reset_state() -> Result<()> {
663 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int64, false)]));
664 let generator = TestGenerator {
665 counter: 0,
666 max_batches: 3,
667 batch_size: 2,
668 schema: Arc::clone(&schema),
669 };
670
671 let exec = Arc::new(LazyMemoryExec::try_new(
672 schema,
673 vec![Arc::new(RwLock::new(generator))],
674 )?);
675 let stream = exec.execute(0, Arc::new(TaskContext::default()))?;
676 let batches = collect(stream).await?;
677
678 let exec_reset = exec.reset_state()?;
679 let stream = exec_reset.execute(0, Arc::new(TaskContext::default()))?;
680 let batches_reset = collect(stream).await?;
681
682 assert_eq!(batches, batches_reset);
684
685 Ok(())
686 }
687
688 #[tokio::test]
689 async fn test_memory_stream_emitted_batch_matches_declared_schema() -> Result<()> {
690 use arrow::array::{ArrayRef, BooleanArray, StructArray};
691 use arrow::datatypes::{DataType, Field, Fields, Schema};
692 use futures::StreamExt;
693
694 let declared_fields =
696 Fields::from(vec![Field::new("colA", DataType::Boolean, true)]);
697 let declared_schema = Arc::new(Schema::new(vec![Field::new(
698 "b",
699 DataType::Struct(declared_fields),
700 false,
701 )]));
702
703 let source_fields =
705 Fields::from(vec![Field::new("colA", DataType::Boolean, false)]);
706 let source_schema = Arc::new(Schema::new(vec![Field::new(
707 "b",
708 DataType::Struct(source_fields.clone()),
709 false,
710 )]));
711
712 let struct_array: ArrayRef = Arc::new(StructArray::new(
713 source_fields,
714 vec![Arc::new(BooleanArray::from(vec![true, false]))],
715 None,
716 ));
717 let stricter_batch = RecordBatch::try_new(source_schema, vec![struct_array])?;
718
719 let mut stream = MemoryStream::try_new(
720 vec![stricter_batch],
721 Arc::clone(&declared_schema),
722 None,
723 )?;
724
725 assert_eq!(stream.schema(), declared_schema);
726
727 let emitted_batch = stream.next().await.unwrap()?;
728 assert_eq!(emitted_batch.schema(), declared_schema);
729
730 let struct_col = emitted_batch
731 .column(0)
732 .as_any()
733 .downcast_ref::<StructArray>()
734 .unwrap();
735 assert!(struct_col.fields()[0].is_nullable());
736 let bool_child = struct_col
737 .column(0)
738 .as_any()
739 .downcast_ref::<BooleanArray>()
740 .unwrap();
741 assert!(bool_child.value(0));
742 assert!(!bool_child.value(1));
743
744 Ok(())
745 }
746
747 #[tokio::test]
748 async fn test_memory_stream_emitted_batch_matches_declared_schema_with_projection()
749 -> Result<()> {
750 use arrow::array::{ArrayRef, BooleanArray, Int32Array, StructArray};
751 use arrow::datatypes::{DataType, Field, Fields, Schema};
752 use futures::StreamExt;
753
754 let declared_fields =
756 Fields::from(vec![Field::new("colA", DataType::Boolean, true)]);
757 let full_declared_schema = Arc::new(Schema::new(vec![
758 Field::new("a", DataType::Int32, false),
759 Field::new("b", DataType::Struct(declared_fields), false),
760 ]));
761
762 let projected_schema = Arc::new(full_declared_schema.project(&[1])?);
764
765 let source_fields =
767 Fields::from(vec![Field::new("colA", DataType::Boolean, false)]);
768 let source_schema = Arc::new(Schema::new(vec![
769 Field::new("a", DataType::Int32, false),
770 Field::new("b", DataType::Struct(source_fields.clone()), false),
771 ]));
772
773 let struct_array: ArrayRef = Arc::new(StructArray::new(
774 source_fields,
775 vec![Arc::new(BooleanArray::from(vec![true, false]))],
776 None,
777 ));
778 let stricter_batch = RecordBatch::try_new(
779 source_schema,
780 vec![Arc::new(Int32Array::from(vec![10, 20])), struct_array],
781 )?;
782
783 let mut stream = MemoryStream::try_new(
784 vec![stricter_batch],
785 Arc::clone(&projected_schema),
786 Some(vec![1]),
787 )?;
788
789 assert_eq!(stream.schema(), projected_schema);
790
791 let emitted_batch = stream.next().await.unwrap()?;
792 assert_eq!(emitted_batch.schema(), projected_schema);
793 assert_eq!(emitted_batch.num_columns(), 1);
794
795 let struct_col = emitted_batch
796 .column(0)
797 .as_any()
798 .downcast_ref::<StructArray>()
799 .unwrap();
800 assert!(struct_col.fields()[0].is_nullable());
801 let bool_child = struct_col
802 .column(0)
803 .as_any()
804 .downcast_ref::<BooleanArray>()
805 .unwrap();
806 assert!(bool_child.value(0));
807 assert!(!bool_child.value(1));
808
809 Ok(())
810 }
811
812 #[tokio::test]
816 async fn test_memory_stream_emitted_batch_matches_declared_schema_union() -> Result<()>
817 {
818 use arrow::array::{Array, ArrayRef, Float64Array, Int32Array, UnionArray};
819 use arrow::buffer::ScalarBuffer;
820 use arrow::datatypes::{DataType, Field, Schema, UnionFields, UnionMode};
821 use futures::StreamExt;
822
823 let declared_union_fields = UnionFields::try_new(
824 vec![0_i8, 1],
825 vec![
826 Field::new("i", DataType::Int32, true),
827 Field::new("f", DataType::Float64, true),
828 ],
829 )?;
830 let declared_schema = Arc::new(Schema::new(vec![Field::new(
831 "u",
832 DataType::Union(declared_union_fields, UnionMode::Dense),
833 false,
834 )]));
835
836 let source_union_fields = UnionFields::try_new(
837 vec![0_i8, 1],
838 vec![
839 Field::new("i", DataType::Int32, false),
840 Field::new("f", DataType::Float64, false),
841 ],
842 )?;
843 let source_schema = Arc::new(Schema::new(vec![Field::new(
844 "u",
845 DataType::Union(source_union_fields.clone(), UnionMode::Dense),
846 false,
847 )]));
848
849 let type_ids = ScalarBuffer::from(vec![0_i8, 1, 0]);
850 let offsets = ScalarBuffer::from(vec![0_i32, 0, 1]);
851 let union_array: ArrayRef = Arc::new(UnionArray::try_new(
852 source_union_fields,
853 type_ids,
854 Some(offsets),
855 vec![
856 Arc::new(Int32Array::from(vec![10, 20])),
857 Arc::new(Float64Array::from(vec![1.5])),
858 ],
859 )?);
860 let stricter_batch = RecordBatch::try_new(source_schema, vec![union_array])?;
861
862 assert!(declared_schema.contains(stricter_batch.schema().as_ref()));
863
864 let mut stream = MemoryStream::try_new(
865 vec![stricter_batch],
866 Arc::clone(&declared_schema),
867 None,
868 )?;
869
870 assert_eq!(stream.schema(), declared_schema);
871
872 let emitted_batch = stream.next().await.unwrap()?;
873 assert_eq!(emitted_batch.schema(), stream.schema());
874 assert_eq!(emitted_batch.schema(), declared_schema);
875
876 let union_col = emitted_batch
877 .column(0)
878 .as_any()
879 .downcast_ref::<UnionArray>()
880 .unwrap();
881 assert_eq!(union_col.len(), 3);
882 assert_eq!(union_col.type_id(0), 0);
883 assert_eq!(union_col.type_id(1), 1);
884 assert_eq!(union_col.type_id(2), 0);
885 let i_child = union_col
886 .child(0)
887 .as_any()
888 .downcast_ref::<Int32Array>()
889 .unwrap();
890 assert_eq!(i_child.values(), &[10, 20]);
891
892 Ok(())
893 }
894
895 #[tokio::test]
898 async fn test_memory_stream_emitted_batch_matches_declared_schema_map_of_struct()
899 -> Result<()> {
900 use arrow::array::{
901 Array, ArrayRef, Int32Array, MapArray, StringArray, StructArray,
902 };
903 use arrow::buffer::OffsetBuffer;
904 use arrow::datatypes::{DataType, Field, Fields, Schema};
905 use futures::StreamExt;
906
907 fn map_field(value_child_nullable: bool) -> Field {
908 let value_struct = DataType::Struct(Fields::from(vec![Field::new(
909 "v",
910 DataType::Int32,
911 value_child_nullable,
912 )]));
913 let entries = Field::new(
914 "entries",
915 DataType::Struct(Fields::from(vec![
916 Field::new("keys", DataType::Utf8, false),
917 Field::new("values", value_struct, true),
918 ])),
919 false,
920 );
921 Field::new("m", DataType::Map(Arc::new(entries), false), true)
922 }
923
924 let declared_schema = Arc::new(Schema::new(vec![map_field(true)]));
925 let source_schema = Arc::new(Schema::new(vec![map_field(false)]));
926
927 let value_fields = Fields::from(vec![Field::new("v", DataType::Int32, false)]);
928 let values_struct = StructArray::new(
929 value_fields,
930 vec![Arc::new(Int32Array::from(vec![1, 2, 3])) as ArrayRef],
931 None,
932 );
933 let entries = StructArray::new(
934 Fields::from(vec![
935 Field::new("keys", DataType::Utf8, false),
936 Field::new("values", values_struct.data_type().clone(), true),
937 ]),
938 vec![
939 Arc::new(StringArray::from(vec!["a", "b", "c"])) as ArrayRef,
940 Arc::new(values_struct) as ArrayRef,
941 ],
942 None,
943 );
944 let DataType::Map(source_entries_field, _) = source_schema.field(0).data_type()
945 else {
946 unreachable!("map field")
947 };
948 let map_array: ArrayRef = Arc::new(MapArray::try_new(
949 Arc::clone(source_entries_field),
950 OffsetBuffer::new(vec![0, 2, 3].into()),
951 entries,
952 None,
953 false,
954 )?);
955 let stricter_batch = RecordBatch::try_new(source_schema, vec![map_array])?;
956
957 assert!(declared_schema.contains(stricter_batch.schema().as_ref()));
959
960 let mut stream = MemoryStream::try_new(
961 vec![stricter_batch],
962 Arc::clone(&declared_schema),
963 None,
964 )?;
965
966 assert_eq!(stream.schema(), declared_schema);
967
968 let emitted_batch = stream.next().await.unwrap()?;
969 assert_eq!(emitted_batch.schema(), stream.schema());
970 assert_eq!(emitted_batch.schema(), declared_schema);
971
972 let map_col = emitted_batch
973 .column(0)
974 .as_any()
975 .downcast_ref::<MapArray>()
976 .unwrap();
977 assert_eq!(map_col.len(), 2);
978 let values = map_col
979 .values()
980 .as_any()
981 .downcast_ref::<StructArray>()
982 .unwrap();
983 assert!(values.fields()[0].is_nullable());
984 let ints = values
985 .column(0)
986 .as_any()
987 .downcast_ref::<Int32Array>()
988 .unwrap();
989 assert_eq!(ints.values(), &[1, 2, 3]);
990
991 Ok(())
992 }
993}