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::future::BoxFuture;
13use futures::stream::BoxStream;
14use itertools::Itertools;
15use vortex_array::ArrayRef;
16use vortex_array::dtype::DType;
17use vortex_array::dtype::FieldMask;
18use vortex_array::expr::BoundExpression;
19use vortex_array::expr::analysis::referenced_field_paths;
20use vortex_array::expr::transform::find_between;
21use vortex_array::iter::ArrayIterator;
22use vortex_array::iter::ArrayIteratorAdapter;
23use vortex_array::stream::ArrayStream;
24use vortex_array::stream::ArrayStreamAdapter;
25use vortex_error::VortexExpect;
26use vortex_error::VortexResult;
27use vortex_io::runtime::BlockingRuntime;
28use vortex_io::runtime::Task;
29use vortex_io::session::RuntimeSessionExt;
30use vortex_metrics::MetricsRegistry;
31use vortex_scan::selection::Selection;
32use vortex_scan::strict_sorted_buffer::StrictSortedBuffer;
33use vortex_session::VortexSession;
34
35use crate::LayoutReader;
36use crate::LayoutReaderRef;
37use crate::layouts::row_idx::RowIdx;
38use crate::layouts::row_idx::RowIdxLayoutReader;
39use crate::scan::limit::RowLimit;
40use crate::scan::limit::ScanLimit;
41use crate::scan::repeated_scan::RepeatedScan;
42use crate::scan::split_by::SplitBy;
43use crate::scan::splits::Splits;
44use crate::scan::splits::attempt_split_ranges;
45
46pub struct ScanBuilder<A> {
57 session: VortexSession,
58 layout_reader: LayoutReaderRef,
59 projection: BoundExpression,
60 filter: Option<BoundExpression>,
61 ordered: bool,
63 row_range: Option<Range<u64>>,
65 selection: Selection,
68 split_by: SplitBy,
70 natural_splits: Option<Arc<[u64]>>,
73 concurrency: usize,
75 map_fn: Arc<dyn Fn(ArrayRef) -> VortexResult<A> + Send + Sync>,
77 metrics_registry: Option<Arc<dyn MetricsRegistry>>,
78 limit: Option<ScanLimit>,
80 row_offset: u64,
83}
84
85impl ScanBuilder<ArrayRef> {
86 pub fn new(session: VortexSession, layout_reader: Arc<dyn LayoutReader>) -> Self {
88 let projection = BoundExpression::new_root(layout_reader.dtype().clone());
89 Self {
90 session,
91 layout_reader,
92 projection,
93 filter: None,
94 ordered: true,
95 row_range: None,
96 selection: Default::default(),
97 split_by: SplitBy::default(),
98 natural_splits: None,
99 concurrency: 4,
102 map_fn: Arc::new(Ok),
103 metrics_registry: None,
104 limit: None,
105 row_offset: 0,
106 }
107 }
108
109 pub fn into_array_stream(self) -> VortexResult<impl ArrayStream + Send + 'static> {
113 let dtype = self.dtype()?;
114 let stream = self.into_stream()?;
115 Ok(ArrayStreamAdapter::new(dtype, stream))
116 }
117
118 pub fn into_array_iter<B: BlockingRuntime>(
120 self,
121 runtime: &B,
122 ) -> VortexResult<impl ArrayIterator + 'static> {
123 let stream = self.into_array_stream()?;
124 let dtype = stream.dtype().clone();
125 Ok(ArrayIteratorAdapter::new(
126 dtype,
127 runtime.block_on_stream(stream),
128 ))
129 }
130}
131
132impl<A: 'static + Send> ScanBuilder<A> {
133 pub fn with_filter(mut self, filter: BoundExpression) -> Self {
135 self.filter = Some(filter);
136 self
137 }
138
139 pub fn with_some_filter(mut self, filter: Option<BoundExpression>) -> Self {
141 self.filter = filter;
142 self
143 }
144
145 pub fn with_projection(mut self, projection: BoundExpression) -> Self {
147 self.projection = projection;
148 self
149 }
150
151 pub fn ordered(&self) -> bool {
153 self.ordered
154 }
155
156 pub fn with_ordered(mut self, ordered: bool) -> Self {
158 self.ordered = ordered;
159 self
160 }
161
162 pub fn with_row_range(mut self, row_range: Range<u64>) -> Self {
164 self.row_range = Some(row_range);
165 self
166 }
167
168 pub fn with_selection(mut self, selection: Selection) -> Self {
170 self.selection = selection;
171 self
172 }
173
174 pub fn with_row_indices(mut self, row_indices: StrictSortedBuffer<u64>) -> Self {
176 self.selection = Selection::IncludeByIndex(row_indices);
177 self
178 }
179
180 pub fn with_row_offset(mut self, row_offset: u64) -> Self {
182 self.row_offset = row_offset;
183 self
184 }
185
186 pub fn with_split_by(mut self, split_by: SplitBy) -> Self {
188 self.split_by = split_by;
189 self
190 }
191
192 pub fn with_natural_splits(mut self, boundaries: Arc<[u64]>) -> Self {
200 debug_assert!(
201 boundaries.windows(2).all(|w| w[0] < w[1]),
202 "natural split boundaries must be strictly increasing"
203 );
204 self.natural_splits = Some(boundaries);
205 self
206 }
207
208 pub fn full_file_splits(&self) -> VortexResult<Vec<u64>> {
215 let field_mask = referenced_field_masks(&self.projection, self.filter.as_ref())?;
216 self.split_by.splits(
217 self.layout_reader.as_ref(),
218 &(0..self.layout_reader.row_count()),
219 &field_mask,
220 )
221 }
222
223 pub fn concurrency(&self) -> usize {
225 self.concurrency
226 }
227
228 pub fn with_concurrency(mut self, concurrency: usize) -> Self {
231 assert!(concurrency > 0);
232 self.concurrency = concurrency;
233 self
234 }
235
236 pub fn with_some_metrics_registry(mut self, metrics: Option<Arc<dyn MetricsRegistry>>) -> Self {
238 self.metrics_registry = metrics;
239 self
240 }
241
242 pub fn with_metrics_registry(mut self, metrics: Arc<dyn MetricsRegistry>) -> Self {
244 self.metrics_registry = Some(metrics);
245 self
246 }
247
248 pub fn with_some_limit(mut self, limit: Option<u64>) -> Self {
252 self.limit = limit.map(ScanLimit::PerExecution);
253 self
254 }
255
256 pub fn with_limit(self, limit: u64) -> Self {
260 self.with_some_limit(Some(limit))
261 }
262
263 pub(crate) fn with_shared_limit(mut self, limit: RowLimit) -> Self {
265 self.limit = Some(ScanLimit::Shared(limit));
266 self
267 }
268
269 pub fn dtype(&self) -> VortexResult<DType> {
271 Ok(self.projection.dtype().clone())
272 }
273
274 pub fn session(&self) -> &VortexSession {
276 &self.session
277 }
278
279 pub fn map<B: 'static>(
281 self,
282 map_fn: impl Fn(A) -> VortexResult<B> + 'static + Send + Sync,
283 ) -> ScanBuilder<B> {
284 let old_map_fn = self.map_fn;
285 ScanBuilder {
286 session: self.session,
287 layout_reader: self.layout_reader,
288 projection: self.projection,
289 filter: self.filter,
290 ordered: self.ordered,
291 row_range: self.row_range,
292 selection: self.selection,
293 split_by: self.split_by,
294 natural_splits: self.natural_splits,
295 concurrency: self.concurrency,
296 metrics_registry: self.metrics_registry,
297 limit: self.limit,
298 row_offset: self.row_offset,
299 map_fn: Arc::new(move |a| old_map_fn(a).and_then(&map_fn)),
300 }
301 }
302
303 pub fn prepare(self) -> VortexResult<RepeatedScan<A>> {
306 let dtype = self.dtype()?;
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.map(find_between).transpose()?;
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>>>>> {
371 self.prepare()?.execute(None)
372 }
373
374 pub fn into_stream(
379 self,
380 ) -> VortexResult<impl Stream<Item = VortexResult<A>> + Send + 'static + use<A>> {
381 Ok(LazyScanStream::new(self))
382 }
383
384 pub fn into_iter<B: BlockingRuntime>(
386 self,
387 runtime: &B,
388 ) -> VortexResult<impl Iterator<Item = VortexResult<A>> + 'static> {
389 let stream = self.into_stream()?;
390 Ok(runtime.block_on_stream(stream))
391 }
392}
393
394enum LazyScanState<A: 'static + Send> {
395 Builder(Option<Box<ScanBuilder<A>>>),
396 Preparing(Task<VortexResult<BoxStream<'static, VortexResult<A>>>>),
397 Stream(BoxStream<'static, VortexResult<A>>),
398 Error(Option<vortex_error::VortexError>),
399}
400
401struct LazyScanStream<A: 'static + Send> {
402 state: LazyScanState<A>,
403}
404
405impl<A: 'static + Send> LazyScanStream<A> {
406 fn new(builder: ScanBuilder<A>) -> Self {
407 Self {
408 state: LazyScanState::Builder(Some(Box::new(builder))),
409 }
410 }
411}
412
413impl<A: 'static + Send> Unpin for LazyScanStream<A> {}
414
415impl<A: 'static + Send> Stream for LazyScanStream<A> {
416 type Item = VortexResult<A>;
417
418 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
419 loop {
420 match &mut self.state {
421 LazyScanState::Builder(builder) => {
422 let builder = builder.take().vortex_expect("polled after completion");
423 let handle = builder.session.handle();
424 let task = handle.spawn_cpu(move || builder.prepare()?.execute_stream(None));
430 self.state = LazyScanState::Preparing(task);
431 }
432 LazyScanState::Preparing(task) => match ready!(Pin::new(task).poll(cx)) {
433 Ok(stream) => self.state = LazyScanState::Stream(stream),
434 Err(err) => self.state = LazyScanState::Error(Some(err)),
435 },
436 LazyScanState::Stream(stream) => return stream.as_mut().poll_next(cx),
437 LazyScanState::Error(err) => return Poll::Ready(err.take().map(Err)),
438 }
439 }
440 }
441}
442
443pub fn referenced_field_masks(
447 projection: &BoundExpression,
448 filter: Option<&BoundExpression>,
449) -> VortexResult<Vec<FieldMask>> {
450 let mut field_paths = referenced_field_paths(projection)?;
451 if let Some(filter) = filter {
452 field_paths.extend(referenced_field_paths(filter)?);
453 }
454 Ok(field_paths
455 .into_iter()
456 .map(|path| {
457 if path.is_root() {
458 FieldMask::All
459 } else {
460 FieldMask::Prefix(path)
461 }
462 })
463 .collect_vec())
464}
465
466#[cfg(test)]
467mod test {
468 use std::ops::Range;
469 use std::pin::Pin;
470 use std::sync::Arc;
471 use std::sync::atomic::AtomicUsize;
472 use std::sync::atomic::Ordering;
473 use std::sync::mpsc;
474 use std::task::Context;
475 use std::task::Poll;
476 use std::time::Duration;
477
478 use futures::Stream;
479 use futures::StreamExt;
480 use futures::task::noop_waker_ref;
481 use parking_lot::Mutex;
482 use rstest::rstest;
483 use vortex_array::IntoArray;
484 use vortex_array::MaskFuture;
485 use vortex_array::VortexSessionExecute;
486 use vortex_array::array_session;
487 use vortex_array::arrays::PrimitiveArray;
488 use vortex_array::dtype::DType;
489 use vortex_array::dtype::FieldMask;
490 use vortex_array::dtype::FieldPath;
491 use vortex_array::dtype::Nullability;
492 use vortex_array::dtype::PType;
493 use vortex_array::dtype::StructFields;
494 use vortex_array::expr::BoundExpression;
495 use vortex_array::expr::ExactBoundExpr;
496 use vortex_array::expr::and;
497 use vortex_array::expr::between;
498 use vortex_array::expr::col;
499 use vortex_array::expr::eq;
500 use vortex_array::expr::get_item;
501 use vortex_array::expr::gt_eq;
502 use vortex_array::expr::is_not_null;
503 use vortex_array::expr::lit;
504 use vortex_array::expr::lt;
505 use vortex_array::expr::root;
506 use vortex_array::scalar_fn::fns::between::BetweenOptions;
507 use vortex_array::scalar_fn::fns::between::StrictComparison;
508 use vortex_error::VortexResult;
509 use vortex_error::vortex_bail;
510 use vortex_error::vortex_err;
511 use vortex_io::runtime::BlockingRuntime;
512 use vortex_io::runtime::single::SingleThreadRuntime;
513 use vortex_io::runtime::tokio::TokioRuntime;
514 use vortex_mask::Mask;
515
516 use super::ScanBuilder;
517 use super::referenced_field_masks;
518 use crate::ArrayFuture;
519 use crate::LayoutReader;
520 use crate::RowSplits;
521 use crate::SplitRange;
522 use crate::scan::limit::RowLimit;
523 use crate::scan::test::SCAN_SESSION;
524 use crate::scan::test::TestLayoutReader;
525 use crate::scan::test::collect_scan_values;
526 use crate::scan::test::drain_runtime;
527 use crate::scan::test::keep_all;
528 use crate::scan::test::keep_odd;
529 use crate::scan::test::session_with_handle;
530
531 fn nested_dtype() -> DType {
532 DType::Struct(
533 StructFields::from_iter([
534 (
535 "a",
536 DType::Struct(
537 StructFields::from_iter([
538 ("1", DType::Primitive(PType::I32, Nullability::NonNullable)),
539 ("2", DType::Primitive(PType::I32, Nullability::NonNullable)),
540 ]),
541 Nullability::NonNullable,
542 ),
543 ),
544 ("b", DType::Primitive(PType::I32, Nullability::NonNullable)),
545 ]),
546 Nullability::NonNullable,
547 )
548 }
549
550 #[test]
551 fn bound_setters_preserve_identity() -> VortexResult<()> {
552 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
553 let projection = eq(root(), lit(1_i32)).bind(&dtype)?;
554 let filter = eq(root(), lit(2_i32)).bind(&dtype)?;
555 let expected_projection = ExactBoundExpr(projection.clone());
556 let expected_filter = ExactBoundExpr(filter.clone());
557 let reader = Arc::new(CountingLayoutReader::new(Arc::new(AtomicUsize::new(0))));
558
559 let builder = ScanBuilder::new(SCAN_SESSION.clone(), reader)
560 .with_projection(projection)
561 .with_filter(filter);
562
563 assert_eq!(ExactBoundExpr(builder.projection), expected_projection);
564 assert_eq!(builder.filter.map(ExactBoundExpr), Some(expected_filter));
565 Ok(())
566 }
567
568 #[test]
569 fn root_projection_produces_all_mask() -> VortexResult<()> {
570 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
571 let projection = root().bind(&dtype)?;
572
573 assert_eq!(referenced_field_masks(&projection, None)?, [FieldMask::All]);
574 Ok(())
575 }
576
577 #[test]
578 fn nested_projection_preserves_field_path_in_split_mask() -> VortexResult<()> {
579 let dtype = nested_dtype();
580 let projection = get_item("1", get_item("a", root())).bind(&dtype)?;
581 let filter = eq(get_item("2", get_item("a", root())), lit(0_i32)).bind(&dtype)?;
582
583 let field_masks = referenced_field_masks(&projection, Some(&filter))?;
584
585 assert_eq!(field_masks.len(), 2);
586 assert!(field_masks.contains(&FieldMask::Prefix(FieldPath::from_name("a").push("1"))));
587 assert!(field_masks.contains(&FieldMask::Prefix(FieldPath::from_name("a").push("2"))));
588 Ok(())
589 }
590
591 #[test]
592 fn filter_path_covers_nested_projection_path() -> VortexResult<()> {
593 let dtype = nested_dtype();
594 let projection = get_item("1", get_item("a", root())).bind(&dtype)?;
595 let filter = is_not_null(get_item("a", root())).bind(&dtype)?;
596
597 let field_masks = referenced_field_masks(&projection, Some(&filter))?;
598
599 assert_eq!(field_masks, [FieldMask::Prefix(FieldPath::from_name("a"))]);
600 Ok(())
601 }
602
603 #[test]
604 fn parent_projection_path_covers_nested_filter_path() -> VortexResult<()> {
605 let dtype = nested_dtype();
606 let projection = get_item("a", root()).bind(&dtype)?;
607 let filter = is_not_null(get_item("1", get_item("a", root()))).bind(&dtype)?;
608
609 let field_masks = referenced_field_masks(&projection, Some(&filter))?;
610
611 assert_eq!(field_masks, [FieldMask::Prefix(FieldPath::from_name("a"))]);
612 Ok(())
613 }
614
615 #[derive(Debug)]
616 struct CountingLayoutReader {
617 name: Arc<str>,
618 dtype: DType,
619 row_count: u64,
620 register_splits_calls: Arc<AtomicUsize>,
621 }
622
623 impl CountingLayoutReader {
624 fn new(register_splits_calls: Arc<AtomicUsize>) -> Self {
625 Self {
626 name: Arc::from("counting"),
627 dtype: DType::Primitive(PType::I32, Nullability::NonNullable),
628 row_count: 1,
629 register_splits_calls,
630 }
631 }
632 }
633
634 impl LayoutReader for CountingLayoutReader {
635 fn name(&self) -> &Arc<str> {
636 &self.name
637 }
638
639 fn dtype(&self) -> &DType {
640 &self.dtype
641 }
642
643 fn row_count(&self) -> u64 {
644 self.row_count
645 }
646
647 fn register_splits(
648 &self,
649 _field_mask: &[FieldMask],
650 split_range: &SplitRange,
651 splits: &mut RowSplits,
652 ) -> VortexResult<()> {
653 self.register_splits_calls.fetch_add(1, Ordering::Relaxed);
654 splits.push(split_range.root_row_range().end);
655 Ok(())
656 }
657
658 fn pruning_evaluation(
659 &self,
660 _row_range: &Range<u64>,
661 _expr: &BoundExpression,
662 _mask: Mask,
663 ) -> VortexResult<MaskFuture> {
664 vortex_bail!("not needed for this test");
665 }
666
667 fn filter_evaluation(
668 &self,
669 _row_range: &Range<u64>,
670 _expr: &BoundExpression,
671 _mask: MaskFuture,
672 ) -> VortexResult<MaskFuture> {
673 vortex_bail!("not needed for this test");
674 }
675
676 fn projection_evaluation(
677 &self,
678 _row_range: &Range<u64>,
679 _expr: &BoundExpression,
680 _mask: MaskFuture,
681 ) -> VortexResult<ArrayFuture> {
682 Ok(Box::pin(async move {
683 unreachable!("scan should not be polled in this test")
684 }))
685 }
686
687 fn as_any(&self) -> &dyn std::any::Any {
688 self
689 }
690 }
691
692 #[test]
693 fn into_stream_is_lazy() {
694 let calls = Arc::new(AtomicUsize::new(0));
695 let reader = Arc::new(CountingLayoutReader::new(Arc::clone(&calls)));
696
697 let session = SCAN_SESSION.clone();
698
699 let _stream = ScanBuilder::new(session, reader).into_stream().unwrap();
700
701 assert_eq!(calls.load(Ordering::Relaxed), 0);
702 }
703
704 #[derive(Debug)]
705 struct SplittingLayoutReader {
706 name: Arc<str>,
707 dtype: DType,
708 row_count: u64,
709 register_splits_calls: Arc<AtomicUsize>,
710 blocking_projection: Option<BlockingProjection>,
711 }
712
713 #[derive(Debug)]
714 struct BlockingProjection {
715 started: mpsc::Sender<()>,
716 gate: Arc<Mutex<()>>,
717 }
718
719 impl SplittingLayoutReader {
720 fn new(register_splits_calls: Arc<AtomicUsize>) -> Self {
721 Self {
722 name: Arc::from("splitting"),
723 dtype: DType::Primitive(PType::I32, Nullability::NonNullable),
724 row_count: 4,
725 register_splits_calls,
726 blocking_projection: None,
727 }
728 }
729
730 fn with_blocking_projection(
731 register_splits_calls: Arc<AtomicUsize>,
732 gate: Arc<Mutex<()>>,
733 started: mpsc::Sender<()>,
734 ) -> Self {
735 let mut reader = Self::new(register_splits_calls);
736 reader.blocking_projection = Some(BlockingProjection { started, gate });
737 reader
738 }
739 }
740
741 impl LayoutReader for SplittingLayoutReader {
742 fn name(&self) -> &Arc<str> {
743 &self.name
744 }
745
746 fn dtype(&self) -> &DType {
747 &self.dtype
748 }
749
750 fn row_count(&self) -> u64 {
751 self.row_count
752 }
753
754 fn register_splits(
755 &self,
756 _field_mask: &[FieldMask],
757 split_range: &SplitRange,
758 splits: &mut RowSplits,
759 ) -> VortexResult<()> {
760 self.register_splits_calls.fetch_add(1, Ordering::Relaxed);
761 for split in (split_range.row_range().start + 1)..=split_range.row_range().end {
762 splits.push(split_range.row_offset() + split);
763 }
764 Ok(())
765 }
766
767 fn pruning_evaluation(
768 &self,
769 _row_range: &Range<u64>,
770 _expr: &BoundExpression,
771 mask: Mask,
772 ) -> VortexResult<MaskFuture> {
773 Ok(MaskFuture::ready(mask))
774 }
775
776 fn filter_evaluation(
777 &self,
778 _row_range: &Range<u64>,
779 _expr: &BoundExpression,
780 mask: MaskFuture,
781 ) -> VortexResult<MaskFuture> {
782 Ok(mask)
783 }
784
785 fn projection_evaluation(
786 &self,
787 row_range: &Range<u64>,
788 _expr: &BoundExpression,
789 _mask: MaskFuture,
790 ) -> VortexResult<ArrayFuture> {
791 if let Some(blocking_projection) = &self.blocking_projection {
792 blocking_projection
793 .started
794 .send(())
795 .map_err(|_| vortex_err!("test projection-start receiver dropped"))?;
796 let _guard = blocking_projection.gate.lock();
797 }
798
799 let start = usize::try_from(row_range.start)
800 .map_err(|_| vortex_err!("row_range.start must fit in usize"))?;
801 let end = usize::try_from(row_range.end)
802 .map_err(|_| vortex_err!("row_range.end must fit in usize"))?;
803
804 let values: VortexResult<Vec<i32>> = (start..end)
805 .map(|v| i32::try_from(v).map_err(|_| vortex_err!("split value must fit in i32")))
806 .collect();
807
808 let array = PrimitiveArray::from_iter(values?).into_array();
809 Ok(Box::pin(async move { Ok(array) }))
810 }
811
812 fn as_any(&self) -> &dyn std::any::Any {
813 self
814 }
815 }
816
817 #[test]
818 fn into_stream_executes_after_prepare() -> VortexResult<()> {
819 let mut ctx = array_session().create_execution_ctx();
820 let calls = Arc::new(AtomicUsize::new(0));
821 let reader = Arc::new(SplittingLayoutReader::new(Arc::clone(&calls)));
822
823 let runtime = SingleThreadRuntime::default();
824 let session = session_with_handle(runtime.handle());
825
826 let stream = ScanBuilder::new(session, reader).into_stream()?;
827 let mut iter = runtime.block_on_stream(stream);
828
829 let mut values = Vec::new();
830 for chunk in &mut iter {
831 let prim = chunk?.execute::<PrimitiveArray>(&mut ctx)?;
832 values.extend(prim.into_buffer::<i32>().iter().copied());
833 }
834
835 assert_eq!(calls.load(Ordering::Relaxed), 1);
836 assert_eq!(values.as_ref(), [0, 1, 2, 3]);
837
838 Ok(())
839 }
840
841 #[test]
842 fn supplied_natural_splits_skip_layout_walk() -> VortexResult<()> {
843 let mut ctx = array_session().create_execution_ctx();
844 let calls = Arc::new(AtomicUsize::new(0));
845 let reader = Arc::new(SplittingLayoutReader::new(Arc::clone(&calls)));
846
847 let runtime = SingleThreadRuntime::default();
848 let session = session_with_handle(runtime.handle());
849
850 let stream = ScanBuilder::new(session, reader)
851 .with_natural_splits(vec![0u64, 2, 4].into())
852 .with_row_range(1..4)
853 .into_stream()?;
854 let mut iter = runtime.block_on_stream(stream);
855
856 let mut chunks = Vec::new();
857 for chunk in &mut iter {
858 let prim = chunk?.execute::<PrimitiveArray>(&mut ctx)?;
859 chunks.push(prim.into_buffer::<i32>().to_vec());
860 }
861
862 assert_eq!(calls.load(Ordering::Relaxed), 0);
863 assert_eq!(chunks, [vec![1], vec![2, 3]]);
865
866 Ok(())
867 }
868
869 #[rstest]
870 #[case::eager(false)]
871 #[case::filtered_limit(true)]
872 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
873 async fn scan_constructs_tasks_off_the_poller(#[case] filtered: bool) -> VortexResult<()> {
874 let gate = Arc::new(Mutex::new(()));
875 let guard = gate.lock();
876 let calls = Arc::new(AtomicUsize::new(0));
877 let (started_send, started_recv) = mpsc::channel();
878 let reader = Arc::new(SplittingLayoutReader::with_blocking_projection(
879 Arc::clone(&calls),
880 Arc::clone(&gate),
881 started_send,
882 ));
883
884 let runtime = TokioRuntime::new(tokio::runtime::Handle::current());
885 let session = session_with_handle(runtime.handle());
886 let filter = is_not_null(root()).bind(reader.dtype())?;
887 let builder = ScanBuilder::new(session, reader);
888 let mut stream = if filtered {
889 builder
891 .with_filter(filter)
892 .with_limit(1)
893 .prepare()?
894 .execute_stream(None)?
895 } else {
896 builder.into_stream()?.boxed()
897 };
898
899 let (poll_send, poll_recv) = mpsc::channel();
900 let (release_send, release_recv) = mpsc::channel();
901 let join = std::thread::spawn(move || {
902 let waker = noop_waker_ref();
903 let mut cx = Context::from_waker(waker);
904 let poll = Pin::new(&mut stream).poll_next(&mut cx);
905 let _ = poll_send.send(matches!(poll, Poll::Pending));
906 let _ = release_recv.recv();
907 });
908
909 let poll_result = poll_recv.recv_timeout(Duration::from_secs(1));
910 let projection_started = started_recv.recv_timeout(Duration::from_secs(1));
911
912 drop(guard);
914 let _ = release_send.send(());
915 drop(join.join());
916
917 assert!(
918 poll_result.is_ok_and(|poll_pending| poll_pending),
919 "first poll must return while scan task construction is blocked"
920 );
921 projection_started
922 .map_err(|_| vortex_err!("stream construction did not begin in the background"))?;
923 assert_eq!(calls.load(Ordering::Relaxed), 1);
924
925 Ok(())
926 }
927
928 #[tokio::test]
929 async fn into_stream_reports_stream_construction_errors() -> VortexResult<()> {
930 let range_start = i32::MAX as u64 + 1;
931 let reader = Arc::new(SplittingLayoutReader::new(Arc::new(AtomicUsize::new(0))));
932 let session = session_with_handle(TokioRuntime::current());
933 let mut stream = ScanBuilder::new(session, reader)
934 .with_row_range(range_start..range_start + 1)
935 .into_stream()?;
936
937 assert!(matches!(stream.next().await, Some(Err(_))));
938 assert!(stream.next().await.is_none());
939
940 Ok(())
941 }
942
943 #[rstest]
944 #[case::limit_below_matches(8, keep_all, 3, &[0, 1, 2])]
945 #[case::limit_zero(8, keep_all, 0, &[])]
946 #[case::limit_exceeds_matches(8, keep_odd, 100, &[1, 3, 5, 7])]
947 #[case::empty_input(0, keep_all, 3, &[])]
948 fn filtered_limit_yields_expected_rows(
949 #[case] row_count: u64,
950 #[case] keep_row: fn(u64) -> bool,
951 #[case] limit: u64,
952 #[case] expected: &[i32],
953 ) -> VortexResult<()> {
954 let runtime = SingleThreadRuntime::default();
955 let session = session_with_handle(runtime.handle());
956 let reader = Arc::new(
957 TestLayoutReader::new(row_count)
958 .with_split_size(2)
959 .with_keep_row(keep_row),
960 );
961 let filter = root().bind(reader.dtype())?;
962
963 let stream = ScanBuilder::new(session, reader)
964 .with_filter(filter)
965 .with_limit(limit)
966 .into_stream()?;
967 let values = collect_scan_values(runtime.block_on_stream(stream))?;
968 drain_runtime(&runtime);
969
970 assert_eq!(values.as_slice(), expected);
971 Ok(())
972 }
973
974 #[test]
977 fn ordered_filtered_limit_returns_the_earliest_rows() -> VortexResult<()> {
978 let runtime = SingleThreadRuntime::default();
979 let session = session_with_handle(runtime.handle());
980 let reader = Arc::new(TestLayoutReader::new(12).with_split_size(4));
981 let filter = root().bind(reader.dtype())?;
982
983 let stream = ScanBuilder::new(session, reader)
984 .with_filter(filter)
985 .with_limit(6)
986 .into_stream()?;
987 let values = collect_scan_values(runtime.block_on_stream(stream))?;
988 drain_runtime(&runtime);
989
990 assert_eq!(values, [0, 1, 2, 3, 4, 5]);
991 Ok(())
992 }
993
994 #[rstest]
996 #[case::unlimited(None, 8)]
997 #[case::limited(Some(3), 3)]
998 fn map_applies_to_every_emitted_split(
999 #[case] limit: Option<u64>,
1000 #[case] expected_rows: usize,
1001 ) -> VortexResult<()> {
1002 let runtime = SingleThreadRuntime::default();
1003 let session = session_with_handle(runtime.handle());
1004 let reader = Arc::new(TestLayoutReader::new(8).with_split_size(2));
1005 let filter = root().bind(reader.dtype())?;
1006
1007 let rows = ScanBuilder::new(session, reader)
1008 .with_filter(filter)
1009 .with_some_limit(limit)
1010 .map(|array| Ok(array.len()))
1011 .into_iter(&runtime)?
1012 .sum::<VortexResult<usize>>()?;
1013 drain_runtime(&runtime);
1014
1015 assert_eq!(rows, expected_rows);
1016 Ok(())
1017 }
1018
1019 #[rstest]
1020 #[case::filter_error(true, None)]
1021 #[case::filter_error_with_limit(true, Some(2))]
1022 #[case::projection_error(false, None)]
1023 #[case::projection_error_with_limit(false, Some(2))]
1024 fn errors_end_the_scan(
1025 #[case] fail_filter: bool,
1026 #[case] limit: Option<u64>,
1027 ) -> VortexResult<()> {
1028 let runtime = SingleThreadRuntime::default();
1029 let session = session_with_handle(runtime.handle());
1030 let reader = TestLayoutReader::new(2).with_split_size(1);
1031 let reader = Arc::new(if fail_filter {
1032 reader.with_fail_first_filter()
1033 } else {
1034 reader.with_fail_first_projection()
1035 });
1036 let filter = root().bind(reader.dtype())?;
1037 let stream = ScanBuilder::new(session, reader)
1038 .with_filter(filter)
1039 .with_some_limit(limit)
1040 .into_stream()?;
1041 let mut iter = runtime.block_on_stream(stream);
1042
1043 assert!(matches!(iter.next(), Some(Err(_))));
1044 assert!(iter.next().is_none());
1045 drop(iter);
1046 drain_runtime(&runtime);
1047 Ok(())
1048 }
1049
1050 #[test]
1051 fn full_file_splits_ignore_row_range() -> VortexResult<()> {
1052 let calls = Arc::new(AtomicUsize::new(0));
1053 let reader = Arc::new(SplittingLayoutReader::new(Arc::clone(&calls)));
1054
1055 let splits = ScanBuilder::new(SCAN_SESSION.clone(), reader)
1056 .with_row_range(1..3)
1057 .full_file_splits()?;
1058
1059 assert_eq!(splits, [0, 1, 2, 3, 4]);
1060
1061 Ok(())
1062 }
1063
1064 #[test]
1065 fn projection_errors_are_stream_items() -> VortexResult<()> {
1066 let runtime = SingleThreadRuntime::default();
1067 let session = session_with_handle(runtime.handle());
1068 let projection_masks = Arc::new(Mutex::new(Vec::new()));
1069 let reader = Arc::new(
1070 TestLayoutReader::new(1)
1071 .with_projection_masks(Arc::clone(&projection_masks))
1072 .with_projection_error(),
1073 );
1074 let filter = root().bind(reader.dtype())?;
1075 let stream = ScanBuilder::new(session, reader)
1076 .with_filter(filter)
1077 .with_limit(1)
1078 .into_stream()?;
1079 let mut iter = runtime.block_on_stream(stream);
1080
1081 assert!(matches!(iter.next(), Some(Err(_))));
1082 assert!(iter.next().is_none());
1083 assert_eq!(projection_masks.lock().as_slice(), [1]);
1084 Ok(())
1085 }
1086
1087 #[test]
1088 fn prepared_scan_limits_filtered_results() -> VortexResult<()> {
1089 let runtime = SingleThreadRuntime::default();
1090 let session = session_with_handle(runtime.handle());
1091 let reader = Arc::new(
1092 TestLayoutReader::new(8)
1093 .with_split_size(2)
1094 .with_keep_row(keep_odd),
1095 );
1096 let filter = root().bind(reader.dtype())?;
1097
1098 let scan = ScanBuilder::new(session, reader)
1099 .with_filter(filter)
1100 .with_limit(3)
1101 .prepare()?;
1102 let values = collect_scan_values(scan.execute_array_iter(None, &runtime)?)?;
1103 drain_runtime(&runtime);
1104
1105 assert_eq!(values, [1, 3, 5]);
1106 Ok(())
1107 }
1108
1109 #[test]
1110 fn scan_evaluates_filter_ranges_as_between() -> VortexResult<()> {
1111 let runtime = SingleThreadRuntime::default();
1112 let session = session_with_handle(runtime.handle());
1113 let filter_exprs = Arc::new(Mutex::new(Vec::new()));
1114 let dtype = nested_dtype();
1115 let reader = Arc::new(
1116 TestLayoutReader::new(4)
1117 .with_dtype(dtype.clone())
1118 .with_filter_exprs(Arc::clone(&filter_exprs)),
1119 );
1120 let filter = and(gt_eq(col("b"), lit(2_i32)), lt(col("b"), lit(5_i32))).bind(&dtype)?;
1121
1122 let values = collect_scan_values(
1123 ScanBuilder::new(session, reader)
1124 .with_projection(col("b").bind(&dtype)?)
1125 .with_filter(filter)
1126 .into_array_iter(&runtime)?,
1127 )?;
1128 drain_runtime(&runtime);
1129
1130 assert_eq!(values, [0, 1, 2, 3]);
1131 let expected = between(
1132 col("b"),
1133 lit(2_i32),
1134 lit(5_i32),
1135 BetweenOptions {
1136 lower_strict: StrictComparison::NonStrict,
1137 upper_strict: StrictComparison::Strict,
1138 },
1139 )
1140 .bind(&dtype)?;
1141 assert_eq!(filter_exprs.lock().as_slice(), [expected]);
1142 Ok(())
1143 }
1144
1145 #[test]
1146 fn prepared_scan_split_futures_can_be_polled_in_reverse() -> VortexResult<()> {
1147 let runtime = SingleThreadRuntime::default();
1148 let session = session_with_handle(runtime.handle());
1149 let projection_masks = Arc::new(Mutex::new(Vec::new()));
1150 let reader = Arc::new(
1151 TestLayoutReader::new(8)
1152 .with_split_size(2)
1153 .with_keep_row(keep_odd)
1154 .with_projection_masks(Arc::clone(&projection_masks)),
1155 );
1156 let filter = root().bind(reader.dtype())?;
1157 let scan = ScanBuilder::new(session, reader)
1158 .with_filter(filter)
1159 .prepare()?;
1160 let tasks = scan.execute(Some(1..7))?;
1161 assert!(projection_masks.lock().is_empty());
1162
1163 let mut arrays = Vec::new();
1164 for task in tasks.into_iter().rev() {
1165 if let Some(array) = runtime.block_on(task)? {
1166 arrays.push(Ok(array));
1167 }
1168 }
1169 assert_eq!(collect_scan_values(arrays)?, [5, 3, 1]);
1170 Ok(())
1171 }
1172
1173 #[rstest]
1174 #[case(false)]
1175 #[case(true)]
1176 fn prepared_scan_split_futures_reject_limits(#[case] shared: bool) -> VortexResult<()> {
1177 let reader = Arc::new(TestLayoutReader::new(8).with_split_size(2));
1178 let builder = ScanBuilder::new(SCAN_SESSION.clone(), reader);
1179 let builder = if shared {
1180 builder.with_shared_limit(RowLimit::new(1))
1181 } else {
1182 builder.with_limit(1)
1183 };
1184
1185 assert!(builder.prepare()?.execute(None).is_err());
1186 Ok(())
1187 }
1188
1189 #[derive(Debug)]
1190 struct BlockingSplitsLayoutReader {
1191 name: Arc<str>,
1192 dtype: DType,
1193 row_count: u64,
1194 register_splits_calls: Arc<AtomicUsize>,
1195 gate: Arc<Mutex<()>>,
1196 }
1197
1198 impl BlockingSplitsLayoutReader {
1199 fn new(gate: Arc<Mutex<()>>, register_splits_calls: Arc<AtomicUsize>) -> Self {
1200 Self {
1201 name: Arc::from("blocking-splits"),
1202 dtype: DType::Primitive(PType::I32, Nullability::NonNullable),
1203 row_count: 1,
1204 register_splits_calls,
1205 gate,
1206 }
1207 }
1208 }
1209
1210 impl LayoutReader for BlockingSplitsLayoutReader {
1211 fn name(&self) -> &Arc<str> {
1212 &self.name
1213 }
1214
1215 fn dtype(&self) -> &DType {
1216 &self.dtype
1217 }
1218
1219 fn row_count(&self) -> u64 {
1220 self.row_count
1221 }
1222
1223 fn register_splits(
1224 &self,
1225 _field_mask: &[FieldMask],
1226 split_range: &SplitRange,
1227 splits: &mut RowSplits,
1228 ) -> VortexResult<()> {
1229 self.register_splits_calls.fetch_add(1, Ordering::Relaxed);
1230 let _guard = self.gate.lock();
1231 splits.push(split_range.root_row_range().end);
1232 Ok(())
1233 }
1234
1235 fn pruning_evaluation(
1236 &self,
1237 _row_range: &Range<u64>,
1238 _expr: &BoundExpression,
1239 _mask: Mask,
1240 ) -> VortexResult<MaskFuture> {
1241 vortex_bail!("not needed for this test");
1242 }
1243
1244 fn filter_evaluation(
1245 &self,
1246 _row_range: &Range<u64>,
1247 _expr: &BoundExpression,
1248 _mask: MaskFuture,
1249 ) -> VortexResult<MaskFuture> {
1250 vortex_bail!("not needed for this test");
1251 }
1252
1253 fn projection_evaluation(
1254 &self,
1255 _row_range: &Range<u64>,
1256 _expr: &BoundExpression,
1257 _mask: MaskFuture,
1258 ) -> VortexResult<ArrayFuture> {
1259 Ok(Box::pin(async move {
1260 unreachable!("scan should not be polled in this test")
1261 }))
1262 }
1263
1264 fn as_any(&self) -> &dyn std::any::Any {
1265 self
1266 }
1267 }
1268
1269 #[test]
1270 fn into_stream_first_poll_does_not_block() {
1271 let gate = Arc::new(Mutex::new(()));
1272 let guard = gate.lock();
1273
1274 let calls = Arc::new(AtomicUsize::new(0));
1275 let reader = Arc::new(BlockingSplitsLayoutReader::new(
1276 Arc::clone(&gate),
1277 Arc::clone(&calls),
1278 ));
1279
1280 let runtime = SingleThreadRuntime::default();
1281 let session = session_with_handle(runtime.handle());
1282
1283 let mut stream = ScanBuilder::new(session, reader).into_stream().unwrap();
1284
1285 let (send, recv) = mpsc::channel::<bool>();
1286 let join = std::thread::spawn(move || {
1287 let waker = noop_waker_ref();
1288 let mut cx = Context::from_waker(waker);
1289 let poll = Pin::new(&mut stream).poll_next(&mut cx);
1290 let _ = send.send(matches!(poll, Poll::Pending));
1291 });
1292
1293 let polled_pending = recv.recv_timeout(Duration::from_secs(1)).ok();
1294
1295 drop(guard);
1297 drop(join.join());
1298
1299 let polled_pending = polled_pending.expect("poll_next blocked; expected quick return");
1300 assert!(
1301 polled_pending,
1302 "expected Poll::Pending while prepare is blocked"
1303 );
1304 assert_eq!(calls.load(Ordering::Relaxed), 0);
1305
1306 drop(runtime);
1307 }
1308
1309 #[test]
1310 fn into_stream_with_row_range() -> VortexResult<()> {
1311 let mut ctx = array_session().create_execution_ctx();
1312 let calls = Arc::new(AtomicUsize::new(0));
1313 let reader = Arc::new(SplittingLayoutReader::new(Arc::clone(&calls)));
1314
1315 let runtime = SingleThreadRuntime::default();
1316 let session = session_with_handle(runtime.handle());
1317
1318 let stream = ScanBuilder::new(session, reader)
1319 .with_row_range(1..3)
1320 .into_stream()?;
1321 let mut iter = runtime.block_on_stream(stream);
1322
1323 let mut values = Vec::new();
1324 for chunk in &mut iter {
1325 let prim = chunk?.execute::<PrimitiveArray>(&mut ctx)?;
1326 values.extend(prim.into_buffer::<i32>().iter().copied());
1327 }
1328
1329 assert_eq!(calls.load(Ordering::Relaxed), 1);
1330 assert_eq!(values.as_ref(), [1, 2]);
1331
1332 Ok(())
1333 }
1334}