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 opened_count > 0 {
249 let avg = sum / 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 Ok(Box::new(MultiLayoutScan {
310 session: self.session.clone(),
311 source_dtype: self.dtype.clone(),
312 dtype,
313 request,
314 ready,
315 deferred,
316 handle: self.session.handle(),
317 concurrency: self.concurrency,
318 }))
319 }
320
321 async fn field_statistics(&self, _field_path: &FieldPath) -> VortexResult<StatsSet> {
322 Ok(StatsSet::default())
323 }
324}
325
326#[derive(Clone)]
327struct BoundScanRequest {
328 projection: BoundExpression,
329 filter: SharedVortexResult<Option<BoundExpression>>,
330 row_range: Option<Range<u64>>,
331 selection: Selection,
332 partition_selection: Selection,
333 partition_range: Option<Range<u64>>,
334 ordered: bool,
335 limit: Option<u64>,
336}
337
338impl BoundScanRequest {
339 fn try_new(request: ScanRequest, dtype: &DType) -> VortexResult<Self> {
340 let ScanRequest {
341 projection,
342 filter,
343 row_range,
344 selection,
345 partition_selection,
346 partition_range,
347 ordered,
348 limit,
349 } = request;
350
351 Ok(Self {
352 projection: projection.optimize_recursive(dtype)?.bind(dtype)?,
353 filter: filter
354 .map(|expr| expr.optimize_recursive(dtype)?.bind(dtype))
355 .transpose()
356 .map_err(Arc::new),
357 row_range,
358 selection,
359 partition_selection,
360 partition_range,
361 ordered,
362 limit,
363 })
364 }
365}
366
367struct MultiLayoutScan {
368 session: VortexSession,
369 source_dtype: DType,
370 dtype: DType,
371 request: BoundScanRequest,
372 ready: VecDeque<LayoutReaderRef>,
373 deferred: VecDeque<Arc<dyn LayoutReaderFactory>>,
374 handle: vortex_io::runtime::Handle,
375 concurrency: usize,
376}
377
378impl DataSourceScan for MultiLayoutScan {
379 fn dtype(&self) -> &DType {
380 &self.dtype
381 }
382
383 fn partition_count(&self) -> Precision<usize> {
384 let count = self.ready.len() + self.deferred.len();
385 if self.deferred.is_empty() {
386 Precision::exact(count)
387 } else {
388 Precision::inexact(count)
389 }
390 }
391
392 fn partitions(self: Box<Self>) -> PartitionStream {
393 let Self {
394 session,
395 source_dtype,
396 dtype: _,
397 request,
398 ready,
399 deferred,
400 handle,
401 concurrency,
402 } = *self;
403
404 let ordered = request.ordered;
405
406 let ready_stream = stream::iter(ready).map(Ok);
408
409 let spawned = stream::iter(deferred).map(move |factory| {
413 handle.spawn(async move {
414 factory
415 .open()
416 .instrument(tracing::info_span!("LayoutReaderFactory::open"))
417 .await
418 })
419 });
420
421 let deferred_stream = if ordered {
422 spawned
423 .buffered(concurrency)
424 .filter_map(|result| async move {
425 match result {
426 Ok(Some(reader)) => Some(Ok(reader)),
427 Ok(None) => None,
428 Err(e) => Some(Err(e)),
429 }
430 })
431 .boxed()
432 } else {
433 spawned
434 .buffer_unordered(concurrency)
435 .filter_map(|result| async move {
436 match result {
437 Ok(Some(reader)) => Some(Ok(reader)),
438 Ok(None) => None,
439 Err(e) => Some(Err(e)),
440 }
441 })
442 .boxed()
443 };
444
445 ready_stream
449 .chain(deferred_stream)
450 .enumerate()
451 .flat_map(move |(i, reader_result)| match reader_result {
452 Ok(reader) => {
453 reader_partition(i, reader, session.clone(), &source_dtype, request.clone())
454 }
455 Err(e) => stream::once(async move { Err(e) }).boxed(),
456 })
457 .boxed()
458 }
459}
460
461fn reader_partition(
467 partition_idx: usize,
468 reader: LayoutReaderRef,
469 session: VortexSession,
470 source_dtype: &DType,
471 request: BoundScanRequest,
472) -> PartitionStream {
473 if reader.dtype() != source_dtype {
474 let error = vortex_err!(
475 "Multi-layout reader dtype mismatch: expected {}, got {}",
476 source_dtype,
477 reader.dtype()
478 );
479 return stream::once(async move { Err(error) }).boxed();
480 }
481
482 let row_count = reader.row_count();
483 let row_range = request.row_range.clone().unwrap_or(0..row_count);
484
485 let partition_idx_u64: u64 = partition_idx as u64;
486 if let Some(range) = &request.partition_range
487 && !range.contains(&partition_idx_u64)
488 {
489 return stream::empty().boxed();
490 };
491 match &request.partition_selection {
492 Selection::IncludeByIndex(buffer) => {
493 if buffer.as_slice().binary_search(&partition_idx_u64).is_err() {
494 return stream::empty().boxed();
495 }
496 }
497 Selection::ExcludeByIndex(buffer) => {
498 if buffer.as_slice().binary_search(&partition_idx_u64).is_ok() {
499 return stream::empty().boxed();
500 }
501 }
502 _ => {}
503 };
504
505 if let Ok(Some(filter)) = &request.filter {
508 let mask_len = usize::try_from(row_range.end - row_range.start).unwrap_or(usize::MAX);
509 let mask = Mask::new_true(mask_len);
510 if let Ok(pruning_future) = reader.pruning_evaluation(&row_range, filter, mask)
511 && let Some(Ok(result_mask)) = pruning_future.now_or_never()
512 && result_mask.all_false()
513 {
514 return stream::empty().boxed();
515 }
516 }
517
518 stream::once(async move {
519 Ok(Box::new(MultiLayoutPartition {
520 reader,
521 session,
522 request: BoundScanRequest {
523 row_range: Some(row_range),
524 ..request
525 },
526 index: partition_idx,
527 }) as PartitionRef)
528 })
529 .boxed()
530}
531
532struct MultiLayoutPartition {
537 reader: LayoutReaderRef,
538 session: VortexSession,
539 request: BoundScanRequest,
540 index: usize,
541}
542
543impl Partition for MultiLayoutPartition {
544 fn as_any(&self) -> &dyn Any {
545 self
546 }
547
548 fn index(&self) -> usize {
549 self.index
550 }
551
552 fn row_count(&self) -> Precision<u64> {
553 let Some(row_range) = self.request.row_range.as_ref() else {
554 return Precision::Absent;
555 };
556 let row_count = row_range.end - row_range.start;
557 let row_count = self.request.selection.row_count(row_count);
558 let row_count = self
559 .request
560 .limit
561 .map_or(row_count, |limit| row_count.min(limit));
562
563 let has_filter = match &self.request.filter {
564 Ok(filter) => filter.is_some(),
565 Err(_) => true,
566 };
567 if has_filter {
568 Precision::inexact(row_count)
569 } else {
570 Precision::exact(row_count)
571 }
572 }
573
574 fn byte_size(&self) -> Precision<u64> {
575 Precision::Absent
576 }
577
578 fn execute(self: Box<Self>) -> VortexResult<SendableArrayStream> {
579 let request = self.request;
580 let filter = request.filter?;
581 let mut builder = ScanBuilder::new(self.session, self.reader)
582 .with_selection(request.selection)
583 .with_projection(request.projection)
584 .with_some_filter(filter)
585 .with_some_limit(request.limit)
586 .with_ordered(request.ordered);
587
588 if let Some(row_range) = request.row_range {
589 builder = builder.with_row_range(row_range);
590 }
591
592 let dtype = builder.dtype()?;
593 let stream = builder.into_stream()?;
594
595 Ok(ArrayStreamExt::boxed(ArrayStreamAdapter::new(
596 dtype, stream,
597 )))
598 }
599}
600
601#[cfg(test)]
602mod tests {
603 use rstest::rstest;
604 use vortex_array::dtype::Nullability;
605 use vortex_array::dtype::PType;
606 use vortex_array::expr::eq;
607 use vortex_array::expr::lit;
608 use vortex_array::expr::root;
609
610 use super::*;
611 use crate::scan::test::new_session;
612
613 struct NeverOpened;
614
615 #[async_trait]
616 impl LayoutReaderFactory for NeverOpened {
617 async fn open(&self) -> VortexResult<Option<LayoutReaderRef>> {
618 unreachable!("byte_size must not open readers")
619 }
620 }
621
622 fn deferred_source(byte_sizes: Vec<Option<u64>>) -> MultiLayoutDataSource {
623 let factories: Vec<Arc<dyn LayoutReaderFactory>> = byte_sizes
624 .iter()
625 .map(|_| Arc::new(NeverOpened) as _)
626 .collect();
627 MultiLayoutDataSource::new_deferred(
628 DType::Bool(Nullability::NonNullable),
629 factories,
630 byte_sizes,
631 &new_session(),
632 )
633 }
634
635 #[rstest]
636 #[case::all_known(vec![Some(10), Some(20), Some(30)], Precision::exact(60u64))]
637 #[case::some_known_extrapolates(vec![Some(10), None, Some(30)], Precision::inexact(60u64))]
638 #[case::none_known(vec![None, None], Precision::Absent)]
639 #[case::no_children(vec![], Precision::exact(0u64))]
640 fn byte_size_precision(#[case] sizes: Vec<Option<u64>>, #[case] expected: Precision<u64>) {
641 assert_eq!(deferred_source(sizes).byte_size(), expected);
642 }
643
644 #[test]
645 fn filter_binding_errors_are_deferred() -> VortexResult<()> {
646 let dtype = DType::Primitive(PType::U8, Nullability::NonNullable);
647 let request = ScanRequest {
648 filter: Some(eq(root(), lit(67_i32))),
649 ..ScanRequest::default()
650 };
651
652 let request = BoundScanRequest::try_new(request, &dtype)?;
653
654 assert_eq!(request.projection.dtype(), &dtype);
655 assert!(request.filter.is_err());
656 Ok(())
657 }
658}