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 Some(&fetch) = self.fetch.as_ref() else {
110 return Poll::Ready(Some(Ok(batch)));
111 };
112 if fetch == 0 {
113 return Poll::Ready(None);
114 }
115
116 let batch = if batch.num_rows() > fetch {
117 batch.slice(0, fetch)
118 } else {
119 batch
120 };
121 self.fetch = Some(fetch - batch.num_rows());
122 Poll::Ready(Some(Ok(batch)))
123 }
124
125 fn size_hint(&self) -> (usize, Option<usize>) {
126 (self.data.len(), Some(self.data.len()))
127 }
128}
129
130impl RecordBatchStream for MemoryStream {
131 fn schema(&self) -> SchemaRef {
133 Arc::clone(&self.schema)
134 }
135}
136
137pub trait LazyBatchGenerator: Send + Sync + fmt::Debug + fmt::Display {
138 fn as_any(&self) -> &dyn Any;
141
142 fn boundedness(&self) -> Boundedness {
143 Boundedness::Bounded
144 }
145
146 fn generate_next_batch(&mut self) -> Result<Option<RecordBatch>>;
148
149 fn reset_state(&self) -> Arc<RwLock<dyn LazyBatchGenerator>>;
151}
152
153pub struct LazyMemoryExec {
158 schema: SchemaRef,
160 projection: Option<Vec<usize>>,
162 batch_generators: Vec<Arc<RwLock<dyn LazyBatchGenerator>>>,
164 cache: Arc<PlanProperties>,
166 metrics: ExecutionPlanMetricsSet,
168}
169
170impl LazyMemoryExec {
171 pub fn try_new(
173 schema: SchemaRef,
174 generators: Vec<Arc<RwLock<dyn LazyBatchGenerator>>>,
175 ) -> Result<Self> {
176 let boundedness = generators
177 .iter()
178 .map(|g| g.read().boundedness())
179 .reduce(|acc, b| match acc {
180 Boundedness::Bounded => b,
181 Boundedness::Unbounded {
182 requires_infinite_memory,
183 } => {
184 let acc_infinite_memory = requires_infinite_memory;
185 match b {
186 Boundedness::Bounded => acc,
187 Boundedness::Unbounded {
188 requires_infinite_memory,
189 } => Boundedness::Unbounded {
190 requires_infinite_memory: requires_infinite_memory
191 || acc_infinite_memory,
192 },
193 }
194 }
195 })
196 .unwrap_or(Boundedness::Bounded);
197
198 let cache = PlanProperties::new(
199 EquivalenceProperties::new(Arc::clone(&schema)),
200 Partitioning::RoundRobinBatch(generators.len()),
201 EmissionType::Incremental,
202 boundedness,
203 )
204 .with_scheduling_type(SchedulingType::Cooperative)
205 .into();
206
207 Ok(Self {
208 schema,
209 projection: None,
210 batch_generators: generators,
211 cache,
212 metrics: ExecutionPlanMetricsSet::new(),
213 })
214 }
215
216 pub fn with_projection(mut self, projection: Option<Vec<usize>>) -> Self {
217 match projection.as_ref() {
218 Some(columns) => {
219 let projected = Arc::new(self.schema.project(columns).unwrap());
220 Arc::make_mut(&mut self.cache).set_eq_properties(
221 EquivalenceProperties::new(Arc::clone(&projected)),
222 );
223 self.schema = projected;
224 self.projection = projection;
225 self
226 }
227 _ => self,
228 }
229 }
230
231 pub fn try_set_partitioning(&mut self, partitioning: Partitioning) -> Result<()> {
232 let partition_count = partitioning.partition_count();
233 let generator_count = self.batch_generators.len();
234 assert_eq_or_internal_err!(
235 partition_count,
236 generator_count,
237 "Partition count must match generator count: {} != {}",
238 partition_count,
239 generator_count
240 );
241 Arc::make_mut(&mut self.cache).partitioning = partitioning;
242 Ok(())
243 }
244
245 pub fn add_ordering(&mut self, ordering: impl IntoIterator<Item = PhysicalSortExpr>) {
246 Arc::make_mut(&mut self.cache)
247 .eq_properties
248 .add_orderings(std::iter::once(ordering));
249 }
250
251 pub fn generators(&self) -> &Vec<Arc<RwLock<dyn LazyBatchGenerator>>> {
253 &self.batch_generators
254 }
255}
256
257impl fmt::Debug for LazyMemoryExec {
258 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
259 f.debug_struct("LazyMemoryExec")
260 .field("schema", &self.schema)
261 .field("batch_generators", &self.batch_generators)
262 .finish()
263 }
264}
265
266impl DisplayAs for LazyMemoryExec {
267 fn fmt_as(&self, t: DisplayFormatType, f: &mut fmt::Formatter) -> fmt::Result {
268 match t {
269 DisplayFormatType::Default | DisplayFormatType::Verbose => {
270 write!(
271 f,
272 "LazyMemoryExec: partitions={}, batch_generators=[{}]",
273 self.batch_generators.len(),
274 self.batch_generators
275 .iter()
276 .map(|g| g.read().to_string())
277 .collect::<Vec<_>>()
278 .join(", ")
279 )
280 }
281 DisplayFormatType::TreeRender => {
282 writeln!(
284 f,
285 "batch_generators={}",
286 self.batch_generators
287 .iter()
288 .map(|g| g.read().to_string())
289 .collect::<Vec<String>>()
290 .join(", ")
291 )?;
292 Ok(())
293 }
294 }
295 }
296}
297
298impl ExecutionPlan for LazyMemoryExec {
299 fn name(&self) -> &'static str {
300 "LazyMemoryExec"
301 }
302
303 fn schema(&self) -> SchemaRef {
304 Arc::clone(&self.schema)
305 }
306
307 fn properties(&self) -> &Arc<PlanProperties> {
308 &self.cache
309 }
310
311 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
312 vec![]
313 }
314
315 fn apply_expressions(
316 &self,
317 _f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
318 ) -> Result<TreeNodeRecursion> {
319 Ok(TreeNodeRecursion::Continue)
320 }
321
322 fn replace_children(
323 self: Arc<Self>,
324 children: Vec<Arc<dyn ExecutionPlan>>,
325 _: ReplaceChildrenOptions,
326 ) -> Result<Arc<dyn ExecutionPlan>> {
327 assert_or_internal_err!(
328 children.is_empty(),
329 "Children cannot be replaced in LazyMemoryExec"
330 );
331 Ok(self)
332 }
333
334 fn with_new_children(
335 self: Arc<Self>,
336 children: Vec<Arc<dyn ExecutionPlan>>,
337 ) -> Result<Arc<dyn ExecutionPlan>> {
338 self.replace_children(
339 children,
340 ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
341 )
342 }
343
344 fn execute(
345 &self,
346 partition: usize,
347 _context: Arc<TaskContext>,
348 ) -> Result<SendableRecordBatchStream> {
349 assert_or_internal_err!(
350 partition < self.batch_generators.len(),
351 "Invalid partition {} for LazyMemoryExec with {} partitions",
352 partition,
353 self.batch_generators.len()
354 );
355
356 let baseline_metrics = BaselineMetrics::new(&self.metrics, partition);
357
358 let generator = self.batch_generators[partition].read().reset_state();
361
362 let stream = LazyMemoryStream {
363 schema: Arc::clone(&self.schema),
364 projection: self.projection.clone(),
365 generator,
366 baseline_metrics,
367 };
368 Ok(Box::pin(cooperative(stream)))
369 }
370
371 fn metrics(&self) -> Option<MetricsSet> {
372 Some(self.metrics.clone_inner())
373 }
374
375 fn reset_state(self: Arc<Self>) -> Result<Arc<dyn ExecutionPlan>> {
376 let generators = self
377 .generators()
378 .iter()
379 .map(|g| g.read().reset_state())
380 .collect::<Vec<_>>();
381 Ok(Arc::new(LazyMemoryExec {
382 schema: Arc::clone(&self.schema),
383 batch_generators: generators,
384 cache: Arc::clone(&self.cache),
385 metrics: ExecutionPlanMetricsSet::new(),
386 projection: self.projection.clone(),
387 }))
388 }
389}
390
391pub struct LazyMemoryStream {
393 schema: SchemaRef,
394 projection: Option<Vec<usize>>,
396 generator: Arc<RwLock<dyn LazyBatchGenerator>>,
404 baseline_metrics: BaselineMetrics,
406}
407
408impl Stream for LazyMemoryStream {
409 type Item = Result<RecordBatch>;
410
411 fn poll_next(
412 self: std::pin::Pin<&mut Self>,
413 _: &mut Context<'_>,
414 ) -> Poll<Option<Self::Item>> {
415 let _timer_guard = self.baseline_metrics.elapsed_compute().timer();
416 let batch = self.generator.write().generate_next_batch();
417
418 let poll = match batch {
419 Ok(Some(batch)) => {
420 let batch = match self.projection.as_ref() {
422 Some(columns) => batch.project(columns)?,
423 None => batch,
424 };
425 Poll::Ready(Some(Ok(batch)))
426 }
427 Ok(None) => Poll::Ready(None),
428 Err(e) => Poll::Ready(Some(Err(e))),
429 };
430
431 self.baseline_metrics.record_poll(poll)
432 }
433}
434
435impl RecordBatchStream for LazyMemoryStream {
436 fn schema(&self) -> SchemaRef {
437 Arc::clone(&self.schema)
438 }
439}
440
441#[cfg(test)]
442mod lazy_memory_tests {
443 use super::*;
444 use crate::common::collect;
445 use arrow::array::Int64Array;
446 use arrow::datatypes::{DataType, Field, Schema};
447 use futures::StreamExt;
448
449 #[derive(Debug, Clone)]
450 struct TestGenerator {
451 counter: i64,
452 max_batches: i64,
453 batch_size: usize,
454 schema: SchemaRef,
455 }
456
457 impl fmt::Display for TestGenerator {
458 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
459 write!(
460 f,
461 "TestGenerator: counter={}, max_batches={}, batch_size={}",
462 self.counter, self.max_batches, self.batch_size
463 )
464 }
465 }
466
467 impl LazyBatchGenerator for TestGenerator {
468 fn as_any(&self) -> &dyn Any {
469 self
470 }
471
472 fn generate_next_batch(&mut self) -> Result<Option<RecordBatch>> {
473 if self.counter >= self.max_batches {
474 return Ok(None);
475 }
476
477 let array = Int64Array::from_iter_values(
478 (self.counter * self.batch_size as i64)
479 ..(self.counter * self.batch_size as i64 + self.batch_size as i64),
480 );
481 self.counter += 1;
482 Ok(Some(RecordBatch::try_new(
483 Arc::clone(&self.schema),
484 vec![Arc::new(array)],
485 )?))
486 }
487
488 fn reset_state(&self) -> Arc<RwLock<dyn LazyBatchGenerator>> {
489 Arc::new(RwLock::new(TestGenerator {
490 counter: 0,
491 max_batches: self.max_batches,
492 batch_size: self.batch_size,
493 schema: Arc::clone(&self.schema),
494 }))
495 }
496 }
497
498 #[tokio::test]
499 async fn test_lazy_memory_exec() -> Result<()> {
500 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int64, false)]));
501 let generator = TestGenerator {
502 counter: 0,
503 max_batches: 3,
504 batch_size: 2,
505 schema: Arc::clone(&schema),
506 };
507
508 let exec =
509 LazyMemoryExec::try_new(schema, vec![Arc::new(RwLock::new(generator))])?;
510
511 assert_eq!(exec.schema().fields().len(), 1);
513 assert_eq!(exec.schema().field(0).name(), "a");
514
515 let stream = exec.execute(0, Arc::new(TaskContext::default()))?;
517 let batches: Vec<_> = stream.collect::<Vec<_>>().await;
518
519 assert_eq!(batches.len(), 3);
520
521 let batch0 = batches[0].as_ref().unwrap();
523 let array0 = batch0
524 .column(0)
525 .as_any()
526 .downcast_ref::<Int64Array>()
527 .unwrap();
528 assert_eq!(array0.values(), &[0, 1]);
529
530 let batch1 = batches[1].as_ref().unwrap();
531 let array1 = batch1
532 .column(0)
533 .as_any()
534 .downcast_ref::<Int64Array>()
535 .unwrap();
536 assert_eq!(array1.values(), &[2, 3]);
537
538 let batch2 = batches[2].as_ref().unwrap();
539 let array2 = batch2
540 .column(0)
541 .as_any()
542 .downcast_ref::<Int64Array>()
543 .unwrap();
544 assert_eq!(array2.values(), &[4, 5]);
545
546 Ok(())
547 }
548
549 #[tokio::test]
552 async fn test_lazy_memory_exec_multiple_executions_are_independent() -> Result<()> {
553 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int64, false)]));
554 let generator = TestGenerator {
555 counter: 0,
556 max_batches: 3,
557 batch_size: 2,
558 schema: Arc::clone(&schema),
559 };
560
561 let exec =
562 LazyMemoryExec::try_new(schema, vec![Arc::new(RwLock::new(generator))])?;
563 let task_ctx = Arc::new(TaskContext::default());
564
565 let batches_1 = collect(exec.execute(0, Arc::clone(&task_ctx))?).await?;
567 let total_rows_1: usize = batches_1.iter().map(|b| b.num_rows()).sum();
568 assert_eq!(total_rows_1, 6);
569
570 let batches_2 = collect(exec.execute(0, Arc::clone(&task_ctx))?).await?;
573 let total_rows_2: usize = batches_2.iter().map(|b| b.num_rows()).sum();
574 assert_eq!(total_rows_2, 6);
575
576 for (b1, b2) in batches_1.iter().zip(batches_2.iter()) {
578 assert_eq!(b1, b2);
579 }
580
581 Ok(())
582 }
583
584 #[tokio::test]
585 async fn test_lazy_memory_exec_invalid_partition() -> Result<()> {
586 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int64, false)]));
587 let generator = TestGenerator {
588 counter: 0,
589 max_batches: 1,
590 batch_size: 1,
591 schema: Arc::clone(&schema),
592 };
593
594 let exec =
595 LazyMemoryExec::try_new(schema, vec![Arc::new(RwLock::new(generator))])?;
596
597 let result = exec.execute(1, Arc::new(TaskContext::default()));
599
600 assert!(matches!(
602 result,
603 Err(e) if e.to_string().contains("Invalid partition 1 for LazyMemoryExec with 1 partitions")
604 ));
605
606 Ok(())
607 }
608
609 #[tokio::test]
610 async fn test_generate_series_metrics_integration() -> Result<()> {
611 let test_cases = vec![
613 (10, 2, 10), (100, 10, 100), (5, 1, 5), ];
617
618 for (total_rows, batch_size, expected_rows) in test_cases {
619 let schema =
620 Arc::new(Schema::new(vec![Field::new("a", DataType::Int64, false)]));
621 let generator = TestGenerator {
622 counter: 0,
623 max_batches: (total_rows + batch_size - 1) / batch_size, batch_size: batch_size as usize,
625 schema: Arc::clone(&schema),
626 };
627
628 let exec =
629 LazyMemoryExec::try_new(schema, vec![Arc::new(RwLock::new(generator))])?;
630 let task_ctx = Arc::new(TaskContext::default());
631
632 let stream = exec.execute(0, task_ctx)?;
633 let batches = collect(stream).await?;
634
635 let metrics = exec.metrics().unwrap();
637
638 let actual_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
640 assert_eq!(actual_rows, expected_rows);
641
642 assert_eq!(metrics.output_rows().unwrap(), expected_rows);
644 assert!(metrics.elapsed_compute().unwrap() > 0);
645 }
646
647 Ok(())
648 }
649
650 #[tokio::test]
651 async fn test_lazy_memory_exec_reset_state() -> Result<()> {
652 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int64, false)]));
653 let generator = TestGenerator {
654 counter: 0,
655 max_batches: 3,
656 batch_size: 2,
657 schema: Arc::clone(&schema),
658 };
659
660 let exec = Arc::new(LazyMemoryExec::try_new(
661 schema,
662 vec![Arc::new(RwLock::new(generator))],
663 )?);
664 let stream = exec.execute(0, Arc::new(TaskContext::default()))?;
665 let batches = collect(stream).await?;
666
667 let exec_reset = exec.reset_state()?;
668 let stream = exec_reset.execute(0, Arc::new(TaskContext::default()))?;
669 let batches_reset = collect(stream).await?;
670
671 assert_eq!(batches, batches_reset);
673
674 Ok(())
675 }
676}