Skip to main content

vortex_layout/
reader.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::any::Any;
5use std::ops::Range;
6use std::sync::Arc;
7
8use futures::future::BoxFuture;
9use futures::try_join;
10use once_cell::sync::OnceCell;
11use vortex_array::ArrayRef;
12use vortex_array::IntoArray;
13use vortex_array::MaskFuture;
14use vortex_array::builtins::ArrayBuiltins;
15use vortex_array::dtype::DType;
16use vortex_array::dtype::FieldMask;
17use vortex_array::expr::Expression;
18use vortex_error::VortexResult;
19use vortex_error::vortex_bail;
20use vortex_mask::Mask;
21use vortex_session::VortexSession;
22
23use crate::LayoutReaderContext;
24use crate::children::LayoutChildren;
25use crate::segments::SegmentSource;
26
27/// Shared handle to a stateful layout reader.
28pub type LayoutReaderRef = Arc<dyn LayoutReader>;
29
30/// A row range used when registering natural scan splits.
31///
32/// Row range is relative to the reader that receives it. Offset is the offset
33/// that the local row range needs to be shifted by to get the global row range.
34#[derive(Clone, Debug, Eq, PartialEq)]
35pub struct SplitRange {
36    row_offset: u64,
37    row_range: Range<u64>,
38}
39
40impl SplitRange {
41    /// Constructs a split range, returning an error if the local row range is invalid.
42    pub fn try_new(row_offset: u64, row_range: Range<u64>) -> VortexResult<Self> {
43        if row_range.start > row_range.end {
44            vortex_bail!("Invalid split range {:?}", row_range);
45        }
46
47        Ok(Self {
48            row_offset,
49            row_range,
50        })
51    }
52
53    /// Constructs a split range for the root layout.
54    pub fn root(row_range: Range<u64>) -> VortexResult<Self> {
55        Self::try_new(0, row_range)
56    }
57
58    /// The root-layout row offset of this reader's local row zero.
59    pub fn row_offset(&self) -> u64 {
60        self.row_offset
61    }
62
63    /// The local row range within this reader.
64    pub fn row_range(&self) -> &Range<u64> {
65        &self.row_range
66    }
67
68    /// The length of the local row range.
69    pub fn len(&self) -> u64 {
70        self.row_range.end - self.row_range.start
71    }
72
73    /// Returns `true` if the local row range is empty.
74    pub fn is_empty(&self) -> bool {
75        self.row_range.is_empty()
76    }
77
78    /// Returns the equivalent row range in the root layout's coordinate space.
79    pub fn root_row_range(&self) -> Range<u64> {
80        self.row_offset + self.row_range.start..self.row_offset + self.row_range.end
81    }
82
83    /// Returns an error if the local row range is outside the given row count.
84    pub fn check_bounds(&self, row_count: u64) -> VortexResult<()> {
85        if self.row_range.end > row_count {
86            vortex_bail!(
87                "Split range {:?} is out of bounds for row count {}",
88                self.row_range,
89                row_count
90            );
91        }
92
93        Ok(())
94    }
95}
96
97/// A collection of root-coordinate row split points.
98pub struct RowSplits(Vec<u64>);
99
100impl RowSplits {
101    /// Add a row boundary to the split set.
102    pub fn push(&mut self, row: u64) {
103        self.0.push(row);
104    }
105
106    /// Reserve space for additional row boundaries.
107    pub fn reserve(&mut self, additional: usize) {
108        self.0.reserve(additional);
109    }
110
111    /// Create a new RowSplits with preallocated "capacity"
112    pub(crate) fn new_capacity(capacity: usize) -> Self {
113        Self(Vec::with_capacity(capacity))
114    }
115
116    pub(crate) fn into_sorted_deduped(mut self) -> Vec<u64> {
117        self.0.sort_unstable();
118        self.0.dedup();
119        self.0.shrink_to_fit();
120        self.0
121    }
122}
123
124/// Stateful reader for a [`crate::Layout`].
125///
126/// A reader owns or references any state needed to evaluate many scan operations over the same
127/// layout, such as child readers, decoded metadata, or segment caches. Scan planning calls
128/// [`register_splits`](Self::register_splits); execution calls pruning, filter, and projection
129/// evaluation for each selected row range.
130pub trait LayoutReader: 'static + Send + Sync {
131    /// Returns the name of the layout reader for debugging.
132    fn name(&self) -> &Arc<str>;
133
134    /// Returns this reader as [`Any`] for downcasting by specialized wrappers.
135    fn as_any(&self) -> &dyn Any;
136
137    /// Returns the un-projected dtype of the layout reader.
138    fn dtype(&self) -> &DType;
139
140    /// Returns the number of rows in the layout.
141    fn row_count(&self) -> u64;
142
143    /// Register natural split boundaries for this reader.
144    ///
145    /// `field_mask` contains the projected and filtered field paths needed by the scan.
146    /// Implementations should add root-coordinate split boundaries to `splits`, constrained to
147    /// `split_range`.
148    // TODO(ngates): this is a temporary API until we make layout readers stream based.
149    fn register_splits(
150        &self,
151        field_mask: &[FieldMask],
152        split_range: &SplitRange,
153        splits: &mut RowSplits,
154    ) -> VortexResult<()>;
155
156    /// Returns a mask where all false values are proven to be false in the given expression.
157    ///
158    /// The returned mask **does not** need to have been intersected with the input mask.
159    fn pruning_evaluation(
160        &self,
161        row_range: &Range<u64>,
162        expr: &Expression,
163        mask: Mask,
164    ) -> VortexResult<MaskFuture>;
165
166    /// Refines the given mask, returning a mask equal in length to the input mask.
167    ///
168    /// It is recommended to defer awaiting the input mask for as long as possible (ideally, after
169    /// all I/O is complete). This allows other conjuncts the opportunity to refine the mask as much
170    /// as possible before it is used.
171    ///
172    /// ## Post-conditions
173    ///
174    /// The returned mask **MUST** have been intersected with the input mask.
175    fn filter_evaluation(
176        &self,
177        row_range: &Range<u64>,
178        expr: &Expression,
179        mask: MaskFuture,
180    ) -> VortexResult<MaskFuture>;
181
182    /// Evaluates an expression against an array.
183    ///
184    /// It is recommended to defer awaiting the input mask for as long as possible (ideally, after
185    /// all I/O is complete). This allows other conjuncts the opportunity to refine the mask as much
186    /// as possible before it is used.
187    ///
188    /// ## Post-conditions
189    ///
190    /// The returned array **MUST** have length equal to the true count of the input mask.
191    fn projection_evaluation(
192        &self,
193        row_range: &Range<u64>,
194        expr: &Expression,
195        mask: MaskFuture,
196    ) -> VortexResult<ArrayFuture>;
197}
198
199/// Future resolving to a projected Vortex array.
200pub type ArrayFuture = BoxFuture<'static, VortexResult<ArrayRef>>;
201
202/// Helpers for futures that resolve to arrays.
203pub trait ArrayFutureExt {
204    /// Apply a row mask to the resolved array.
205    fn masked(self, mask: MaskFuture) -> Self;
206}
207
208impl ArrayFutureExt for ArrayFuture {
209    /// Returns a new `ArrayFuture` that masks the output with a mask
210    fn masked(self, mask: MaskFuture) -> Self {
211        Box::pin(async move {
212            let (array, mask) = try_join!(self, mask)?;
213            array.mask(mask.into_array())
214        })
215    }
216}
217
218/// Lazily constructs and caches child readers while preserving reader context.
219pub struct LazyReaderChildren {
220    children: Arc<dyn LayoutChildren>,
221    dtypes: Vec<DType>,
222    names: Vec<Arc<str>>,
223    segment_source: Arc<dyn SegmentSource>,
224    session: VortexSession,
225    ctx: LayoutReaderContext,
226    // TODO(ngates): we may want a hash map of some sort here?
227    cache: Vec<OnceCell<LayoutReaderRef>>,
228}
229
230impl LazyReaderChildren {
231    /// Create a lazy child-reader cache.
232    ///
233    /// `dtypes` and `names` must be aligned with the child indices exposed by `children`.
234    pub fn new(
235        children: Arc<dyn LayoutChildren>,
236        dtypes: Vec<DType>,
237        names: Vec<Arc<str>>,
238        segment_source: Arc<dyn SegmentSource>,
239        session: VortexSession,
240        ctx: LayoutReaderContext,
241    ) -> Self {
242        let nchildren = children.nchildren();
243        let cache = (0..nchildren).map(|_| OnceCell::new()).collect();
244        Self {
245            children,
246            dtypes,
247            names,
248            segment_source,
249            session,
250            ctx,
251            cache,
252        }
253    }
254
255    /// Return the child reader at `idx`, constructing it on first access.
256    pub fn get(&self, idx: usize) -> VortexResult<&LayoutReaderRef> {
257        if idx >= self.cache.len() {
258            vortex_bail!("Child index out of bounds: {} of {}", idx, self.cache.len());
259        }
260
261        self.cache[idx].get_or_try_init(|| {
262            let dtype = &self.dtypes[idx];
263            let child = self.children.child(idx, dtype)?;
264            child.new_reader(
265                Arc::clone(&self.names[idx]),
266                Arc::clone(&self.segment_source),
267                &self.session,
268                &self.ctx,
269            )
270        })
271    }
272}