1use std::any::Any;
28use std::collections::VecDeque;
29use std::ops::Range;
30use std::sync::Arc;
31
32use async_trait::async_trait;
33use futures::FutureExt;
34use futures::StreamExt;
35use futures::stream;
36use itertools::Itertools;
37use tracing::Instrument;
38use vortex_array::dtype::DType;
39use vortex_array::dtype::FieldPath;
40use vortex_array::expr::BoundExpression;
41use vortex_array::expr::stats::Precision;
42use vortex_array::stats::StatsSet;
43use vortex_array::stream::ArrayStreamAdapter;
44use vortex_array::stream::ArrayStreamExt;
45use vortex_array::stream::SendableArrayStream;
46use vortex_error::SharedVortexResult;
47use vortex_error::VortexResult;
48use vortex_error::vortex_bail;
49use vortex_error::vortex_err;
50use vortex_io::session::RuntimeSessionExt;
51use vortex_mask::Mask;
52use vortex_scan::DataSource;
53use vortex_scan::DataSourceScan;
54use vortex_scan::DataSourceScanRef;
55use vortex_scan::Partition;
56use vortex_scan::PartitionRef;
57use vortex_scan::PartitionStream;
58use vortex_scan::ScanRequest;
59use vortex_scan::selection::Selection;
60use vortex_session::VortexSession;
61use vortex_utils::parallelism::get_available_parallelism;
62
63use crate::LayoutReaderRef;
64use crate::scan::limit::RowLimit;
65use crate::scan::scan_builder::ScanBuilder;
66
67const DEFAULT_CONCURRENCY: usize = 8;
69
70#[async_trait]
76pub trait LayoutReaderFactory: 'static + Send + Sync {
77 async fn open(&self) -> VortexResult<Option<LayoutReaderRef>>;
79}
80
81#[derive(Clone)]
89pub struct MultiLayoutDataSource {
90 dtype: DType,
91 session: VortexSession,
92 children: Arc<[MultiLayoutChild]>,
93 concurrency: usize,
94}
95
96pub enum MultiLayoutChild {
97 Opened {
98 reader: LayoutReaderRef,
99 byte_size: Option<u64>,
101 },
102 Deferred {
103 factory: Arc<dyn LayoutReaderFactory>,
104 byte_size: Option<u64>,
106 },
107}
108
109impl MultiLayoutChild {
110 pub fn byte_size(&self) -> Option<u64> {
112 match self {
113 MultiLayoutChild::Opened { byte_size, .. } => *byte_size,
114 MultiLayoutChild::Deferred { byte_size, .. } => *byte_size,
115 }
116 }
117}
118
119impl MultiLayoutDataSource {
120 pub fn new_with_first(
127 first: LayoutReaderRef,
128 remaining: Vec<Arc<dyn LayoutReaderFactory>>,
129 byte_sizes: Vec<Option<u64>>,
130 session: &VortexSession,
131 ) -> Self {
132 let dtype = first.dtype().clone();
133 let concurrency = get_available_parallelism().unwrap_or(DEFAULT_CONCURRENCY);
134
135 let total = 1 + remaining.len();
136 let mut sizes = byte_sizes;
137 if sizes.is_empty() {
138 sizes = vec![None; total];
139 }
140 debug_assert_eq!(
141 sizes.len(),
142 total,
143 "byte_sizes length must match the number of children"
144 );
145
146 let mut children = Vec::with_capacity(total);
147 let mut sizes_iter = sizes.into_iter();
148 let first_size = sizes_iter.next().unwrap_or(None);
149 children.push(MultiLayoutChild::Opened {
150 reader: first,
151 byte_size: first_size,
152 });
153 children.extend(
154 remaining
155 .into_iter()
156 .zip_eq(sizes_iter)
157 .map(|(factory, byte_size)| MultiLayoutChild::Deferred { factory, byte_size }),
158 );
159
160 Self {
161 dtype,
162 session: session.clone(),
163 children: children.into(),
164 concurrency,
165 }
166 }
167
168 pub fn new_deferred(
176 dtype: DType,
177 factories: Vec<Arc<dyn LayoutReaderFactory>>,
178 byte_sizes: Vec<Option<u64>>,
179 session: &VortexSession,
180 ) -> Self {
181 let concurrency = get_available_parallelism().unwrap_or(DEFAULT_CONCURRENCY);
182
183 let mut sizes = byte_sizes;
184 if sizes.is_empty() {
185 sizes = vec![None; factories.len()];
186 }
187 debug_assert_eq!(
188 sizes.len(),
189 factories.len(),
190 "byte_sizes length must match the number of factories"
191 );
192
193 Self {
194 dtype,
195 session: session.clone(),
196 children: factories
197 .into_iter()
198 .zip_eq(sizes)
199 .map(|(factory, byte_size)| MultiLayoutChild::Deferred { factory, byte_size })
200 .collect(),
201 concurrency,
202 }
203 }
204
205 pub fn children(&self) -> &[MultiLayoutChild] {
206 &self.children
207 }
208
209 pub fn with_concurrency(mut self, concurrency: usize) -> Self {
214 self.concurrency = concurrency;
215 self
216 }
217}
218
219#[async_trait]
220impl DataSource for MultiLayoutDataSource {
221 fn dtype(&self) -> &DType {
222 &self.dtype
223 }
224
225 fn row_count(&self) -> Precision<u64> {
226 let mut sum: u64 = 0;
227 let mut opened_count: u64 = 0;
228 let mut deferred_count: u64 = 0;
229
230 for child in self.children.iter() {
231 match child {
232 MultiLayoutChild::Opened { reader, .. } => {
233 opened_count += 1;
234 sum = sum.saturating_add(reader.row_count());
235 }
236 MultiLayoutChild::Deferred { .. } => {
237 deferred_count += 1;
238 }
239 }
240 }
241
242 let total_count = opened_count + deferred_count;
243 if total_count == 0 {
244 return Precision::exact(0u64);
245 }
246
247 if deferred_count == 0 {
248 Precision::exact(sum)
249 } else if let Some(avg) = sum.checked_div(opened_count) {
250 let extrapolated = avg.saturating_mul(total_count);
251 Precision::inexact(extrapolated)
252 } else {
253 Precision::Absent
254 }
255 }
256
257 fn byte_size(&self) -> Precision<u64> {
258 let total_count = self.children.len() as u64;
259 if total_count == 0 {
260 return Precision::exact(0u64);
261 }
262
263 let mut sum: u64 = 0;
264 let mut known_count: u64 = 0;
265 for child in self.children.iter() {
266 if let Some(size) = child.byte_size() {
267 sum = sum.saturating_add(size);
268 known_count += 1;
269 }
270 }
271
272 if known_count == 0 {
273 return Precision::Absent;
274 }
275
276 if known_count == total_count {
277 Precision::exact(sum)
278 } else {
279 let avg = sum / known_count;
280 let extrapolated = avg.saturating_mul(total_count);
281 Precision::inexact(extrapolated)
282 }
283 }
284
285 fn deserialize_partition(
286 &self,
287 _data: &[u8],
288 _session: &VortexSession,
289 ) -> VortexResult<PartitionRef> {
290 vortex_bail!("MultiLayoutDataSource partitions are not yet serializable")
291 }
292
293 async fn scan(&self, scan_request: ScanRequest) -> VortexResult<DataSourceScanRef> {
294 let mut ready = VecDeque::new();
295 let mut deferred = VecDeque::new();
296
297 for child in self.children.iter() {
298 match child {
299 MultiLayoutChild::Opened { reader, .. } => ready.push_back(Arc::clone(reader)),
300 MultiLayoutChild::Deferred { factory, .. } => {
301 deferred.push_back(Arc::clone(factory))
302 }
303 }
304 }
305
306 let request = BoundScanRequest::try_new(scan_request, &self.dtype)?;
307 let dtype = request.projection.dtype().clone();
308
309 let row_limit = (!request.ordered)
313 .then(|| request.limit.map(RowLimit::new))
314 .flatten();
315
316 Ok(Box::new(MultiLayoutScan {
317 session: self.session.clone(),
318 source_dtype: self.dtype.clone(),
319 dtype,
320 request,
321 row_limit,
322 ready,
323 deferred,
324 handle: self.session.handle(),
325 concurrency: self.concurrency,
326 }))
327 }
328
329 async fn field_statistics(&self, _field_path: &FieldPath) -> VortexResult<StatsSet> {
330 Ok(StatsSet::default())
331 }
332}
333
334#[derive(Clone)]
335struct BoundScanRequest {
336 projection: BoundExpression,
337 filter: SharedVortexResult<Option<BoundExpression>>,
338 row_range: Option<Range<u64>>,
339 selection: Selection,
340 partition_selection: Selection,
341 partition_range: Option<Range<u64>>,
342 ordered: bool,
343 limit: Option<u64>,
344}
345
346impl BoundScanRequest {
347 fn try_new(request: ScanRequest, dtype: &DType) -> VortexResult<Self> {
348 let ScanRequest {
349 projection,
350 filter,
351 row_range,
352 selection,
353 partition_selection,
354 partition_range,
355 ordered,
356 limit,
357 } = request;
358
359 Ok(Self {
360 projection: projection.bind(dtype)?.optimize_recursive()?,
361 filter: filter
362 .map(|expr| expr.bind(dtype)?.optimize_recursive())
363 .transpose()
364 .map_err(Arc::new),
365 row_range,
366 selection,
367 partition_selection,
368 partition_range,
369 ordered,
370 limit,
371 })
372 }
373}
374
375struct MultiLayoutScan {
376 session: VortexSession,
377 source_dtype: DType,
378 dtype: DType,
379 request: BoundScanRequest,
380 row_limit: Option<RowLimit>,
381 ready: VecDeque<LayoutReaderRef>,
382 deferred: VecDeque<Arc<dyn LayoutReaderFactory>>,
383 handle: vortex_io::runtime::Handle,
384 concurrency: usize,
385}
386
387impl DataSourceScan for MultiLayoutScan {
388 fn dtype(&self) -> &DType {
389 &self.dtype
390 }
391
392 fn partition_count(&self) -> Precision<usize> {
393 let count = self.ready.len() + self.deferred.len();
394 if self.deferred.is_empty() {
395 Precision::exact(count)
396 } else {
397 Precision::inexact(count)
398 }
399 }
400
401 fn partitions(self: Box<Self>) -> PartitionStream {
402 let Self {
403 session,
404 source_dtype,
405 dtype: _,
406 request,
407 row_limit,
408 ready,
409 deferred,
410 handle,
411 concurrency,
412 } = *self;
413
414 let ordered = request.ordered;
415
416 let ready_stream = stream::iter(ready).map(Ok);
418
419 let spawned = stream::iter(deferred).map(move |factory| {
423 handle.spawn(async move {
424 factory
425 .open()
426 .instrument(tracing::info_span!("LayoutReaderFactory::open"))
427 .await
428 })
429 });
430
431 let deferred_stream = if ordered {
432 spawned
433 .buffered(concurrency)
434 .filter_map(|result| async move {
435 match result {
436 Ok(Some(reader)) => Some(Ok(reader)),
437 Ok(None) => None,
438 Err(e) => Some(Err(e)),
439 }
440 })
441 .boxed()
442 } else {
443 spawned
444 .buffer_unordered(concurrency)
445 .filter_map(|result| async move {
446 match result {
447 Ok(Some(reader)) => Some(Ok(reader)),
448 Ok(None) => None,
449 Err(e) => Some(Err(e)),
450 }
451 })
452 .boxed()
453 };
454
455 ready_stream
459 .chain(deferred_stream)
460 .enumerate()
461 .flat_map(move |(i, reader_result)| match reader_result {
462 Ok(reader) => reader_partition(
463 i,
464 reader,
465 session.clone(),
466 &source_dtype,
467 request.clone(),
468 row_limit.clone(),
469 ),
470 Err(e) => stream::once(async move { Err(e) }).boxed(),
471 })
472 .boxed()
473 }
474}
475
476fn reader_partition(
482 partition_idx: usize,
483 reader: LayoutReaderRef,
484 session: VortexSession,
485 source_dtype: &DType,
486 request: BoundScanRequest,
487 row_limit: Option<RowLimit>,
488) -> PartitionStream {
489 if reader.dtype() != source_dtype {
490 let error = vortex_err!(
491 "Multi-layout reader dtype mismatch: expected {}, got {}",
492 source_dtype,
493 reader.dtype()
494 );
495 return stream::once(async move { Err(error) }).boxed();
496 }
497
498 let row_count = reader.row_count();
499 let row_range = request.row_range.clone().unwrap_or(0..row_count);
500
501 let partition_idx_u64: u64 = partition_idx as u64;
502 if let Some(range) = &request.partition_range
503 && !range.contains(&partition_idx_u64)
504 {
505 return stream::empty().boxed();
506 };
507 match &request.partition_selection {
508 Selection::IncludeByIndex(buffer)
509 if buffer.as_slice().binary_search(&partition_idx_u64).is_err() =>
510 {
511 return stream::empty().boxed();
512 }
513 Selection::ExcludeByIndex(buffer)
514 if buffer.as_slice().binary_search(&partition_idx_u64).is_ok() =>
515 {
516 return stream::empty().boxed();
517 }
518 _ => {}
519 };
520
521 if let Ok(Some(filter)) = &request.filter {
524 let mask_len = usize::try_from(row_range.end - row_range.start).unwrap_or(usize::MAX);
525 let mask = Mask::new_true(mask_len);
526 if let Ok(pruning_future) = reader.pruning_evaluation(&row_range, filter, mask)
527 && let Some(Ok(result_mask)) = pruning_future.now_or_never()
528 && result_mask.all_false()
529 {
530 return stream::empty().boxed();
531 }
532 }
533
534 stream::once(async move {
535 Ok(Box::new(MultiLayoutPartition {
536 reader,
537 session,
538 request: BoundScanRequest {
539 row_range: Some(row_range),
540 ..request
541 },
542 row_limit,
543 index: partition_idx,
544 }) as PartitionRef)
545 })
546 .boxed()
547}
548
549struct MultiLayoutPartition {
554 reader: LayoutReaderRef,
555 session: VortexSession,
556 request: BoundScanRequest,
557 row_limit: Option<RowLimit>,
558 index: usize,
559}
560
561impl Partition for MultiLayoutPartition {
562 fn as_any(&self) -> &dyn Any {
563 self
564 }
565
566 fn index(&self) -> usize {
567 self.index
568 }
569
570 fn row_count(&self) -> Precision<u64> {
571 let Some(row_range) = self.request.row_range.as_ref() else {
572 return Precision::Absent;
573 };
574 let row_count = self.request.selection.row_count_in_range(row_range);
575 let row_count = self
576 .request
577 .limit
578 .map_or(row_count, |limit| row_count.min(limit));
579
580 let has_filter = match &self.request.filter {
581 Ok(filter) => filter.is_some(),
582 Err(_) => true,
583 };
584 if has_filter || self.row_limit.is_some() {
585 Precision::inexact(row_count)
586 } else {
587 Precision::exact(row_count)
588 }
589 }
590
591 fn byte_size(&self) -> Precision<u64> {
592 Precision::Absent
593 }
594
595 fn execute(self: Box<Self>) -> VortexResult<SendableArrayStream> {
596 let request = self.request;
597 let filter = request.filter?;
598 let mut builder = ScanBuilder::new(self.session, self.reader)
599 .with_selection(request.selection)
600 .with_projection(request.projection)
601 .with_some_filter(filter)
602 .with_ordered(request.ordered);
603 builder = match self.row_limit {
604 Some(row_limit) => builder.with_shared_limit(row_limit),
605 None => builder.with_some_limit(request.limit),
606 };
607
608 if let Some(row_range) = request.row_range {
609 builder = builder.with_row_range(row_range);
610 }
611
612 let dtype = builder.dtype()?;
613 let stream = builder.into_stream()?.boxed();
614
615 Ok(ArrayStreamExt::boxed(ArrayStreamAdapter::new(
616 dtype, stream,
617 )))
618 }
619}
620
621#[cfg(test)]
622mod tests {
623 use std::sync::Arc;
624
625 use async_trait::async_trait;
626 use futures::TryStreamExt;
627 use rstest::rstest;
628 use vortex_array::dtype::Nullability;
629 use vortex_array::dtype::PType;
630 use vortex_array::expr::eq;
631 use vortex_array::expr::lit;
632 use vortex_array::expr::root;
633 use vortex_error::VortexResult;
634 use vortex_io::runtime::BlockingRuntime;
635 use vortex_io::runtime::single::SingleThreadRuntime;
636 use vortex_scan::DataSource;
637 use vortex_scan::ScanRequest;
638
639 use super::*;
640 use crate::scan::test::TestLayoutReader;
641 use crate::scan::test::collect_scan_values;
642 use crate::scan::test::new_session;
643 use crate::scan::test::session_with_handle;
644
645 struct NeverOpened;
646
647 #[async_trait]
648 impl LayoutReaderFactory for NeverOpened {
649 async fn open(&self) -> VortexResult<Option<LayoutReaderRef>> {
650 unreachable!("byte_size must not open readers")
651 }
652 }
653
654 fn deferred_source(byte_sizes: Vec<Option<u64>>) -> MultiLayoutDataSource {
655 let factories: Vec<Arc<dyn LayoutReaderFactory>> = byte_sizes
656 .iter()
657 .map(|_| Arc::new(NeverOpened) as _)
658 .collect();
659 MultiLayoutDataSource::new_deferred(
660 DType::Bool(Nullability::NonNullable),
661 factories,
662 byte_sizes,
663 &new_session(),
664 )
665 }
666
667 #[rstest]
668 #[case::all_known(vec![Some(10), Some(20), Some(30)], Precision::exact(60u64))]
669 #[case::some_known_extrapolates(vec![Some(10), None, Some(30)], Precision::inexact(60u64))]
670 #[case::none_known(vec![None, None], Precision::Absent)]
671 #[case::no_children(vec![], Precision::exact(0u64))]
672 fn byte_size_precision(#[case] sizes: Vec<Option<u64>>, #[case] expected: Precision<u64>) {
673 assert_eq!(deferred_source(sizes).byte_size(), expected);
674 }
675
676 #[test]
677 fn filter_binding_errors_are_deferred() -> VortexResult<()> {
678 let dtype = DType::Primitive(PType::U8, Nullability::NonNullable);
679 let request = ScanRequest {
680 filter: Some(eq(root(), lit(67_i32))),
681 ..ScanRequest::default()
682 };
683
684 let request = BoundScanRequest::try_new(request, &dtype)?;
685
686 assert_eq!(request.projection.dtype(), &dtype);
687 assert!(request.filter.is_err());
688 Ok(())
689 }
690
691 struct StaticReaderFactory {
692 reader: LayoutReaderRef,
693 }
694
695 #[async_trait]
696 impl LayoutReaderFactory for StaticReaderFactory {
697 async fn open(&self) -> VortexResult<Option<LayoutReaderRef>> {
698 Ok(Some(Arc::clone(&self.reader)))
699 }
700 }
701
702 #[test]
705 fn unordered_limit_is_shared_across_readers() -> VortexResult<()> {
706 let runtime = SingleThreadRuntime::default();
707 let session = session_with_handle(runtime.handle());
708 let first: LayoutReaderRef = Arc::new(TestLayoutReader::new(2));
709 let second: LayoutReaderRef = Arc::new(TestLayoutReader::new(2).with_base(10));
710 let source = MultiLayoutDataSource::new_with_first(
711 first,
712 vec![Arc::new(StaticReaderFactory { reader: second })],
713 vec![],
714 &session,
715 );
716
717 let scan = runtime.block_on(source.scan(ScanRequest {
718 filter: Some(root()),
719 limit: Some(3),
720 ordered: false,
721 ..Default::default()
722 }))?;
723 let partitions = runtime.block_on(scan.partitions().try_collect::<Vec<_>>())?;
724 assert_eq!(partitions.len(), 2);
725
726 let mut values = Vec::new();
727 for partition in partitions {
728 values.extend(collect_scan_values(
729 runtime.block_on_stream(partition.execute()?),
730 )?);
731 }
732
733 assert_eq!(values.len(), 3);
735 Ok(())
736 }
737}