1use std::any::Any;
28use std::collections::VecDeque;
29use std::sync::Arc;
30
31use async_trait::async_trait;
32use futures::FutureExt;
33use futures::StreamExt;
34use futures::stream;
35use itertools::Itertools;
36use tracing::Instrument;
37use vortex_array::dtype::DType;
38use vortex_array::dtype::FieldPath;
39use vortex_array::expr::stats::Precision;
40use vortex_array::stats::StatsSet;
41use vortex_array::stream::ArrayStreamAdapter;
42use vortex_array::stream::ArrayStreamExt;
43use vortex_array::stream::SendableArrayStream;
44use vortex_error::VortexResult;
45use vortex_error::vortex_bail;
46use vortex_io::session::RuntimeSessionExt;
47use vortex_mask::Mask;
48use vortex_scan::DataSource;
49use vortex_scan::DataSourceScan;
50use vortex_scan::DataSourceScanRef;
51use vortex_scan::Partition;
52use vortex_scan::PartitionRef;
53use vortex_scan::PartitionStream;
54use vortex_scan::ScanRequest;
55use vortex_scan::selection::Selection;
56use vortex_session::VortexSession;
57use vortex_utils::parallelism::get_available_parallelism;
58
59use crate::LayoutReaderRef;
60use crate::scan::scan_builder::ScanBuilder;
61
62const DEFAULT_CONCURRENCY: usize = 8;
64
65#[async_trait]
71pub trait LayoutReaderFactory: 'static + Send + Sync {
72 async fn open(&self) -> VortexResult<Option<LayoutReaderRef>>;
74}
75
76pub struct MultiLayoutDataSource {
84 dtype: DType,
85 session: VortexSession,
86 children: Vec<MultiLayoutChild>,
87 concurrency: usize,
88}
89
90pub enum MultiLayoutChild {
91 Opened {
92 reader: LayoutReaderRef,
93 byte_size: Option<u64>,
95 },
96 Deferred {
97 factory: Arc<dyn LayoutReaderFactory>,
98 byte_size: Option<u64>,
100 },
101}
102
103impl MultiLayoutChild {
104 pub fn byte_size(&self) -> Option<u64> {
106 match self {
107 MultiLayoutChild::Opened { byte_size, .. } => *byte_size,
108 MultiLayoutChild::Deferred { byte_size, .. } => *byte_size,
109 }
110 }
111}
112
113impl MultiLayoutDataSource {
114 pub fn new_with_first(
121 first: LayoutReaderRef,
122 remaining: Vec<Arc<dyn LayoutReaderFactory>>,
123 byte_sizes: Vec<Option<u64>>,
124 session: &VortexSession,
125 ) -> Self {
126 let dtype = first.dtype().clone();
127 let concurrency = get_available_parallelism().unwrap_or(DEFAULT_CONCURRENCY);
128
129 let total = 1 + remaining.len();
130 let mut sizes = byte_sizes;
131 if sizes.is_empty() {
132 sizes = vec![None; total];
133 }
134 debug_assert_eq!(
135 sizes.len(),
136 total,
137 "byte_sizes length must match the number of children"
138 );
139
140 let mut children = Vec::with_capacity(total);
141 let mut sizes_iter = sizes.into_iter();
142 let first_size = sizes_iter.next().unwrap_or(None);
143 children.push(MultiLayoutChild::Opened {
144 reader: first,
145 byte_size: first_size,
146 });
147 children.extend(
148 remaining
149 .into_iter()
150 .zip_eq(sizes_iter)
151 .map(|(factory, byte_size)| MultiLayoutChild::Deferred { factory, byte_size }),
152 );
153
154 Self {
155 dtype,
156 session: session.clone(),
157 children,
158 concurrency,
159 }
160 }
161
162 pub fn new_deferred(
170 dtype: DType,
171 factories: Vec<Arc<dyn LayoutReaderFactory>>,
172 byte_sizes: Vec<Option<u64>>,
173 session: &VortexSession,
174 ) -> Self {
175 let concurrency = get_available_parallelism().unwrap_or(DEFAULT_CONCURRENCY);
176
177 let mut sizes = byte_sizes;
178 if sizes.is_empty() {
179 sizes = vec![None; factories.len()];
180 }
181 debug_assert_eq!(
182 sizes.len(),
183 factories.len(),
184 "byte_sizes length must match the number of factories"
185 );
186
187 Self {
188 dtype,
189 session: session.clone(),
190 children: factories
191 .into_iter()
192 .zip_eq(sizes)
193 .map(|(factory, byte_size)| MultiLayoutChild::Deferred { factory, byte_size })
194 .collect(),
195 concurrency,
196 }
197 }
198
199 pub fn children(&self) -> &Vec<MultiLayoutChild> {
200 &self.children
201 }
202
203 pub fn with_concurrency(mut self, concurrency: usize) -> Self {
208 self.concurrency = concurrency;
209 self
210 }
211}
212
213#[async_trait]
214impl DataSource for MultiLayoutDataSource {
215 fn dtype(&self) -> &DType {
216 &self.dtype
217 }
218
219 fn row_count(&self) -> Precision<u64> {
220 let mut sum: u64 = 0;
221 let mut opened_count: u64 = 0;
222 let mut deferred_count: u64 = 0;
223
224 for child in &self.children {
225 match child {
226 MultiLayoutChild::Opened { reader, .. } => {
227 opened_count += 1;
228 sum = sum.saturating_add(reader.row_count());
229 }
230 MultiLayoutChild::Deferred { .. } => {
231 deferred_count += 1;
232 }
233 }
234 }
235
236 let total_count = opened_count + deferred_count;
237 if total_count == 0 {
238 return Precision::exact(0u64);
239 }
240
241 if deferred_count == 0 {
242 Precision::exact(sum)
243 } else if opened_count > 0 {
244 let avg = sum / opened_count;
245 let extrapolated = avg.saturating_mul(total_count);
246 Precision::inexact(extrapolated)
247 } else {
248 Precision::Absent
249 }
250 }
251
252 fn byte_size(&self) -> Precision<u64> {
253 let total_count = self.children.len() as u64;
254 if total_count == 0 {
255 return Precision::exact(0u64);
256 }
257
258 let mut sum: u64 = 0;
259 let mut known_count: u64 = 0;
260 for child in &self.children {
261 if let Some(size) = child.byte_size() {
262 sum = sum.saturating_add(size);
263 known_count += 1;
264 }
265 }
266
267 if known_count == 0 {
268 return Precision::Absent;
269 }
270
271 if known_count == total_count {
272 Precision::exact(sum)
273 } else {
274 let avg = sum / known_count;
275 let extrapolated = avg.saturating_mul(total_count);
276 Precision::inexact(extrapolated)
277 }
278 }
279
280 fn deserialize_partition(
281 &self,
282 _data: &[u8],
283 _session: &VortexSession,
284 ) -> VortexResult<PartitionRef> {
285 vortex_bail!("MultiLayoutDataSource partitions are not yet serializable")
286 }
287
288 async fn scan(&self, scan_request: ScanRequest) -> VortexResult<DataSourceScanRef> {
289 let mut ready = VecDeque::new();
290 let mut deferred = VecDeque::new();
291
292 for child in &self.children {
293 match child {
294 MultiLayoutChild::Opened { reader, .. } => ready.push_back(Arc::clone(reader)),
295 MultiLayoutChild::Deferred { factory, .. } => {
296 deferred.push_back(Arc::clone(factory))
297 }
298 }
299 }
300
301 let dtype = scan_request.projection.return_dtype(&self.dtype)?;
302
303 Ok(Box::new(MultiLayoutScan {
304 session: self.session.clone(),
305 dtype,
306 request: scan_request,
307 ready,
308 deferred,
309 handle: self.session.handle(),
310 concurrency: self.concurrency,
311 }))
312 }
313
314 async fn field_statistics(&self, _field_path: &FieldPath) -> VortexResult<StatsSet> {
315 Ok(StatsSet::default())
316 }
317}
318
319struct MultiLayoutScan {
320 session: VortexSession,
321 dtype: DType,
322 request: ScanRequest,
323 ready: VecDeque<LayoutReaderRef>,
324 deferred: VecDeque<Arc<dyn LayoutReaderFactory>>,
325 handle: vortex_io::runtime::Handle,
326 concurrency: usize,
327}
328
329impl DataSourceScan for MultiLayoutScan {
330 fn dtype(&self) -> &DType {
331 &self.dtype
332 }
333
334 fn partition_count(&self) -> Precision<usize> {
335 let count = self.ready.len() + self.deferred.len();
336 if self.deferred.is_empty() {
337 Precision::exact(count)
338 } else {
339 Precision::inexact(count)
340 }
341 }
342
343 fn partitions(self: Box<Self>) -> PartitionStream {
344 let Self {
345 session,
346 dtype: _,
347 request,
348 ready,
349 deferred,
350 handle,
351 concurrency,
352 } = *self;
353
354 let ordered = request.ordered;
355
356 let ready_stream = stream::iter(ready).map(Ok);
358
359 let spawned = stream::iter(deferred).map(move |factory| {
363 handle.spawn(async move {
364 factory
365 .open()
366 .instrument(tracing::info_span!("LayoutReaderFactory::open"))
367 .await
368 })
369 });
370
371 let deferred_stream = if ordered {
372 spawned
373 .buffered(concurrency)
374 .filter_map(|result| async move {
375 match result {
376 Ok(Some(reader)) => Some(Ok(reader)),
377 Ok(None) => None,
378 Err(e) => Some(Err(e)),
379 }
380 })
381 .boxed()
382 } else {
383 spawned
384 .buffer_unordered(concurrency)
385 .filter_map(|result| async move {
386 match result {
387 Ok(Some(reader)) => Some(Ok(reader)),
388 Ok(None) => None,
389 Err(e) => Some(Err(e)),
390 }
391 })
392 .boxed()
393 };
394
395 ready_stream
399 .chain(deferred_stream)
400 .enumerate()
401 .flat_map(move |(i, reader_result)| match reader_result {
402 Ok(reader) => reader_partition(i, reader, session.clone(), request.clone()),
403 Err(e) => stream::once(async move { Err(e) }).boxed(),
404 })
405 .boxed()
406 }
407}
408
409fn reader_partition(
415 partition_idx: usize,
416 reader: LayoutReaderRef,
417 session: VortexSession,
418 request: ScanRequest,
419) -> PartitionStream {
420 let row_count = reader.row_count();
421 let row_range = request.row_range.clone().unwrap_or(0..row_count);
422
423 let partition_idx_u64: u64 = partition_idx as u64;
424 if let Some(range) = &request.partition_range
425 && !range.contains(&partition_idx_u64)
426 {
427 return stream::empty().boxed();
428 };
429 match &request.partition_selection {
430 Selection::IncludeByIndex(buffer) => {
431 if buffer.as_slice().binary_search(&partition_idx_u64).is_err() {
432 return stream::empty().boxed();
433 }
434 }
435 Selection::ExcludeByIndex(buffer) => {
436 if buffer.as_slice().binary_search(&partition_idx_u64).is_ok() {
437 return stream::empty().boxed();
438 }
439 }
440 _ => {}
441 };
442
443 if let Some(filter) = &request.filter {
446 let mask_len = usize::try_from(row_range.end - row_range.start).unwrap_or(usize::MAX);
447 let mask = Mask::new_true(mask_len);
448 if let Ok(pruning_future) = reader.pruning_evaluation(&row_range, filter, mask)
449 && let Some(Ok(result_mask)) = pruning_future.now_or_never()
450 && result_mask.all_false()
451 {
452 return stream::empty().boxed();
453 }
454 }
455
456 stream::once(async move {
457 Ok(Box::new(MultiLayoutPartition {
458 reader,
459 session,
460 request: ScanRequest {
461 row_range: Some(row_range),
462 ..request
463 },
464 index: partition_idx,
465 }) as PartitionRef)
466 })
467 .boxed()
468}
469
470struct MultiLayoutPartition {
475 reader: LayoutReaderRef,
476 session: VortexSession,
477 request: ScanRequest,
478 index: usize,
479}
480
481impl Partition for MultiLayoutPartition {
482 fn as_any(&self) -> &dyn Any {
483 self
484 }
485
486 fn index(&self) -> usize {
487 self.index
488 }
489
490 fn row_count(&self) -> Precision<u64> {
491 let Some(row_range) = self.request.row_range.as_ref() else {
492 return Precision::Absent;
493 };
494 let row_count = row_range.end - row_range.start;
495 let row_count = self.request.selection.row_count(row_count);
496 let row_count = self
497 .request
498 .limit
499 .map_or(row_count, |limit| row_count.min(limit));
500
501 if self.request.filter.is_some() {
502 Precision::inexact(row_count)
503 } else {
504 Precision::exact(row_count)
505 }
506 }
507
508 fn byte_size(&self) -> Precision<u64> {
509 Precision::Absent
510 }
511
512 fn execute(self: Box<Self>) -> VortexResult<SendableArrayStream> {
513 let request = self.request;
514 let mut builder = ScanBuilder::new(self.session, self.reader)
515 .with_selection(request.selection)
516 .with_projection(request.projection)
517 .with_some_filter(request.filter)
518 .with_some_limit(request.limit)
519 .with_ordered(request.ordered);
520
521 if let Some(row_range) = request.row_range {
522 builder = builder.with_row_range(row_range);
523 }
524
525 let dtype = builder.dtype()?;
526 let stream = builder.into_stream()?;
527
528 Ok(ArrayStreamExt::boxed(ArrayStreamAdapter::new(
529 dtype, stream,
530 )))
531 }
532}
533
534#[cfg(test)]
535mod tests {
536 use rstest::rstest;
537 use vortex_array::dtype::Nullability;
538
539 use super::*;
540 use crate::scan::test::new_session;
541
542 struct NeverOpened;
543
544 #[async_trait]
545 impl LayoutReaderFactory for NeverOpened {
546 async fn open(&self) -> VortexResult<Option<LayoutReaderRef>> {
547 unreachable!("byte_size must not open readers")
548 }
549 }
550
551 fn deferred_source(byte_sizes: Vec<Option<u64>>) -> MultiLayoutDataSource {
552 let factories: Vec<Arc<dyn LayoutReaderFactory>> = byte_sizes
553 .iter()
554 .map(|_| Arc::new(NeverOpened) as _)
555 .collect();
556 MultiLayoutDataSource::new_deferred(
557 DType::Bool(Nullability::NonNullable),
558 factories,
559 byte_sizes,
560 &new_session(),
561 )
562 }
563
564 #[rstest]
565 #[case::all_known(vec![Some(10), Some(20), Some(30)], Precision::exact(60u64))]
566 #[case::some_known_extrapolates(vec![Some(10), None, Some(30)], Precision::inexact(60u64))]
567 #[case::none_known(vec![None, None], Precision::Absent)]
568 #[case::no_children(vec![], Precision::exact(0u64))]
569 fn byte_size_precision(#[case] sizes: Vec<Option<u64>>, #[case] expected: Precision<u64>) {
570 assert_eq!(deferred_source(sizes).byte_size(), expected);
571 }
572}