1use std::ops::Range;
5use std::pin::Pin;
6use std::sync::Arc;
7use std::task::Context;
8use std::task::Poll;
9use std::task::ready;
10
11use futures::Stream;
12use futures::StreamExt;
13use futures::future::BoxFuture;
14use futures::stream::BoxStream;
15use itertools::Itertools;
16use vortex_array::ArrayRef;
17use vortex_array::dtype::DType;
18use vortex_array::dtype::FieldMask;
19use vortex_array::expr::BoundExpression;
20use vortex_array::expr::analysis::referenced_field_paths;
21use vortex_array::iter::ArrayIterator;
22use vortex_array::iter::ArrayIteratorAdapter;
23use vortex_array::stats::StatsSet;
24use vortex_array::stream::ArrayStream;
25use vortex_array::stream::ArrayStreamAdapter;
26use vortex_error::VortexExpect;
27use vortex_error::VortexResult;
28use vortex_error::vortex_bail;
29use vortex_io::runtime::BlockingRuntime;
30use vortex_io::runtime::Handle;
31use vortex_io::runtime::Task;
32use vortex_io::session::RuntimeSessionExt;
33use vortex_metrics::MetricsRegistry;
34use vortex_scan::selection::Selection;
35use vortex_scan::strict_sorted_buffer::StrictSortedBuffer;
36use vortex_session::VortexSession;
37use vortex_utils::parallelism::get_available_parallelism;
38
39use crate::LayoutReader;
40use crate::LayoutReaderRef;
41use crate::layouts::row_idx::RowIdx;
42use crate::layouts::row_idx::RowIdxLayoutReader;
43use crate::scan::repeated_scan::RepeatedScan;
44use crate::scan::split_by::SplitBy;
45use crate::scan::splits::Splits;
46use crate::scan::splits::attempt_split_ranges;
47
48pub struct ScanBuilder<A> {
59 session: VortexSession,
60 layout_reader: LayoutReaderRef,
61 projection: BoundExpression,
62 filter: Option<BoundExpression>,
63 ordered: bool,
65 row_range: Option<Range<u64>>,
67 selection: Selection,
70 split_by: SplitBy,
72 natural_splits: Option<Arc<[u64]>>,
75 concurrency: usize,
77 map_fn: Arc<dyn Fn(ArrayRef) -> VortexResult<A> + Send + Sync>,
79 metrics_registry: Option<Arc<dyn MetricsRegistry>>,
80 file_stats: Option<Arc<[StatsSet]>>,
82 limit: Option<u64>,
84 row_offset: u64,
87}
88
89impl ScanBuilder<ArrayRef> {
90 pub fn new(session: VortexSession, layout_reader: Arc<dyn LayoutReader>) -> Self {
92 let projection = BoundExpression::new_root(layout_reader.dtype().clone());
93 Self {
94 session,
95 layout_reader,
96 projection,
97 filter: None,
98 ordered: true,
99 row_range: None,
100 selection: Default::default(),
101 split_by: SplitBy::default(),
102 natural_splits: None,
103 concurrency: 4,
106 map_fn: Arc::new(Ok),
107 metrics_registry: None,
108 file_stats: None,
109 limit: None,
110 row_offset: 0,
111 }
112 }
113
114 pub fn into_array_stream(self) -> VortexResult<impl ArrayStream + Send + 'static> {
118 let dtype = self.dtype()?;
119 let stream = self.into_stream()?;
120 Ok(ArrayStreamAdapter::new(dtype, stream))
121 }
122
123 pub fn into_array_iter<B: BlockingRuntime>(
125 self,
126 runtime: &B,
127 ) -> VortexResult<impl ArrayIterator + 'static> {
128 let stream = self.into_array_stream()?;
129 let dtype = stream.dtype().clone();
130 Ok(ArrayIteratorAdapter::new(
131 dtype,
132 runtime.block_on_stream(stream),
133 ))
134 }
135}
136
137impl<A: 'static + Send> ScanBuilder<A> {
138 pub fn with_filter(mut self, filter: BoundExpression) -> Self {
140 self.filter = Some(filter);
141 self
142 }
143
144 pub fn with_some_filter(mut self, filter: Option<BoundExpression>) -> Self {
146 self.filter = filter;
147 self
148 }
149
150 pub fn with_projection(mut self, projection: BoundExpression) -> Self {
152 self.projection = projection;
153 self
154 }
155
156 pub fn ordered(&self) -> bool {
158 self.ordered
159 }
160
161 pub fn with_ordered(mut self, ordered: bool) -> Self {
163 self.ordered = ordered;
164 self
165 }
166
167 pub fn with_row_range(mut self, row_range: Range<u64>) -> Self {
169 self.row_range = Some(row_range);
170 self
171 }
172
173 pub fn with_selection(mut self, selection: Selection) -> Self {
175 self.selection = selection;
176 self
177 }
178
179 pub fn with_row_indices(mut self, row_indices: StrictSortedBuffer<u64>) -> Self {
181 self.selection = Selection::IncludeByIndex(row_indices);
182 self
183 }
184
185 pub fn with_row_offset(mut self, row_offset: u64) -> Self {
187 self.row_offset = row_offset;
188 self
189 }
190
191 pub fn with_split_by(mut self, split_by: SplitBy) -> Self {
193 self.split_by = split_by;
194 self
195 }
196
197 pub fn with_natural_splits(mut self, boundaries: Arc<[u64]>) -> Self {
205 debug_assert!(
206 boundaries.windows(2).all(|w| w[0] < w[1]),
207 "natural split boundaries must be strictly increasing"
208 );
209 self.natural_splits = Some(boundaries);
210 self
211 }
212
213 pub fn full_file_splits(&self) -> VortexResult<Vec<u64>> {
220 let field_mask = referenced_field_masks(&self.projection, self.filter.as_ref())?;
221 self.split_by.splits(
222 self.layout_reader.as_ref(),
223 &(0..self.layout_reader.row_count()),
224 &field_mask,
225 )
226 }
227
228 pub fn concurrency(&self) -> usize {
230 self.concurrency
231 }
232
233 pub fn with_concurrency(mut self, concurrency: usize) -> Self {
236 assert!(concurrency > 0);
237 self.concurrency = concurrency;
238 self
239 }
240
241 pub fn with_some_metrics_registry(mut self, metrics: Option<Arc<dyn MetricsRegistry>>) -> Self {
243 self.metrics_registry = metrics;
244 self
245 }
246
247 pub fn with_metrics_registry(mut self, metrics: Arc<dyn MetricsRegistry>) -> Self {
249 self.metrics_registry = Some(metrics);
250 self
251 }
252
253 pub fn with_some_limit(mut self, limit: Option<u64>) -> Self {
255 self.limit = limit;
256 self
257 }
258
259 pub fn with_limit(mut self, limit: u64) -> Self {
261 self.limit = Some(limit);
262 self
263 }
264
265 pub fn dtype(&self) -> VortexResult<DType> {
267 Ok(self.projection.dtype().clone())
268 }
269
270 pub fn session(&self) -> &VortexSession {
272 &self.session
273 }
274
275 pub fn map<B: 'static>(
277 self,
278 map_fn: impl Fn(A) -> VortexResult<B> + 'static + Send + Sync,
279 ) -> ScanBuilder<B> {
280 let old_map_fn = self.map_fn;
281 ScanBuilder {
282 session: self.session,
283 layout_reader: self.layout_reader,
284 projection: self.projection,
285 filter: self.filter,
286 ordered: self.ordered,
287 row_range: self.row_range,
288 selection: self.selection,
289 split_by: self.split_by,
290 natural_splits: self.natural_splits,
291 concurrency: self.concurrency,
292 metrics_registry: self.metrics_registry,
293 file_stats: self.file_stats,
294 limit: self.limit,
295 row_offset: self.row_offset,
296 map_fn: Arc::new(move |a| old_map_fn(a).and_then(&map_fn)),
297 }
298 }
299
300 pub fn prepare(self) -> VortexResult<RepeatedScan<A>> {
302 let dtype = self.dtype()?;
303
304 if self.filter.is_some() && self.limit.is_some() {
305 vortex_bail!("Vortex doesn't support scans with both a filter and a limit")
306 }
307
308 let mut layout_reader = self.layout_reader;
311
312 let mut found_row_idx = self.projection.contains::<RowIdx>()?;
316 if !found_row_idx && let Some(filter) = self.filter.as_ref() {
317 found_row_idx = filter.contains::<RowIdx>()?;
318 }
319 if found_row_idx {
320 layout_reader = Arc::new(RowIdxLayoutReader::new(
321 self.row_offset,
322 layout_reader,
323 self.session.clone(),
324 ));
325 }
326
327 let bound_projection = self.projection;
328 let bound_filter = self.filter;
329
330 let splits =
332 if let Some(ranges) = attempt_split_ranges(&self.selection, self.row_range.as_ref()) {
333 Splits::Ranges(ranges)
334 } else if let Some(boundaries) = self.natural_splits {
335 Splits::Natural(boundaries)
337 } else {
338 let field_mask = referenced_field_masks(&bound_projection, bound_filter.as_ref())?;
339 let split_range = self
340 .row_range
341 .clone()
342 .unwrap_or_else(|| 0..layout_reader.row_count());
343 Splits::Natural(
344 self.split_by
345 .splits(layout_reader.as_ref(), &split_range, &field_mask)?
346 .into(),
347 )
348 };
349
350 Ok(RepeatedScan::new(
351 self.session.clone(),
352 layout_reader,
353 bound_projection,
354 bound_filter,
355 self.ordered,
356 self.row_range,
357 self.selection,
358 splits,
359 self.concurrency,
360 self.map_fn,
361 self.limit,
362 dtype,
363 ))
364 }
365
366 pub fn build(self) -> VortexResult<Vec<BoxFuture<'static, VortexResult<Option<A>>>>> {
368 if self.limit.is_some_and(|l| l == 0) {
370 return Ok(vec![]);
371 }
372
373 self.prepare()?.execute(None)
374 }
375
376 pub fn into_stream(
378 self,
379 ) -> VortexResult<impl Stream<Item = VortexResult<A>> + Send + 'static + use<A>> {
380 Ok(LazyScanStream::new(self))
381 }
382
383 pub fn into_iter<B: BlockingRuntime>(
385 self,
386 runtime: &B,
387 ) -> VortexResult<impl Iterator<Item = VortexResult<A>> + 'static> {
388 let stream = self.into_stream()?;
389 Ok(runtime.block_on_stream(stream))
390 }
391}
392
393enum LazyScanState<A: 'static + Send> {
394 Builder(Option<Box<ScanBuilder<A>>>),
395 Preparing(PreparingScan<A>),
396 Stream(BoxStream<'static, VortexResult<A>>),
397 Error(Option<vortex_error::VortexError>),
398}
399
400type PreparedScanTasks<A> = Vec<BoxFuture<'static, VortexResult<Option<A>>>>;
401
402struct PreparingScan<A: 'static + Send> {
403 ordered: bool,
404 concurrency: usize,
405 handle: Handle,
406 task: Task<VortexResult<PreparedScanTasks<A>>>,
407}
408
409struct LazyScanStream<A: 'static + Send> {
410 state: LazyScanState<A>,
411}
412
413impl<A: 'static + Send> LazyScanStream<A> {
414 fn new(builder: ScanBuilder<A>) -> Self {
415 Self {
416 state: LazyScanState::Builder(Some(Box::new(builder))),
417 }
418 }
419}
420
421impl<A: 'static + Send> Unpin for LazyScanStream<A> {}
422
423impl<A: 'static + Send> Stream for LazyScanStream<A> {
424 type Item = VortexResult<A>;
425
426 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
427 loop {
428 match &mut self.state {
429 LazyScanState::Builder(builder) => {
430 let builder = builder.take().vortex_expect("polled after completion");
431 let ordered = builder.ordered;
432 let num_workers = get_available_parallelism().unwrap_or(1);
433 let concurrency = builder.concurrency * num_workers;
434 let handle = builder.session.handle();
435 let task = handle
436 .spawn_cpu(move || builder.prepare().and_then(|scan| scan.execute(None)));
437 self.state = LazyScanState::Preparing(PreparingScan {
438 ordered,
439 concurrency,
440 handle,
441 task,
442 });
443 }
444 LazyScanState::Preparing(preparing) => {
445 match ready!(Pin::new(&mut preparing.task).poll(cx)) {
446 Ok(tasks) => {
447 let ordered = preparing.ordered;
448 let concurrency = preparing.concurrency;
449 let handle = preparing.handle.clone();
450 let stream =
451 futures::stream::iter(tasks).map(move |task| handle.spawn(task));
452 let stream = if ordered {
453 stream.buffered(concurrency).boxed()
454 } else {
455 stream.buffer_unordered(concurrency).boxed()
456 };
457 let stream = stream
458 .filter_map(|chunk| async move { chunk.transpose() })
459 .boxed();
460 self.state = LazyScanState::Stream(stream);
461 }
462 Err(err) => self.state = LazyScanState::Error(Some(err)),
463 }
464 }
465 LazyScanState::Stream(stream) => return stream.as_mut().poll_next(cx),
466 LazyScanState::Error(err) => return Poll::Ready(err.take().map(Err)),
467 }
468 }
469 }
470}
471
472pub fn referenced_field_masks(
476 projection: &BoundExpression,
477 filter: Option<&BoundExpression>,
478) -> VortexResult<Vec<FieldMask>> {
479 let mut field_paths = referenced_field_paths(projection)?;
480 if let Some(filter) = filter {
481 field_paths.extend(referenced_field_paths(filter)?);
482 }
483 Ok(field_paths
484 .into_iter()
485 .map(|path| {
486 if path.is_root() {
487 FieldMask::All
488 } else {
489 FieldMask::Prefix(path)
490 }
491 })
492 .collect_vec())
493}
494
495#[cfg(test)]
496mod test {
497 use std::ops::Range;
498 use std::pin::Pin;
499 use std::sync::Arc;
500 use std::sync::atomic::AtomicUsize;
501 use std::sync::atomic::Ordering;
502 use std::sync::mpsc;
503 use std::task::Context;
504 use std::task::Poll;
505 use std::time::Duration;
506
507 use futures::Stream;
508 use futures::task::noop_waker_ref;
509 use parking_lot::Mutex;
510 use vortex_array::IntoArray;
511 use vortex_array::MaskFuture;
512 use vortex_array::VortexSessionExecute;
513 use vortex_array::array_session;
514 use vortex_array::arrays::PrimitiveArray;
515 use vortex_array::dtype::DType;
516 use vortex_array::dtype::FieldMask;
517 use vortex_array::dtype::FieldPath;
518 use vortex_array::dtype::Nullability;
519 use vortex_array::dtype::PType;
520 use vortex_array::dtype::StructFields;
521 use vortex_array::expr::BoundExpression;
522 use vortex_array::expr::ExactBoundExpr;
523 use vortex_array::expr::eq;
524 use vortex_array::expr::get_item;
525 use vortex_array::expr::is_not_null;
526 use vortex_array::expr::lit;
527 use vortex_array::expr::root;
528 use vortex_error::VortexResult;
529 use vortex_error::vortex_bail;
530 use vortex_error::vortex_err;
531 use vortex_io::runtime::BlockingRuntime;
532 use vortex_io::runtime::single::SingleThreadRuntime;
533 use vortex_mask::Mask;
534
535 use super::ScanBuilder;
536 use super::referenced_field_masks;
537 use crate::ArrayFuture;
538 use crate::LayoutReader;
539 use crate::RowSplits;
540 use crate::SplitRange;
541 use crate::scan::test::SCAN_SESSION;
542 use crate::scan::test::session_with_handle;
543
544 fn nested_dtype() -> DType {
545 DType::Struct(
546 StructFields::from_iter([
547 (
548 "a",
549 DType::Struct(
550 StructFields::from_iter([
551 ("1", DType::Primitive(PType::I32, Nullability::NonNullable)),
552 ("2", DType::Primitive(PType::I32, Nullability::NonNullable)),
553 ]),
554 Nullability::NonNullable,
555 ),
556 ),
557 ("b", DType::Primitive(PType::I32, Nullability::NonNullable)),
558 ]),
559 Nullability::NonNullable,
560 )
561 }
562
563 #[test]
564 fn bound_setters_preserve_identity() -> VortexResult<()> {
565 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
566 let projection = eq(root(), lit(1_i32)).bind(&dtype)?;
567 let filter = eq(root(), lit(2_i32)).bind(&dtype)?;
568 let expected_projection = ExactBoundExpr(projection.clone());
569 let expected_filter = ExactBoundExpr(filter.clone());
570 let reader = Arc::new(CountingLayoutReader::new(Arc::new(AtomicUsize::new(0))));
571
572 let builder = ScanBuilder::new(SCAN_SESSION.clone(), reader)
573 .with_projection(projection)
574 .with_filter(filter);
575
576 assert_eq!(ExactBoundExpr(builder.projection), expected_projection);
577 assert_eq!(builder.filter.map(ExactBoundExpr), Some(expected_filter));
578 Ok(())
579 }
580
581 #[test]
582 fn root_projection_produces_all_mask() -> VortexResult<()> {
583 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
584 let projection = root().bind(&dtype)?;
585
586 assert_eq!(referenced_field_masks(&projection, None)?, [FieldMask::All]);
587 Ok(())
588 }
589
590 #[test]
591 fn nested_projection_preserves_field_path_in_split_mask() -> VortexResult<()> {
592 let dtype = nested_dtype();
593 let projection = get_item("1", get_item("a", root())).bind(&dtype)?;
594 let filter = eq(get_item("2", get_item("a", root())), lit(0_i32)).bind(&dtype)?;
595
596 let field_masks = referenced_field_masks(&projection, Some(&filter))?;
597
598 assert_eq!(field_masks.len(), 2);
599 assert!(field_masks.contains(&FieldMask::Prefix(FieldPath::from_name("a").push("1"))));
600 assert!(field_masks.contains(&FieldMask::Prefix(FieldPath::from_name("a").push("2"))));
601 Ok(())
602 }
603
604 #[test]
605 fn filter_path_covers_nested_projection_path() -> VortexResult<()> {
606 let dtype = nested_dtype();
607 let projection = get_item("1", get_item("a", root())).bind(&dtype)?;
608 let filter = is_not_null(get_item("a", root())).bind(&dtype)?;
609
610 let field_masks = referenced_field_masks(&projection, Some(&filter))?;
611
612 assert_eq!(field_masks, [FieldMask::Prefix(FieldPath::from_name("a"))]);
613 Ok(())
614 }
615
616 #[test]
617 fn parent_projection_path_covers_nested_filter_path() -> VortexResult<()> {
618 let dtype = nested_dtype();
619 let projection = get_item("a", root()).bind(&dtype)?;
620 let filter = is_not_null(get_item("1", get_item("a", root()))).bind(&dtype)?;
621
622 let field_masks = referenced_field_masks(&projection, Some(&filter))?;
623
624 assert_eq!(field_masks, [FieldMask::Prefix(FieldPath::from_name("a"))]);
625 Ok(())
626 }
627
628 #[derive(Debug)]
629 struct CountingLayoutReader {
630 name: Arc<str>,
631 dtype: DType,
632 row_count: u64,
633 register_splits_calls: Arc<AtomicUsize>,
634 }
635
636 impl CountingLayoutReader {
637 fn new(register_splits_calls: Arc<AtomicUsize>) -> Self {
638 Self {
639 name: Arc::from("counting"),
640 dtype: DType::Primitive(PType::I32, Nullability::NonNullable),
641 row_count: 1,
642 register_splits_calls,
643 }
644 }
645 }
646
647 impl LayoutReader for CountingLayoutReader {
648 fn name(&self) -> &Arc<str> {
649 &self.name
650 }
651
652 fn dtype(&self) -> &DType {
653 &self.dtype
654 }
655
656 fn row_count(&self) -> u64 {
657 self.row_count
658 }
659
660 fn register_splits(
661 &self,
662 _field_mask: &[FieldMask],
663 split_range: &SplitRange,
664 splits: &mut RowSplits,
665 ) -> VortexResult<()> {
666 self.register_splits_calls.fetch_add(1, Ordering::Relaxed);
667 splits.push(split_range.root_row_range().end);
668 Ok(())
669 }
670
671 fn pruning_evaluation(
672 &self,
673 _row_range: &Range<u64>,
674 _expr: &BoundExpression,
675 _mask: Mask,
676 ) -> VortexResult<MaskFuture> {
677 vortex_bail!("not needed for this test");
678 }
679
680 fn filter_evaluation(
681 &self,
682 _row_range: &Range<u64>,
683 _expr: &BoundExpression,
684 _mask: MaskFuture,
685 ) -> VortexResult<MaskFuture> {
686 vortex_bail!("not needed for this test");
687 }
688
689 fn projection_evaluation(
690 &self,
691 _row_range: &Range<u64>,
692 _expr: &BoundExpression,
693 _mask: MaskFuture,
694 ) -> VortexResult<ArrayFuture> {
695 Ok(Box::pin(async move {
696 unreachable!("scan should not be polled in this test")
697 }))
698 }
699
700 fn as_any(&self) -> &dyn std::any::Any {
701 self
702 }
703 }
704
705 #[test]
706 fn into_stream_is_lazy() {
707 let calls = Arc::new(AtomicUsize::new(0));
708 let reader = Arc::new(CountingLayoutReader::new(Arc::clone(&calls)));
709
710 let session = SCAN_SESSION.clone();
711
712 let _stream = ScanBuilder::new(session, reader).into_stream().unwrap();
713
714 assert_eq!(calls.load(Ordering::Relaxed), 0);
715 }
716
717 #[derive(Debug)]
718 struct SplittingLayoutReader {
719 name: Arc<str>,
720 dtype: DType,
721 row_count: u64,
722 register_splits_calls: Arc<AtomicUsize>,
723 }
724
725 impl SplittingLayoutReader {
726 fn new(register_splits_calls: Arc<AtomicUsize>) -> Self {
727 Self {
728 name: Arc::from("splitting"),
729 dtype: DType::Primitive(PType::I32, Nullability::NonNullable),
730 row_count: 4,
731 register_splits_calls,
732 }
733 }
734 }
735
736 impl LayoutReader for SplittingLayoutReader {
737 fn name(&self) -> &Arc<str> {
738 &self.name
739 }
740
741 fn dtype(&self) -> &DType {
742 &self.dtype
743 }
744
745 fn row_count(&self) -> u64 {
746 self.row_count
747 }
748
749 fn register_splits(
750 &self,
751 _field_mask: &[FieldMask],
752 split_range: &SplitRange,
753 splits: &mut RowSplits,
754 ) -> VortexResult<()> {
755 self.register_splits_calls.fetch_add(1, Ordering::Relaxed);
756 for split in (split_range.row_range().start + 1)..=split_range.row_range().end {
757 splits.push(split_range.row_offset() + split);
758 }
759 Ok(())
760 }
761
762 fn pruning_evaluation(
763 &self,
764 _row_range: &Range<u64>,
765 _expr: &BoundExpression,
766 mask: Mask,
767 ) -> VortexResult<MaskFuture> {
768 Ok(MaskFuture::ready(mask))
769 }
770
771 fn filter_evaluation(
772 &self,
773 _row_range: &Range<u64>,
774 _expr: &BoundExpression,
775 mask: MaskFuture,
776 ) -> VortexResult<MaskFuture> {
777 Ok(mask)
778 }
779
780 fn projection_evaluation(
781 &self,
782 row_range: &Range<u64>,
783 _expr: &BoundExpression,
784 _mask: MaskFuture,
785 ) -> VortexResult<ArrayFuture> {
786 let start = usize::try_from(row_range.start)
787 .map_err(|_| vortex_err!("row_range.start must fit in usize"))?;
788 let end = usize::try_from(row_range.end)
789 .map_err(|_| vortex_err!("row_range.end must fit in usize"))?;
790
791 let values: VortexResult<Vec<i32>> = (start..end)
792 .map(|v| i32::try_from(v).map_err(|_| vortex_err!("split value must fit in i32")))
793 .collect();
794
795 let array = PrimitiveArray::from_iter(values?).into_array();
796 Ok(Box::pin(async move { Ok(array) }))
797 }
798
799 fn as_any(&self) -> &dyn std::any::Any {
800 self
801 }
802 }
803
804 #[test]
805 fn into_stream_executes_after_prepare() -> VortexResult<()> {
806 let mut ctx = array_session().create_execution_ctx();
807 let calls = Arc::new(AtomicUsize::new(0));
808 let reader = Arc::new(SplittingLayoutReader::new(Arc::clone(&calls)));
809
810 let runtime = SingleThreadRuntime::default();
811 let session = session_with_handle(runtime.handle());
812
813 let stream = ScanBuilder::new(session, reader).into_stream()?;
814 let mut iter = runtime.block_on_stream(stream);
815
816 let mut values = Vec::new();
817 for chunk in &mut iter {
818 let prim = chunk?.execute::<PrimitiveArray>(&mut ctx)?;
819 values.push(prim.into_buffer::<i32>()[0]);
820 }
821
822 assert_eq!(calls.load(Ordering::Relaxed), 1);
823 assert_eq!(values.as_ref(), [0, 1, 2, 3]);
824
825 Ok(())
826 }
827
828 #[test]
829 fn supplied_natural_splits_skip_layout_walk() -> VortexResult<()> {
830 let mut ctx = array_session().create_execution_ctx();
831 let calls = Arc::new(AtomicUsize::new(0));
832 let reader = Arc::new(SplittingLayoutReader::new(Arc::clone(&calls)));
833
834 let runtime = SingleThreadRuntime::default();
835 let session = session_with_handle(runtime.handle());
836
837 let stream = ScanBuilder::new(session, reader)
838 .with_natural_splits(vec![0u64, 2, 4].into())
839 .with_row_range(1..4)
840 .into_stream()?;
841 let mut iter = runtime.block_on_stream(stream);
842
843 let mut chunks = Vec::new();
844 for chunk in &mut iter {
845 let prim = chunk?.execute::<PrimitiveArray>(&mut ctx)?;
846 chunks.push(prim.into_buffer::<i32>().to_vec());
847 }
848
849 assert_eq!(calls.load(Ordering::Relaxed), 0);
850 assert_eq!(chunks, [vec![1], vec![2, 3]]);
852
853 Ok(())
854 }
855
856 #[test]
857 fn full_file_splits_ignore_row_range() -> VortexResult<()> {
858 let calls = Arc::new(AtomicUsize::new(0));
859 let reader = Arc::new(SplittingLayoutReader::new(Arc::clone(&calls)));
860
861 let splits = ScanBuilder::new(SCAN_SESSION.clone(), reader)
862 .with_row_range(1..3)
863 .full_file_splits()?;
864
865 assert_eq!(splits, [0, 1, 2, 3, 4]);
866 Ok(())
867 }
868
869 #[derive(Debug)]
870 struct BlockingSplitsLayoutReader {
871 name: Arc<str>,
872 dtype: DType,
873 row_count: u64,
874 register_splits_calls: Arc<AtomicUsize>,
875 gate: Arc<Mutex<()>>,
876 }
877
878 impl BlockingSplitsLayoutReader {
879 fn new(gate: Arc<Mutex<()>>, register_splits_calls: Arc<AtomicUsize>) -> Self {
880 Self {
881 name: Arc::from("blocking-splits"),
882 dtype: DType::Primitive(PType::I32, Nullability::NonNullable),
883 row_count: 1,
884 register_splits_calls,
885 gate,
886 }
887 }
888 }
889
890 impl LayoutReader for BlockingSplitsLayoutReader {
891 fn name(&self) -> &Arc<str> {
892 &self.name
893 }
894
895 fn dtype(&self) -> &DType {
896 &self.dtype
897 }
898
899 fn row_count(&self) -> u64 {
900 self.row_count
901 }
902
903 fn register_splits(
904 &self,
905 _field_mask: &[FieldMask],
906 split_range: &SplitRange,
907 splits: &mut RowSplits,
908 ) -> VortexResult<()> {
909 self.register_splits_calls.fetch_add(1, Ordering::Relaxed);
910 let _guard = self.gate.lock();
911 splits.push(split_range.root_row_range().end);
912 Ok(())
913 }
914
915 fn pruning_evaluation(
916 &self,
917 _row_range: &Range<u64>,
918 _expr: &BoundExpression,
919 _mask: Mask,
920 ) -> VortexResult<MaskFuture> {
921 vortex_bail!("not needed for this test");
922 }
923
924 fn filter_evaluation(
925 &self,
926 _row_range: &Range<u64>,
927 _expr: &BoundExpression,
928 _mask: MaskFuture,
929 ) -> VortexResult<MaskFuture> {
930 vortex_bail!("not needed for this test");
931 }
932
933 fn projection_evaluation(
934 &self,
935 _row_range: &Range<u64>,
936 _expr: &BoundExpression,
937 _mask: MaskFuture,
938 ) -> VortexResult<ArrayFuture> {
939 Ok(Box::pin(async move {
940 unreachable!("scan should not be polled in this test")
941 }))
942 }
943
944 fn as_any(&self) -> &dyn std::any::Any {
945 self
946 }
947 }
948
949 #[test]
950 fn into_stream_first_poll_does_not_block() {
951 let gate = Arc::new(Mutex::new(()));
952 let guard = gate.lock();
953
954 let calls = Arc::new(AtomicUsize::new(0));
955 let reader = Arc::new(BlockingSplitsLayoutReader::new(
956 Arc::clone(&gate),
957 Arc::clone(&calls),
958 ));
959
960 let runtime = SingleThreadRuntime::default();
961 let session = session_with_handle(runtime.handle());
962
963 let mut stream = ScanBuilder::new(session, reader).into_stream().unwrap();
964
965 let (send, recv) = mpsc::channel::<bool>();
966 let join = std::thread::spawn(move || {
967 let waker = noop_waker_ref();
968 let mut cx = Context::from_waker(waker);
969 let poll = Pin::new(&mut stream).poll_next(&mut cx);
970 let _ = send.send(matches!(poll, Poll::Pending));
971 });
972
973 let polled_pending = recv.recv_timeout(Duration::from_secs(1)).ok();
974
975 drop(guard);
977 drop(join.join());
978
979 let polled_pending = polled_pending.expect("poll_next blocked; expected quick return");
980 assert!(
981 polled_pending,
982 "expected Poll::Pending while prepare is blocked"
983 );
984 assert_eq!(calls.load(Ordering::Relaxed), 0);
985
986 drop(runtime);
987 }
988
989 #[test]
990 fn into_stream_with_row_range() -> VortexResult<()> {
991 let mut ctx = array_session().create_execution_ctx();
992 let calls = Arc::new(AtomicUsize::new(0));
993 let reader = Arc::new(SplittingLayoutReader::new(Arc::clone(&calls)));
994
995 let runtime = SingleThreadRuntime::default();
996 let session = session_with_handle(runtime.handle());
997
998 let stream = ScanBuilder::new(session, reader)
999 .with_row_range(1..3)
1000 .into_stream()?;
1001 let mut iter = runtime.block_on_stream(stream);
1002
1003 let mut values = Vec::new();
1004 for chunk in &mut iter {
1005 let prim = chunk?.execute::<PrimitiveArray>(&mut ctx)?;
1006 values.extend(prim.into_buffer::<i32>().iter().copied());
1007 }
1008
1009 assert_eq!(calls.load(Ordering::Relaxed), 1);
1010 assert_eq!(values.as_ref(), [1, 2]);
1011
1012 Ok(())
1013 }
1014}