vortex_layout/scan/
layout.rs1use 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::scan_builder::ScanBuilder;
44
45pub struct LayoutReaderDataSource {
47 reader: LayoutReaderRef,
48 session: VortexSession,
49 split_max_row_count: u64,
50 metrics_registry: Option<Arc<dyn MetricsRegistry>>,
51}
52
53impl LayoutReaderDataSource {
54 pub fn new(reader: LayoutReaderRef, session: VortexSession) -> Self {
61 Self {
62 reader,
63 session,
64 split_max_row_count: u64::MAX,
65 metrics_registry: None,
66 }
67 }
68
69 pub fn with_split_max_row_count(mut self, row_count: u64) -> Self {
75 self.split_max_row_count = row_count;
76 self
77 }
78
79 pub fn with_metrics_registry(mut self, metrics: Arc<dyn MetricsRegistry>) -> Self {
81 self.metrics_registry = Some(metrics);
82 self
83 }
84
85 pub fn with_some_metrics_registry(mut self, metrics: Option<Arc<dyn MetricsRegistry>>) -> Self {
87 self.metrics_registry = metrics;
88 self
89 }
90}
91
92#[async_trait]
93impl DataSource for LayoutReaderDataSource {
94 fn dtype(&self) -> &DType {
95 self.reader.dtype()
96 }
97
98 fn row_count(&self) -> Precision<u64> {
99 Precision::exact(self.reader.row_count())
100 }
101
102 fn byte_size(&self) -> Precision<u64> {
103 Precision::Absent
104 }
105
106 fn deserialize_partition(
107 &self,
108 _data: &[u8],
109 _session: &VortexSession,
110 ) -> VortexResult<PartitionRef> {
111 vortex_bail!("LayoutReader splits are not yet serializable");
112 }
113
114 async fn scan(&self, scan_request: ScanRequest) -> VortexResult<DataSourceScanRef> {
115 let total_rows = self.reader.row_count();
116 let row_range = scan_request.row_range.unwrap_or(0..total_rows);
117
118 let projection = scan_request
119 .projection
120 .optimize_recursive(self.reader.dtype())?
121 .bind(self.reader.dtype())?;
122 let filter = scan_request
123 .filter
124 .map(|expr| {
125 expr.optimize_recursive(self.reader.dtype())?
126 .bind(self.reader.dtype())
127 })
128 .transpose()?;
129 let dtype = projection.dtype().clone();
130
131 if let DType::Struct(fields, Nullability::NonNullable) = &dtype
134 && fields.nfields() == 0
135 && filter.is_none()
136 {
137 let row_count = row_range.end - row_range.start;
139 let row_count = scan_request.selection.row_count(row_count);
140
141 let row_count = if let Some(limit) = scan_request.limit {
143 row_count.min(limit)
144 } else {
145 row_count
146 };
147
148 return Ok(Box::new(Empty { dtype, row_count }));
149 }
150
151 if let Some(filter) = &filter {
154 let mask = Mask::new_true(
155 usize::try_from(row_range.end - row_range.start).unwrap_or(usize::MAX),
156 );
157 let pruning_result = self
158 .reader
159 .pruning_evaluation(&row_range, filter, mask)?
160 .now_or_never();
161 if let Some(Ok(result_mask)) = pruning_result
162 && result_mask.all_false()
163 {
164 return Ok(Box::new(Empty {
165 dtype,
166 row_count: 0,
167 }));
168 }
169 }
170
171 Ok(Box::new(LayoutReaderScan {
172 reader: Arc::clone(&self.reader),
173 session: self.session.clone(),
174 dtype,
175 projection,
176 filter,
177 limit: scan_request.limit,
178 selection: scan_request.selection,
179 ordered: scan_request.ordered,
180 metrics_registry: self.metrics_registry.clone(),
181 next_row: row_range.start,
182 end_row: row_range.end,
183 split_size: self.split_max_row_count,
184 }))
185 }
186
187 async fn field_statistics(&self, _field_path: &FieldPath) -> VortexResult<StatsSet> {
188 Ok(StatsSet::default())
189 }
190}
191
192struct LayoutReaderScan {
193 reader: LayoutReaderRef,
194 session: VortexSession,
195 dtype: DType,
196 projection: BoundExpression,
197 filter: Option<BoundExpression>,
198 limit: Option<u64>,
199 ordered: bool,
200 selection: Selection,
201 metrics_registry: Option<Arc<dyn MetricsRegistry>>,
202 next_row: u64,
203 end_row: u64,
204 split_size: u64,
205}
206
207impl DataSourceScan for LayoutReaderScan {
208 fn dtype(&self) -> &DType {
209 &self.dtype
210 }
211
212 fn partition_count(&self) -> Precision<usize> {
213 let (lower, upper) = self.size_hint();
214 match upper {
215 Some(u) if u == lower => Precision::exact(lower),
216 Some(u) => Precision::inexact(u),
217 None => Precision::inexact(lower),
218 }
219 }
220
221 fn partitions(self: Box<Self>) -> PartitionStream {
222 (*self).boxed()
223 }
224}
225
226impl Stream for LayoutReaderScan {
227 type Item = VortexResult<PartitionRef>;
228
229 fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
230 let this = self.get_mut();
231
232 if this.next_row >= this.end_row {
233 return Poll::Ready(None);
234 }
235
236 if this.limit.is_some_and(|limit| limit == 0) {
237 return Poll::Ready(None);
238 }
239
240 let split_end = this
241 .next_row
242 .saturating_add(this.split_size)
243 .min(this.end_row);
244 let row_range = this.next_row..split_end;
245 let split_rows = split_end - this.next_row;
246
247 let split_limit = this.limit;
248 if this.filter.is_none()
254 && let Some(ref mut limit) = this.limit
255 {
256 *limit = limit.saturating_sub(split_rows);
257 }
258
259 let split = Box::new(LayoutReaderSplit {
260 reader: Arc::clone(&this.reader),
261 session: this.session.clone(),
262 projection: this.projection.clone(),
263 filter: this.filter.clone(),
264 limit: split_limit,
265 ordered: this.ordered,
266 row_range,
267 selection: this.selection.clone(),
268 metrics_registry: this.metrics_registry.clone(),
269 }) as PartitionRef;
270
271 this.next_row = split_end;
272
273 Poll::Ready(Some(Ok(split)))
274 }
275
276 fn size_hint(&self) -> (usize, Option<usize>) {
277 if self.next_row >= self.end_row {
278 return (0, Some(0));
279 }
280 let remaining_rows = self.end_row - self.next_row;
281 let splits = remaining_rows.div_ceil(self.split_size);
282 (0, Some(usize::try_from(splits).unwrap_or(usize::MAX)))
283 }
284}
285
286struct LayoutReaderSplit {
287 reader: LayoutReaderRef,
288 session: VortexSession,
289 projection: BoundExpression,
290 filter: Option<BoundExpression>,
291 limit: Option<u64>,
292 ordered: bool,
293 row_range: Range<u64>,
294 selection: Selection,
295 metrics_registry: Option<Arc<dyn MetricsRegistry>>,
296}
297
298impl Partition for LayoutReaderSplit {
299 fn as_any(&self) -> &dyn Any {
300 self
301 }
302
303 #[expect(clippy::cast_possible_truncation)]
304 fn index(&self) -> usize {
305 self.row_range.start as usize
307 }
308
309 fn row_count(&self) -> Precision<u64> {
310 let row_count = self.row_range.end - self.row_range.start;
311 let row_count = self.selection.row_count(row_count);
312 let row_count = self.limit.map_or(row_count, |limit| row_count.min(limit));
313
314 if self.filter.is_some() {
315 Precision::inexact(row_count)
316 } else {
317 Precision::exact(row_count)
318 }
319 }
320
321 fn byte_size(&self) -> Precision<u64> {
322 Precision::Absent
323 }
324
325 fn execute(self: Box<Self>) -> VortexResult<SendableArrayStream> {
326 let builder = ScanBuilder::new(self.session, self.reader)
327 .with_row_range(self.row_range)
328 .with_selection(self.selection)
329 .with_projection(self.projection)
330 .with_some_filter(self.filter)
331 .with_some_limit(self.limit)
332 .with_some_metrics_registry(self.metrics_registry)
333 .with_ordered(self.ordered);
334
335 let dtype = builder.dtype()?;
336 let stream = builder.into_stream()?;
339
340 Ok(ArrayStreamExt::boxed(ArrayStreamAdapter::new(
341 dtype, stream,
342 )))
343 }
344}
345
346struct Empty {
348 dtype: DType,
349 row_count: u64,
350}
351
352impl DataSourceScan for Empty {
353 fn dtype(&self) -> &DType {
354 &self.dtype
355 }
356
357 fn partition_count(&self) -> Precision<usize> {
358 Precision::exact(1usize)
359 }
360
361 fn partitions(self: Box<Self>) -> PartitionStream {
362 stream::iter([Ok(self as _)]).boxed()
363 }
364}
365
366impl Partition for Empty {
367 fn as_any(&self) -> &dyn Any {
368 self
369 }
370
371 fn index(&self) -> usize {
372 0
373 }
374
375 fn row_count(&self) -> Precision<u64> {
376 Precision::exact(self.row_count)
377 }
378
379 fn byte_size(&self) -> Precision<u64> {
380 Precision::exact(0u64)
381 }
382
383 fn execute(mut self: Box<Self>) -> VortexResult<SendableArrayStream> {
384 let scalar = Scalar::default_value(&self.dtype);
385 let dtype = self.dtype.clone();
386
387 let iter = std::iter::from_fn(move || {
389 if self.row_count == 0 {
390 return None;
391 }
392 let chunk_size = usize::try_from(self.row_count).unwrap_or(usize::MAX);
393 self.row_count -= chunk_size as u64;
394 Some(VortexResult::Ok(
395 ConstantArray::new(scalar.clone(), chunk_size).into_array(),
396 ))
397 });
398
399 Ok(ArrayStreamExt::boxed(ArrayStreamAdapter::new(
400 dtype,
401 stream::iter(iter),
402 )))
403 }
404}