1use std::cmp;
5use std::iter;
6use std::ops::Range;
7use std::sync::Arc;
8use std::task::Poll;
9
10use futures::Future;
11use futures::FutureExt;
12use futures::Stream;
13use futures::StreamExt;
14use futures::future;
15use futures::future::BoxFuture;
16use futures::ready;
17use futures::stream;
18use futures::stream::BoxStream;
19use itertools::Either;
20use itertools::Itertools;
21use vortex_array::ArrayRef;
22use vortex_array::dtype::DType;
23use vortex_array::expr::BoundExpression;
24use vortex_array::iter::ArrayIterator;
25use vortex_array::iter::ArrayIteratorAdapter;
26use vortex_array::stream::ArrayStream;
27use vortex_array::stream::ArrayStreamAdapter;
28use vortex_error::VortexExpect;
29use vortex_error::VortexResult;
30use vortex_error::vortex_bail;
31use vortex_io::runtime::BlockingRuntime;
32use vortex_io::session::RuntimeSessionExt;
33use vortex_scan::row_mask::RowMask;
34use vortex_scan::selection::Selection;
35use vortex_session::VortexSession;
36use vortex_utils::parallelism::get_available_parallelism;
37
38use crate::LayoutReaderRef;
39use crate::scan::filter::FilterExpr;
40use crate::scan::limit::RowLimit;
41use crate::scan::limit::ScanLimit;
42use crate::scan::splits::Splits;
43use crate::scan::tasks::SplitFuture;
44use crate::scan::tasks::TaskContext;
45use crate::scan::tasks::split_exec;
46use crate::scan::tasks::split_projection;
47
48pub struct RepeatedScan<A> {
53 session: VortexSession,
54 layout_reader: LayoutReaderRef,
55 projection: BoundExpression,
56 filter: Option<BoundExpression>,
57 ordered: bool,
58 row_range: Option<Range<u64>>,
60 selection: Selection,
62 splits: Splits,
64 concurrency: usize,
66 map_fn: MapFn<A>,
68 limit: Option<ScanLimit>,
70 dtype: DType,
72}
73
74type MapFn<A> = Arc<dyn Fn(ArrayRef) -> VortexResult<A> + Send + Sync>;
76
77impl RepeatedScan<ArrayRef> {
78 pub fn execute_array_iter<B: BlockingRuntime>(
79 &self,
80 row_range: Option<Range<u64>>,
81 runtime: &B,
82 ) -> VortexResult<impl ArrayIterator + 'static> {
83 let dtype = self.dtype.clone();
84 let stream = self.execute_stream(row_range)?;
85 let iter = runtime.block_on_stream(stream);
86 Ok(ArrayIteratorAdapter::new(dtype, iter))
87 }
88
89 pub fn execute_array_stream(
90 &self,
91 row_range: Option<Range<u64>>,
92 ) -> VortexResult<impl ArrayStream + Send + 'static> {
93 let dtype = self.dtype.clone();
94 let stream = self.execute_stream(row_range)?;
95 Ok(ArrayStreamAdapter::new(dtype, stream))
96 }
97}
98
99impl<A: 'static + Send> RepeatedScan<A> {
100 pub fn execute(
108 &self,
109 row_range: Option<Range<u64>>,
110 ) -> VortexResult<Vec<BoxFuture<'static, VortexResult<Option<A>>>>> {
111 if self.limit.is_some() {
112 vortex_bail!("Split futures do not support row limits; use a scan stream or iterator");
113 }
114
115 let ctx = self.task_context();
116 self.split_masks(row_range)
117 .map(|row_mask| Ok(map_split(split_exec(&ctx, row_mask)?, &self.map_fn)))
118 .collect()
119 }
120
121 pub fn dtype(&self) -> &DType {
122 &self.dtype
123 }
124
125 #[expect(
127 clippy::too_many_arguments,
128 reason = "all arguments are needed for scan construction"
129 )]
130 pub(crate) fn new(
131 session: VortexSession,
132 layout_reader: LayoutReaderRef,
133 projection: BoundExpression,
134 filter: Option<BoundExpression>,
135 ordered: bool,
136 row_range: Option<Range<u64>>,
137 selection: Selection,
138 splits: Splits,
139 concurrency: usize,
140 map_fn: MapFn<A>,
141 limit: Option<ScanLimit>,
142 dtype: DType,
143 ) -> Self {
144 Self {
145 session,
146 layout_reader,
147 projection,
148 filter,
149 ordered,
150 row_range,
151 selection,
152 splits,
153 concurrency,
154 map_fn,
155 limit,
156 dtype,
157 }
158 }
159
160 fn task_context(&self) -> Arc<TaskContext> {
161 Arc::new(TaskContext {
162 filter: self
163 .filter
164 .clone()
165 .map(|filter| Arc::new(FilterExpr::new(filter))),
166 reader: Arc::clone(&self.layout_reader),
167 projection: self.projection.clone(),
168 })
169 }
170
171 fn split_ranges(&self, row_range: Option<Range<u64>>) -> Vec<Range<u64>> {
172 let selection_range: Option<Range<u64>> = match &self.selection {
173 Selection::IncludeByIndex(buf) if !buf.is_empty() => {
174 Some(buf[0]..buf[buf.len() - 1] + 1)
175 }
176 Selection::IncludeRoaring(roaring) if !roaring.is_empty() => {
177 Some(roaring.min().vortex_expect("empty")..roaring.max().vortex_expect("empty") + 1)
178 }
179 _ => None,
180 };
181 let row_range = intersect_ranges(self.row_range.as_ref(), row_range);
182 let row_range = intersect_ranges(row_range.as_ref(), selection_range);
183
184 match &self.splits {
185 Splits::Natural(vec) => {
186 debug_assert!(vec.is_sorted());
187 let splits_iter = match row_range {
188 None => Either::Left(vec.iter().copied()),
189 Some(range) => {
190 if range.is_empty() {
191 return Vec::new();
192 }
193 let lo = vec.partition_point(|&x| x <= range.start);
194 let hi = vec.partition_point(|&x| x < range.end);
195 Either::Right(
196 iter::once(range.start)
197 .chain(vec[lo..hi].iter().copied())
198 .chain(iter::once(range.end)),
199 )
200 }
201 };
202
203 splits_iter
204 .tuple_windows()
205 .map(|(start, end)| start..end)
206 .collect()
207 }
208 Splits::Ranges(ranges) => match row_range {
209 None => ranges.to_vec(),
210 Some(range) => {
211 if range.is_empty() {
212 return Vec::new();
213 }
214 ranges
215 .iter()
216 .filter_map(move |r| {
217 let start = cmp::max(r.start, range.start);
218 let end = cmp::min(r.end, range.end);
219 (start < end).then_some(start..end)
220 })
221 .collect()
222 }
223 },
224 }
225 }
226
227 fn split_masks(&self, row_range: Option<Range<u64>>) -> impl Iterator<Item = RowMask> + '_ {
229 self.split_ranges(row_range)
230 .into_iter()
231 .filter(|range| range.start < range.end)
232 .map(|range| self.selection.row_mask(&range))
233 .filter(|row_mask| !row_mask.mask().all_false())
234 }
235
236 pub fn execute_stream(
240 &self,
241 row_range: Option<Range<u64>>,
242 ) -> VortexResult<BoxStream<'static, VortexResult<A>>> {
243 let concurrency = self.concurrency * get_available_parallelism().unwrap_or(1);
244 let handle = self.session.handle();
245 let ordered = self.ordered;
246 let ctx = self.task_context();
247
248 let map_fn = &self.map_fn;
249
250 let arrays = match self.limit.as_ref().map(ScanLimit::budget) {
251 Some(limit) if ctx.filter.is_some() => {
257 let gate = limit.clone();
258 let selection = self.selection.clone();
259 let split_handle = handle.clone();
260 let tasks = stream::iter(self.split_ranges(row_range))
261 .take_while(move |_| future::ready(!gate.is_exhausted()))
262 .map(move |row_range| {
263 let ctx = Arc::clone(&ctx);
264 let selection = selection.clone();
265 split_handle.spawn_nested(move |handle| async move {
266 let task = handle
267 .spawn_cpu(move || {
268 let row_mask = selection.row_mask(&row_range);
269 if row_mask.mask().all_false() {
270 return Ok(None);
271 }
272 split_exec(&ctx, row_mask).map(Some)
273 })
274 .await?;
275 match task {
276 Some(task) => task.await,
277 None => Ok(None),
278 }
279 })
280 });
281 let map_fn = Arc::clone(map_fn);
282 let arrays = buffer(tasks, ordered, concurrency)
283 .filter_map(|array| future::ready(array.transpose()))
284 .boxed();
285 let mapped = limit_rows(arrays, limit).map(move |array| {
286 let map_fn = Arc::clone(&map_fn);
287 handle.spawn_cpu(move || map_fn(array?))
288 });
289 buffer(mapped, ordered, concurrency)
290 }
291 limit => {
292 let mut tasks = Vec::new();
295 for row_mask in self.split_masks(row_range) {
296 let task = if let Some(limit) = &limit {
297 if limit.is_exhausted() {
298 break;
299 }
300 let mask = limit.limit(row_mask.mask().clone());
301 split_projection(&ctx, &row_mask.row_range(), mask)?
302 } else {
303 split_exec(&ctx, row_mask)?
304 };
305 tasks.push(map_split(task, map_fn));
306 }
307 buffer(
308 stream::iter(tasks).map(move |task| handle.spawn(task)),
309 ordered,
310 concurrency,
311 )
312 .filter_map(|array| future::ready(array.transpose()))
313 .boxed()
314 }
315 };
316
317 Ok(stop_on_error(arrays))
318 }
319}
320
321fn map_split<A: 'static + Send>(
323 task: SplitFuture,
324 map_fn: &MapFn<A>,
325) -> BoxFuture<'static, VortexResult<Option<A>>> {
326 let map_fn = Arc::clone(map_fn);
327 task.map(move |array| array?.map(|array| map_fn(array)).transpose())
328 .boxed()
329}
330
331fn limit_rows(
333 arrays: BoxStream<'static, VortexResult<ArrayRef>>,
334 limit: RowLimit,
335) -> BoxStream<'static, VortexResult<ArrayRef>> {
336 let mut arrays = Some(arrays);
337 stream::poll_fn(move |cx| {
338 loop {
339 if limit.is_exhausted() {
340 arrays = None;
341 }
342 let Some(stream) = arrays.as_mut() else {
343 return Poll::Ready(None);
344 };
345 let array = match ready!(stream.poll_next_unpin(cx)) {
346 Some(Ok(array)) => array,
347 end => {
348 arrays = None;
349 return Poll::Ready(end);
350 }
351 };
352 let granted = limit.take(array.len());
353 if limit.is_exhausted() {
354 arrays = None;
355 }
356 if granted != 0 {
357 return Poll::Ready(Some(if granted < array.len() {
358 array.slice(0..granted)
359 } else {
360 Ok(array)
361 }));
362 }
363 }
364 })
365 .boxed()
366}
367
368fn stop_on_error<T: Send + 'static>(
370 stream: BoxStream<'static, VortexResult<T>>,
371) -> BoxStream<'static, VortexResult<T>> {
372 let mut stream = Some(stream);
373 stream::poll_fn(move |cx| {
374 let Some(input) = stream.as_mut() else {
375 return Poll::Ready(None);
376 };
377 let item = ready!(input.poll_next_unpin(cx));
378 if !matches!(item, Some(Ok(_))) {
379 stream = None;
380 }
381 Poll::Ready(item)
382 })
383 .boxed()
384}
385
386fn buffer<T: Send + 'static>(
388 tasks: impl Stream<Item = impl Future<Output = T> + Send + 'static> + Send + 'static,
389 ordered: bool,
390 concurrency: usize,
391) -> BoxStream<'static, T> {
392 if ordered {
393 tasks.buffered(concurrency).boxed()
394 } else {
395 tasks.buffer_unordered(concurrency).boxed()
396 }
397}
398
399fn intersect_ranges(left: Option<&Range<u64>>, right: Option<Range<u64>>) -> Option<Range<u64>> {
400 match (left, right) {
401 (None, None) => None,
402 (None, Some(r)) => Some(r),
403 (Some(l), None) => Some(l.clone()),
404 (Some(l), Some(r)) => Some(cmp::max(l.start, r.start)..cmp::min(l.end, r.end)),
405 }
406}
407
408#[cfg(test)]
409mod tests {
410 use std::sync::Arc;
411 use std::task::Poll;
412
413 use futures::FutureExt;
414 use futures::StreamExt;
415 use futures::stream;
416 use rstest::rstest;
417 use vortex_array::IntoArray;
418 use vortex_array::arrays::PrimitiveArray;
419 use vortex_error::VortexResult;
420 use vortex_error::vortex_err;
421
422 use super::limit_rows;
423 use super::stop_on_error;
424 use crate::scan::limit::RowLimit;
425
426 #[rstest]
427 #[case::whole_array(4)]
428 #[case::trimmed_array(2)]
429 fn limit_ends_before_pending_work(#[case] rows: usize) -> VortexResult<()> {
430 let array = PrimitiveArray::from_iter(0..4i32).into_array();
431 let alive = Arc::new(());
432 let dropped = Arc::downgrade(&alive);
433 let pending = stream::poll_fn(move |_| {
434 let _ = &alive;
435 Poll::Pending
436 });
437 let arrays = stream::iter([Ok(array)]).chain(pending).boxed();
438 let mut limited = limit_rows(arrays, RowLimit::new(rows as u64));
439
440 let array = limited
441 .next()
442 .now_or_never()
443 .expect("ready")
444 .expect("array")?;
445 assert_eq!(array.len(), rows);
446 assert!(dropped.upgrade().is_none());
447 assert!(matches!(limited.next().now_or_never(), Some(None)));
448 Ok(())
449 }
450
451 #[test]
452 fn exhausted_shared_limit_ends_before_pending_work() {
453 let limit = RowLimit::new(1);
454 let mut limited = limit_rows(stream::pending().boxed(), limit.clone());
455 assert!(limited.next().now_or_never().is_none());
456
457 assert_eq!(limit.take(1), 1);
458 assert!(matches!(limited.next().now_or_never(), Some(None)));
459 }
460
461 #[test]
462 fn zero_limit_does_not_poll_input() {
463 let arrays = stream::poll_fn(|_| panic!("zero limit must not poll input")).boxed();
464 let mut limited = limit_rows(arrays, RowLimit::new(0));
465 assert!(matches!(limited.next().now_or_never(), Some(None)));
466 }
467
468 #[test]
469 fn error_ends_before_pending_work() {
470 let arrays = stream::iter([Err::<(), _>(vortex_err!("scan failed"))])
471 .chain(stream::pending())
472 .boxed();
473 let mut stream = stop_on_error(arrays);
474
475 assert!(matches!(stream.next().now_or_never(), Some(Some(Err(_)))));
476 assert!(matches!(stream.next().now_or_never(), Some(None)));
477 }
478}