1use std::any::Any;
5use std::ops::Range;
6use std::pin::Pin;
7use std::sync::Arc;
8use std::task::Context;
9use std::task::Poll;
10
11use async_trait::async_trait;
12use futures::FutureExt;
13use futures::Stream;
14use futures::stream;
15use futures::stream::StreamExt;
16use vortex_array::IntoArray;
17use vortex_array::arrays::ConstantArray;
18use vortex_array::dtype::DType;
19use vortex_array::dtype::FieldPath;
20use vortex_array::dtype::Nullability;
21use vortex_array::expr::BoundExpression;
22use vortex_array::expr::stats::Precision;
23use vortex_array::scalar::Scalar;
24use vortex_array::stats::StatsSet;
25use vortex_array::stream::ArrayStreamAdapter;
26use vortex_array::stream::ArrayStreamExt;
27use vortex_array::stream::SendableArrayStream;
28use vortex_error::VortexResult;
29use vortex_error::vortex_bail;
30use vortex_mask::Mask;
31use vortex_metrics::MetricsRegistry;
32use vortex_scan::DataSource;
33use vortex_scan::DataSourceScan;
34use vortex_scan::DataSourceScanRef;
35use vortex_scan::Partition;
36use vortex_scan::PartitionRef;
37use vortex_scan::PartitionStream;
38use vortex_scan::ScanRequest;
39use vortex_scan::selection::Selection;
40use vortex_session::VortexSession;
41
42use crate::LayoutReaderRef;
43use crate::scan::limit::RowLimit;
44use crate::scan::scan_builder::ScanBuilder;
45
46pub struct LayoutReaderDataSource {
48 reader: LayoutReaderRef,
49 session: VortexSession,
50 split_max_row_count: u64,
51 metrics_registry: Option<Arc<dyn MetricsRegistry>>,
52}
53
54impl LayoutReaderDataSource {
55 pub fn new(reader: LayoutReaderRef, session: VortexSession) -> Self {
62 Self {
63 reader,
64 session,
65 split_max_row_count: u64::MAX,
66 metrics_registry: None,
67 }
68 }
69
70 pub fn with_split_max_row_count(mut self, row_count: u64) -> Self {
76 self.split_max_row_count = row_count;
77 self
78 }
79
80 pub fn with_metrics_registry(mut self, metrics: Arc<dyn MetricsRegistry>) -> Self {
82 self.metrics_registry = Some(metrics);
83 self
84 }
85
86 pub fn with_some_metrics_registry(mut self, metrics: Option<Arc<dyn MetricsRegistry>>) -> Self {
88 self.metrics_registry = metrics;
89 self
90 }
91}
92
93#[async_trait]
94impl DataSource for LayoutReaderDataSource {
95 fn dtype(&self) -> &DType {
96 self.reader.dtype()
97 }
98
99 fn row_count(&self) -> Precision<u64> {
100 Precision::exact(self.reader.row_count())
101 }
102
103 fn byte_size(&self) -> Precision<u64> {
104 Precision::Absent
105 }
106
107 fn deserialize_partition(
108 &self,
109 _data: &[u8],
110 _session: &VortexSession,
111 ) -> VortexResult<PartitionRef> {
112 vortex_bail!("LayoutReader splits are not yet serializable");
113 }
114
115 async fn scan(&self, scan_request: ScanRequest) -> VortexResult<DataSourceScanRef> {
116 let total_rows = self.reader.row_count();
117 let row_range = scan_request.row_range.unwrap_or(0..total_rows);
118
119 let projection = scan_request
120 .projection
121 .bind(self.reader.dtype())?
122 .optimize_recursive()?;
123 let filter = scan_request
124 .filter
125 .map(|expr| expr.bind(self.reader.dtype())?.optimize_recursive())
126 .transpose()?;
127 let dtype = projection.dtype().clone();
128
129 if let DType::Struct(fields, Nullability::NonNullable) = &dtype
132 && fields.nfields() == 0
133 && filter.is_none()
134 {
135 let row_count = scan_request.selection.row_count_in_range(&row_range);
137
138 let row_count = if let Some(limit) = scan_request.limit {
140 row_count.min(limit)
141 } else {
142 row_count
143 };
144
145 return Ok(Box::new(Empty { dtype, row_count }));
146 }
147
148 if let Some(filter) = &filter {
151 let mask = Mask::new_true(
152 usize::try_from(row_range.end - row_range.start).unwrap_or(usize::MAX),
153 );
154 let pruning_result = self
155 .reader
156 .pruning_evaluation(&row_range, filter, mask)?
157 .now_or_never();
158 if let Some(Ok(result_mask)) = pruning_result
159 && result_mask.all_false()
160 {
161 return Ok(Box::new(Empty {
162 dtype,
163 row_count: 0,
164 }));
165 }
166 }
167
168 let row_limit = (!scan_request.ordered)
172 .then(|| scan_request.limit.map(RowLimit::new))
173 .flatten();
174
175 Ok(Box::new(LayoutReaderScan {
176 reader: Arc::clone(&self.reader),
177 session: self.session.clone(),
178 dtype,
179 projection,
180 filter,
181 remaining_limit: scan_request.limit,
182 row_limit,
183 selection: scan_request.selection,
184 ordered: scan_request.ordered,
185 metrics_registry: self.metrics_registry.clone(),
186 next_row: row_range.start,
187 end_row: row_range.end,
188 split_size: self.split_max_row_count,
189 }))
190 }
191
192 async fn field_statistics(&self, _field_path: &FieldPath) -> VortexResult<StatsSet> {
193 Ok(StatsSet::default())
194 }
195}
196
197struct LayoutReaderScan {
198 reader: LayoutReaderRef,
199 session: VortexSession,
200 dtype: DType,
201 projection: BoundExpression,
202 filter: Option<BoundExpression>,
203 remaining_limit: Option<u64>,
207 row_limit: Option<RowLimit>,
209 ordered: bool,
210 selection: Selection,
211 metrics_registry: Option<Arc<dyn MetricsRegistry>>,
212 next_row: u64,
213 end_row: u64,
214 split_size: u64,
215}
216
217impl DataSourceScan for LayoutReaderScan {
218 fn dtype(&self) -> &DType {
219 &self.dtype
220 }
221
222 fn partition_count(&self) -> Precision<usize> {
223 let (lower, upper) = self.size_hint();
224 match upper {
225 Some(u) if u == lower => Precision::exact(lower),
226 Some(u) => Precision::inexact(u),
227 None => Precision::inexact(lower),
228 }
229 }
230
231 fn partitions(self: Box<Self>) -> PartitionStream {
232 (*self).boxed()
233 }
234}
235
236impl Stream for LayoutReaderScan {
237 type Item = VortexResult<PartitionRef>;
238
239 fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
240 let this = self.get_mut();
241
242 if this.next_row >= this.end_row {
243 return Poll::Ready(None);
244 }
245
246 if this.remaining_limit.is_some_and(|limit| limit == 0) {
247 return Poll::Ready(None);
248 }
249 if this.row_limit.as_ref().is_some_and(RowLimit::is_exhausted) {
250 return Poll::Ready(None);
251 }
252
253 let split_end = this
254 .next_row
255 .saturating_add(this.split_size)
256 .min(this.end_row);
257 let row_range = this.next_row..split_end;
258
259 let split_limit = this.remaining_limit;
261 if this.filter.is_none()
266 && let Some(ref mut remaining) = this.remaining_limit
267 {
268 *remaining = remaining.saturating_sub(this.selection.row_count_in_range(&row_range));
269 }
270
271 let split = Box::new(LayoutReaderSplit {
272 reader: Arc::clone(&this.reader),
273 session: this.session.clone(),
274 projection: this.projection.clone(),
275 filter: this.filter.clone(),
276 limit: split_limit,
277 row_limit: this.row_limit.clone(),
278 ordered: this.ordered,
279 row_range,
280 selection: this.selection.clone(),
281 metrics_registry: this.metrics_registry.clone(),
282 }) as PartitionRef;
283
284 this.next_row = split_end;
285
286 Poll::Ready(Some(Ok(split)))
287 }
288
289 fn size_hint(&self) -> (usize, Option<usize>) {
290 if self.next_row >= self.end_row
291 || self.remaining_limit.is_some_and(|limit| limit == 0)
292 || self.row_limit.as_ref().is_some_and(RowLimit::is_exhausted)
293 {
294 return (0, Some(0));
295 }
296 let remaining_rows = self.end_row - self.next_row;
297 let splits = remaining_rows.div_ceil(self.split_size);
298 (0, Some(usize::try_from(splits).unwrap_or(usize::MAX)))
299 }
300}
301
302struct LayoutReaderSplit {
303 reader: LayoutReaderRef,
304 session: VortexSession,
305 projection: BoundExpression,
306 filter: Option<BoundExpression>,
307 limit: Option<u64>,
309 row_limit: Option<RowLimit>,
311 ordered: bool,
312 row_range: Range<u64>,
313 selection: Selection,
314 metrics_registry: Option<Arc<dyn MetricsRegistry>>,
315}
316
317impl Partition for LayoutReaderSplit {
318 fn as_any(&self) -> &dyn Any {
319 self
320 }
321
322 #[expect(clippy::cast_possible_truncation)]
323 fn index(&self) -> usize {
324 self.row_range.start as usize
326 }
327
328 fn row_count(&self) -> Precision<u64> {
329 let row_count = self.selection.row_count_in_range(&self.row_range);
330 let row_count = self.limit.map_or(row_count, |limit| row_count.min(limit));
331
332 if self.filter.is_some() || self.row_limit.is_some() {
333 Precision::inexact(row_count)
334 } else {
335 Precision::exact(row_count)
336 }
337 }
338
339 fn byte_size(&self) -> Precision<u64> {
340 Precision::Absent
341 }
342
343 fn execute(self: Box<Self>) -> VortexResult<SendableArrayStream> {
344 let builder = ScanBuilder::new(self.session, self.reader)
345 .with_row_range(self.row_range)
346 .with_selection(self.selection)
347 .with_projection(self.projection)
348 .with_some_filter(self.filter)
349 .with_some_metrics_registry(self.metrics_registry)
350 .with_ordered(self.ordered);
351 let builder = match self.row_limit {
352 Some(row_limit) => builder.with_shared_limit(row_limit),
353 None => builder.with_some_limit(self.limit),
354 };
355
356 let dtype = builder.dtype()?;
357 let stream = builder.into_stream()?.boxed();
360
361 Ok(ArrayStreamExt::boxed(ArrayStreamAdapter::new(
362 dtype, stream,
363 )))
364 }
365}
366
367struct Empty {
369 dtype: DType,
370 row_count: u64,
371}
372
373impl DataSourceScan for Empty {
374 fn dtype(&self) -> &DType {
375 &self.dtype
376 }
377
378 fn partition_count(&self) -> Precision<usize> {
379 Precision::exact(1usize)
380 }
381
382 fn partitions(self: Box<Self>) -> PartitionStream {
383 stream::iter([Ok(self as _)]).boxed()
384 }
385}
386
387impl Partition for Empty {
388 fn as_any(&self) -> &dyn Any {
389 self
390 }
391
392 fn index(&self) -> usize {
393 0
394 }
395
396 fn row_count(&self) -> Precision<u64> {
397 Precision::exact(self.row_count)
398 }
399
400 fn byte_size(&self) -> Precision<u64> {
401 Precision::exact(0u64)
402 }
403
404 fn execute(mut self: Box<Self>) -> VortexResult<SendableArrayStream> {
405 let scalar = Scalar::default_value(&self.dtype);
406 let dtype = self.dtype.clone();
407
408 let iter = std::iter::from_fn(move || {
410 if self.row_count == 0 {
411 return None;
412 }
413 let chunk_size = usize::try_from(self.row_count).unwrap_or(usize::MAX);
414 self.row_count -= chunk_size as u64;
415 Some(VortexResult::Ok(
416 ConstantArray::new(scalar.clone(), chunk_size).into_array(),
417 ))
418 });
419
420 Ok(ArrayStreamExt::boxed(ArrayStreamAdapter::new(
421 dtype,
422 stream::iter(iter),
423 )))
424 }
425}
426
427#[cfg(test)]
428mod tests {
429 use std::sync::Arc;
430
431 use futures::StreamExt;
432 use futures::TryStreamExt;
433 use rstest::rstest;
434 use vortex_array::expr::root;
435 use vortex_buffer::Buffer;
436 use vortex_error::VortexResult;
437 use vortex_io::runtime::BlockingRuntime;
438 use vortex_io::runtime::single::SingleThreadRuntime;
439 use vortex_scan::DataSource;
440 use vortex_scan::ScanRequest;
441 use vortex_scan::selection::Selection;
442 use vortex_scan::strict_sorted_buffer::StrictSortedBuffer;
443
444 use super::LayoutReaderDataSource;
445 use crate::scan::test::TestLayoutReader;
446 use crate::scan::test::collect_scan_values;
447 use crate::scan::test::session_with_handle;
448
449 #[test]
452 fn unordered_limit_is_shared_across_partitions() -> VortexResult<()> {
453 let runtime = SingleThreadRuntime::default();
454 let session = session_with_handle(runtime.handle());
455 let source = LayoutReaderDataSource::new(Arc::new(TestLayoutReader::new(12)), session)
456 .with_split_max_row_count(2);
457
458 let scan = runtime.block_on(source.scan(ScanRequest {
459 filter: Some(root()),
460 limit: Some(3),
461 ordered: false,
462 ..Default::default()
463 }))?;
464 let partitions = runtime.block_on(scan.partitions().try_collect::<Vec<_>>())?;
465 assert_eq!(partitions.len(), 6);
466
467 let chunks = runtime.block_on(
468 futures::stream::iter(partitions)
469 .map(|partition| partition.execute())
470 .try_flatten_unordered(Some(6))
471 .try_collect::<Vec<_>>(),
472 )?;
473 let values = collect_scan_values(chunks.into_iter().map(Ok))?;
474
475 assert_eq!(values.len(), 3);
476 Ok(())
477 }
478
479 #[rstest]
482 #[case::ordered(true)]
483 #[case::unordered(false)]
484 fn sparse_selection_with_limit_fills_the_limit(#[case] ordered: bool) -> VortexResult<()> {
485 let runtime = SingleThreadRuntime::default();
486 let session = session_with_handle(runtime.handle());
487 let source = LayoutReaderDataSource::new(Arc::new(TestLayoutReader::new(12)), session)
488 .with_split_max_row_count(2);
489
490 let selection =
492 Selection::IncludeByIndex(StrictSortedBuffer::try_new(Buffer::from_iter([
493 0u64, 2, 4, 6, 8, 10,
494 ]))?);
495 let scan = runtime.block_on(source.scan(ScanRequest {
496 selection,
497 limit: Some(3),
498 ordered,
499 ..Default::default()
500 }))?;
501 let partitions = runtime.block_on(scan.partitions().try_collect::<Vec<_>>())?;
502
503 let mut values = Vec::new();
504 for partition in partitions {
505 values.extend(collect_scan_values(
506 runtime.block_on_stream(partition.execute()?),
507 )?);
508 }
509
510 assert_eq!(values, [0, 2, 4]);
511 Ok(())
512 }
513}