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::scan_builder::ScanBuilder;
65
66const DEFAULT_CONCURRENCY: usize = 8;
68
69#[async_trait]
75pub trait LayoutReaderFactory: 'static + Send + Sync {
76 async fn open(&self) -> VortexResult<Option<LayoutReaderRef>>;
78}
79
80#[derive(Clone)]
88pub struct MultiLayoutDataSource {
89 dtype: DType,
90 session: VortexSession,
91 children: Arc<[MultiLayoutChild]>,
92 concurrency: usize,
93}
94
95pub enum MultiLayoutChild {
96 Opened {
97 reader: LayoutReaderRef,
98 byte_size: Option<u64>,
100 },
101 Deferred {
102 factory: Arc<dyn LayoutReaderFactory>,
103 byte_size: Option<u64>,
105 },
106}
107
108impl MultiLayoutChild {
109 pub fn byte_size(&self) -> Option<u64> {
111 match self {
112 MultiLayoutChild::Opened { byte_size, .. } => *byte_size,
113 MultiLayoutChild::Deferred { byte_size, .. } => *byte_size,
114 }
115 }
116}
117
118impl MultiLayoutDataSource {
119 pub fn new_with_first(
126 first: LayoutReaderRef,
127 remaining: Vec<Arc<dyn LayoutReaderFactory>>,
128 byte_sizes: Vec<Option<u64>>,
129 session: &VortexSession,
130 ) -> Self {
131 let dtype = first.dtype().clone();
132 let concurrency = get_available_parallelism().unwrap_or(DEFAULT_CONCURRENCY);
133
134 let total = 1 + remaining.len();
135 let mut sizes = byte_sizes;
136 if sizes.is_empty() {
137 sizes = vec![None; total];
138 }
139 debug_assert_eq!(
140 sizes.len(),
141 total,
142 "byte_sizes length must match the number of children"
143 );
144
145 let mut children = Vec::with_capacity(total);
146 let mut sizes_iter = sizes.into_iter();
147 let first_size = sizes_iter.next().unwrap_or(None);
148 children.push(MultiLayoutChild::Opened {
149 reader: first,
150 byte_size: first_size,
151 });
152 children.extend(
153 remaining
154 .into_iter()
155 .zip_eq(sizes_iter)
156 .map(|(factory, byte_size)| MultiLayoutChild::Deferred { factory, byte_size }),
157 );
158
159 Self {
160 dtype,
161 session: session.clone(),
162 children: children.into(),
163 concurrency,
164 }
165 }
166
167 pub fn new_deferred(
175 dtype: DType,
176 factories: Vec<Arc<dyn LayoutReaderFactory>>,
177 byte_sizes: Vec<Option<u64>>,
178 session: &VortexSession,
179 ) -> Self {
180 let concurrency = get_available_parallelism().unwrap_or(DEFAULT_CONCURRENCY);
181
182 let mut sizes = byte_sizes;
183 if sizes.is_empty() {
184 sizes = vec![None; factories.len()];
185 }
186 debug_assert_eq!(
187 sizes.len(),
188 factories.len(),
189 "byte_sizes length must match the number of factories"
190 );
191
192 Self {
193 dtype,
194 session: session.clone(),
195 children: factories
196 .into_iter()
197 .zip_eq(sizes)
198 .map(|(factory, byte_size)| MultiLayoutChild::Deferred { factory, byte_size })
199 .collect(),
200 concurrency,
201 }
202 }
203
204 pub fn children(&self) -> &[MultiLayoutChild] {
205 &self.children
206 }
207
208 pub fn with_concurrency(mut self, concurrency: usize) -> Self {
213 self.concurrency = concurrency;
214 self
215 }
216}
217
218#[async_trait]
219impl DataSource for MultiLayoutDataSource {
220 fn dtype(&self) -> &DType {
221 &self.dtype
222 }
223
224 fn row_count(&self) -> Precision<u64> {
225 let mut sum: u64 = 0;
226 let mut opened_count: u64 = 0;
227 let mut deferred_count: u64 = 0;
228
229 for child in self.children.iter() {
230 match child {
231 MultiLayoutChild::Opened { reader, .. } => {
232 opened_count += 1;
233 sum = sum.saturating_add(reader.row_count());
234 }
235 MultiLayoutChild::Deferred { .. } => {
236 deferred_count += 1;
237 }
238 }
239 }
240
241 let total_count = opened_count + deferred_count;
242 if total_count == 0 {
243 return Precision::exact(0u64);
244 }
245
246 if deferred_count == 0 {
247 Precision::exact(sum)
248 } else if let Some(avg) = sum.checked_div(opened_count) {
249 let extrapolated = avg.saturating_mul(total_count);
250 Precision::inexact(extrapolated)
251 } else {
252 Precision::Absent
253 }
254 }
255
256 fn byte_size(&self) -> Precision<u64> {
257 let total_count = self.children.len() as u64;
258 if total_count == 0 {
259 return Precision::exact(0u64);
260 }
261
262 let mut sum: u64 = 0;
263 let mut known_count: u64 = 0;
264 for child in self.children.iter() {
265 if let Some(size) = child.byte_size() {
266 sum = sum.saturating_add(size);
267 known_count += 1;
268 }
269 }
270
271 if known_count == 0 {
272 return Precision::Absent;
273 }
274
275 if known_count == total_count {
276 Precision::exact(sum)
277 } else {
278 let avg = sum / known_count;
279 let extrapolated = avg.saturating_mul(total_count);
280 Precision::inexact(extrapolated)
281 }
282 }
283
284 fn deserialize_partition(
285 &self,
286 _data: &[u8],
287 _session: &VortexSession,
288 ) -> VortexResult<PartitionRef> {
289 vortex_bail!("MultiLayoutDataSource partitions are not yet serializable")
290 }
291
292 async fn scan(&self, scan_request: ScanRequest) -> VortexResult<DataSourceScanRef> {
293 let mut ready = VecDeque::new();
294 let mut deferred = VecDeque::new();
295
296 for child in self.children.iter() {
297 match child {
298 MultiLayoutChild::Opened { reader, .. } => ready.push_back(Arc::clone(reader)),
299 MultiLayoutChild::Deferred { factory, .. } => {
300 deferred.push_back(Arc::clone(factory))
301 }
302 }
303 }
304
305 let request = BoundScanRequest::try_new(scan_request, &self.dtype)?;
306 let dtype = request.projection.dtype().clone();
307
308 Ok(Box::new(MultiLayoutScan {
309 session: self.session.clone(),
310 source_dtype: self.dtype.clone(),
311 dtype,
312 request,
313 ready,
314 deferred,
315 handle: self.session.handle(),
316 concurrency: self.concurrency,
317 }))
318 }
319
320 async fn field_statistics(&self, _field_path: &FieldPath) -> VortexResult<StatsSet> {
321 Ok(StatsSet::default())
322 }
323}
324
325#[derive(Clone)]
326struct BoundScanRequest {
327 projection: BoundExpression,
328 filter: SharedVortexResult<Option<BoundExpression>>,
329 row_range: Option<Range<u64>>,
330 selection: Selection,
331 partition_selection: Selection,
332 partition_range: Option<Range<u64>>,
333 ordered: bool,
334 limit: Option<u64>,
335}
336
337impl BoundScanRequest {
338 fn try_new(request: ScanRequest, dtype: &DType) -> VortexResult<Self> {
339 let ScanRequest {
340 projection,
341 filter,
342 row_range,
343 selection,
344 partition_selection,
345 partition_range,
346 ordered,
347 limit,
348 } = request;
349
350 Ok(Self {
351 projection: projection.bind(dtype)?.optimize_recursive()?,
352 filter: filter
353 .map(|expr| expr.bind(dtype)?.optimize_recursive())
354 .transpose()
355 .map_err(Arc::new),
356 row_range,
357 selection,
358 partition_selection,
359 partition_range,
360 ordered,
361 limit,
362 })
363 }
364}
365
366struct MultiLayoutScan {
367 session: VortexSession,
368 source_dtype: DType,
369 dtype: DType,
370 request: BoundScanRequest,
371 ready: VecDeque<LayoutReaderRef>,
372 deferred: VecDeque<Arc<dyn LayoutReaderFactory>>,
373 handle: vortex_io::runtime::Handle,
374 concurrency: usize,
375}
376
377impl DataSourceScan for MultiLayoutScan {
378 fn dtype(&self) -> &DType {
379 &self.dtype
380 }
381
382 fn partition_count(&self) -> Precision<usize> {
383 let count = self.ready.len() + self.deferred.len();
384 if self.deferred.is_empty() {
385 Precision::exact(count)
386 } else {
387 Precision::inexact(count)
388 }
389 }
390
391 fn partitions(self: Box<Self>) -> PartitionStream {
392 let Self {
393 session,
394 source_dtype,
395 dtype: _,
396 request,
397 ready,
398 deferred,
399 handle,
400 concurrency,
401 } = *self;
402
403 let ordered = request.ordered;
404
405 let ready_stream = stream::iter(ready).map(Ok);
407
408 let spawned = stream::iter(deferred).map(move |factory| {
412 handle.spawn(async move {
413 factory
414 .open()
415 .instrument(tracing::info_span!("LayoutReaderFactory::open"))
416 .await
417 })
418 });
419
420 let deferred_stream = if ordered {
421 spawned
422 .buffered(concurrency)
423 .filter_map(|result| async move {
424 match result {
425 Ok(Some(reader)) => Some(Ok(reader)),
426 Ok(None) => None,
427 Err(e) => Some(Err(e)),
428 }
429 })
430 .boxed()
431 } else {
432 spawned
433 .buffer_unordered(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 };
443
444 ready_stream
448 .chain(deferred_stream)
449 .enumerate()
450 .flat_map(move |(i, reader_result)| match reader_result {
451 Ok(reader) => {
452 reader_partition(i, reader, session.clone(), &source_dtype, request.clone())
453 }
454 Err(e) => stream::once(async move { Err(e) }).boxed(),
455 })
456 .boxed()
457 }
458}
459
460fn reader_partition(
466 partition_idx: usize,
467 reader: LayoutReaderRef,
468 session: VortexSession,
469 source_dtype: &DType,
470 request: BoundScanRequest,
471) -> PartitionStream {
472 if reader.dtype() != source_dtype {
473 let error = vortex_err!(
474 "Multi-layout reader dtype mismatch: expected {}, got {}",
475 source_dtype,
476 reader.dtype()
477 );
478 return stream::once(async move { Err(error) }).boxed();
479 }
480
481 let row_count = reader.row_count();
482 let row_range = request.row_range.clone().unwrap_or(0..row_count);
483
484 let partition_idx_u64: u64 = partition_idx as u64;
485 if let Some(range) = &request.partition_range
486 && !range.contains(&partition_idx_u64)
487 {
488 return stream::empty().boxed();
489 };
490 match &request.partition_selection {
491 Selection::IncludeByIndex(buffer)
492 if buffer.as_slice().binary_search(&partition_idx_u64).is_err() =>
493 {
494 return stream::empty().boxed();
495 }
496 Selection::ExcludeByIndex(buffer)
497 if buffer.as_slice().binary_search(&partition_idx_u64).is_ok() =>
498 {
499 return stream::empty().boxed();
500 }
501 _ => {}
502 };
503
504 if let Ok(Some(filter)) = &request.filter {
507 let mask_len = usize::try_from(row_range.end - row_range.start).unwrap_or(usize::MAX);
508 let mask = Mask::new_true(mask_len);
509 if let Ok(pruning_future) = reader.pruning_evaluation(&row_range, filter, mask)
510 && let Some(Ok(result_mask)) = pruning_future.now_or_never()
511 && result_mask.all_false()
512 {
513 return stream::empty().boxed();
514 }
515 }
516
517 stream::once(async move {
518 Ok(Box::new(MultiLayoutPartition {
519 reader,
520 session,
521 request: BoundScanRequest {
522 row_range: Some(row_range),
523 ..request
524 },
525 index: partition_idx,
526 }) as PartitionRef)
527 })
528 .boxed()
529}
530
531struct MultiLayoutPartition {
536 reader: LayoutReaderRef,
537 session: VortexSession,
538 request: BoundScanRequest,
539 index: usize,
540}
541
542impl Partition for MultiLayoutPartition {
543 fn as_any(&self) -> &dyn Any {
544 self
545 }
546
547 fn index(&self) -> usize {
548 self.index
549 }
550
551 fn row_count(&self) -> Precision<u64> {
552 let Some(row_range) = self.request.row_range.as_ref() else {
553 return Precision::Absent;
554 };
555 let row_count = row_range.end - row_range.start;
556 let row_count = self.request.selection.row_count(row_count);
557 let row_count = self
558 .request
559 .limit
560 .map_or(row_count, |limit| row_count.min(limit));
561
562 let has_filter = match &self.request.filter {
563 Ok(filter) => filter.is_some(),
564 Err(_) => true,
565 };
566 if has_filter {
567 Precision::inexact(row_count)
568 } else {
569 Precision::exact(row_count)
570 }
571 }
572
573 fn byte_size(&self) -> Precision<u64> {
574 Precision::Absent
575 }
576
577 fn execute(self: Box<Self>) -> VortexResult<SendableArrayStream> {
578 let request = self.request;
579 let filter = request.filter?;
580 let mut builder = ScanBuilder::new(self.session, self.reader)
581 .with_selection(request.selection)
582 .with_projection(request.projection)
583 .with_some_filter(filter)
584 .with_some_limit(request.limit)
585 .with_ordered(request.ordered);
586
587 if let Some(row_range) = request.row_range {
588 builder = builder.with_row_range(row_range);
589 }
590
591 let dtype = builder.dtype()?;
592 let stream = builder.into_stream()?;
593
594 Ok(ArrayStreamExt::boxed(ArrayStreamAdapter::new(
595 dtype, stream,
596 )))
597 }
598}
599
600#[cfg(test)]
601mod tests {
602 use rstest::rstest;
603 use vortex_array::dtype::Nullability;
604 use vortex_array::dtype::PType;
605 use vortex_array::expr::eq;
606 use vortex_array::expr::lit;
607 use vortex_array::expr::root;
608
609 use super::*;
610 use crate::scan::test::new_session;
611
612 struct NeverOpened;
613
614 #[async_trait]
615 impl LayoutReaderFactory for NeverOpened {
616 async fn open(&self) -> VortexResult<Option<LayoutReaderRef>> {
617 unreachable!("byte_size must not open readers")
618 }
619 }
620
621 fn deferred_source(byte_sizes: Vec<Option<u64>>) -> MultiLayoutDataSource {
622 let factories: Vec<Arc<dyn LayoutReaderFactory>> = byte_sizes
623 .iter()
624 .map(|_| Arc::new(NeverOpened) as _)
625 .collect();
626 MultiLayoutDataSource::new_deferred(
627 DType::Bool(Nullability::NonNullable),
628 factories,
629 byte_sizes,
630 &new_session(),
631 )
632 }
633
634 #[rstest]
635 #[case::all_known(vec![Some(10), Some(20), Some(30)], Precision::exact(60u64))]
636 #[case::some_known_extrapolates(vec![Some(10), None, Some(30)], Precision::inexact(60u64))]
637 #[case::none_known(vec![None, None], Precision::Absent)]
638 #[case::no_children(vec![], Precision::exact(0u64))]
639 fn byte_size_precision(#[case] sizes: Vec<Option<u64>>, #[case] expected: Precision<u64>) {
640 assert_eq!(deferred_source(sizes).byte_size(), expected);
641 }
642
643 #[test]
644 fn filter_binding_errors_are_deferred() -> VortexResult<()> {
645 let dtype = DType::Primitive(PType::U8, Nullability::NonNullable);
646 let request = ScanRequest {
647 filter: Some(eq(root(), lit(67_i32))),
648 ..ScanRequest::default()
649 };
650
651 let request = BoundScanRequest::try_new(request, &dtype)?;
652
653 assert_eq!(request.projection.dtype(), &dtype);
654 assert!(request.filter.is_err());
655 Ok(())
656 }
657}