Skip to main content

vortex_layout/
children.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::fmt::Debug;
5use std::fmt::Formatter;
6use std::sync::Arc;
7
8use flatbuffers::Follow;
9use itertools::Itertools;
10use once_cell::sync::OnceCell;
11use vortex_array::dtype::DType;
12use vortex_error::VortexResult;
13use vortex_error::vortex_bail;
14use vortex_error::vortex_err;
15use vortex_flatbuffers::FlatBuffer;
16use vortex_flatbuffers::layout as fbl;
17use vortex_session::VortexSession;
18use vortex_session::registry::ReadContext;
19
20use crate::LayoutBuildContext;
21use crate::LayoutRef;
22use crate::layouts::foreign::new_foreign_layout;
23use crate::segments::SegmentId;
24use crate::session::LayoutRegistry;
25
26/// Abstract way of accessing the children of a layout.
27///
28/// This allows us to abstract over the lazy flatbuffer-based layouts, as well as the in-memory
29/// layout trees.
30pub trait LayoutChildren: 'static + Send + Sync {
31    fn to_arc(&self) -> Arc<dyn LayoutChildren>;
32
33    fn child(&self, idx: usize, dtype: &DType) -> VortexResult<LayoutRef>;
34
35    fn child_row_count(&self, idx: usize) -> u64;
36
37    fn nchildren(&self) -> usize;
38
39    /// Returns `true` if the child at `idx` is known — without materializing it — to be
40    /// indivisible: it registers no split boundaries strictly inside its row range (see
41    /// [`VTable::is_indivisible`](crate::VTable::is_indivisible)).
42    ///
43    /// Implementations must conservatively return `false` when answering would require
44    /// materializing the child.
45    fn child_is_indivisible(&self, _idx: usize) -> bool {
46        false
47    }
48}
49
50impl Debug for dyn LayoutChildren {
51    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
52        f.debug_struct("LayoutChildren")
53            .field("nchildren", &self.nchildren())
54            .finish()
55    }
56}
57
58impl LayoutChildren for Arc<dyn LayoutChildren> {
59    fn to_arc(&self) -> Arc<dyn LayoutChildren> {
60        Arc::clone(self)
61    }
62
63    fn child(&self, idx: usize, dtype: &DType) -> VortexResult<LayoutRef> {
64        self.as_ref().child(idx, dtype)
65    }
66
67    fn child_row_count(&self, idx: usize) -> u64 {
68        self.as_ref().child_row_count(idx)
69    }
70
71    fn nchildren(&self) -> usize {
72        self.as_ref().nchildren()
73    }
74
75    fn child_is_indivisible(&self, idx: usize) -> bool {
76        self.as_ref().child_is_indivisible(idx)
77    }
78}
79
80/// An implementation of [`LayoutChildren`] for in-memory owned children.
81#[derive(Clone)]
82pub(crate) struct OwnedLayoutChildren(Vec<LayoutRef>);
83
84impl OwnedLayoutChildren {
85    pub fn layout_children(children: Vec<LayoutRef>) -> Arc<dyn LayoutChildren> {
86        Arc::new(Self(children))
87    }
88}
89
90/// Create an in-memory child adapter from owned layout references.
91pub fn layout_children(children: Vec<LayoutRef>) -> Arc<dyn LayoutChildren> {
92    OwnedLayoutChildren::layout_children(children)
93}
94
95/// In-memory implementation of [`LayoutChildren`].
96impl LayoutChildren for OwnedLayoutChildren {
97    fn to_arc(&self) -> Arc<dyn LayoutChildren> {
98        Arc::new(self.clone())
99    }
100
101    fn child(&self, idx: usize, dtype: &DType) -> VortexResult<LayoutRef> {
102        if idx >= self.0.len() {
103            vortex_bail!("Child index out of bounds: {} of {}", idx, self.0.len());
104        }
105        let child = &self.0[idx];
106        if child.dtype() != dtype {
107            vortex_bail!("Child dtype mismatch: {} != {}", child.dtype(), dtype);
108        }
109        Ok(Arc::clone(child))
110    }
111
112    fn child_row_count(&self, idx: usize) -> u64 {
113        self.0[idx].row_count()
114    }
115
116    fn nchildren(&self) -> usize {
117        self.0.len()
118    }
119
120    fn child_is_indivisible(&self, idx: usize) -> bool {
121        self.0[idx].dyn_is_indivisible()
122    }
123}
124
125#[derive(Clone)]
126pub(crate) struct ViewedLayoutChildren {
127    flatbuffer: FlatBuffer,
128    flatbuffer_loc: usize,
129    array_read_ctx: ReadContext,
130    layout_read_ctx: ReadContext,
131    layouts: LayoutRegistry,
132    allow_unknown: bool,
133    session: VortexSession,
134    cache: Arc<[OnceCell<LayoutRef>]>,
135    /// Per-child answers to [`LayoutChildren::child_is_indivisible`], precomputed at
136    /// construction from the flatbuffer encoding tags alone.
137    indivisible: Arc<[bool]>,
138}
139
140impl ViewedLayoutChildren {
141    /// Create a new [`ViewedLayoutChildren`] from the given parameters.
142    ///
143    /// # Safety
144    ///
145    /// Assumes the flatbuffer is validated and that the `flatbuffer_loc` is the correct offset
146    pub(super) unsafe fn new_unchecked(
147        flatbuffer: FlatBuffer,
148        flatbuffer_loc: usize,
149        array_read_ctx: ReadContext,
150        layout_read_ctx: ReadContext,
151        layouts: LayoutRegistry,
152        allow_unknown: bool,
153        session: VortexSession,
154    ) -> Self {
155        // SAFETY: guaranteed by caller
156        let fb_children = unsafe { fbl::Layout::follow(flatbuffer.as_ref(), flatbuffer_loc) }
157            .children()
158            .unwrap_or_default();
159        let cache = vec![OnceCell::new(); fb_children.len()]
160            .into_boxed_slice()
161            .into();
162        // Unknown encodings are conservatively not indivisible so callers fall back to
163        // materializing the child.
164        let indivisible = fb_children
165            .iter()
166            .map(|child| {
167                layout_read_ctx
168                    .resolve(child.encoding())
169                    .and_then(|encoding_id| layouts.get(&encoding_id))
170                    .is_some_and(|encoding| encoding.is_indivisible())
171            })
172            .collect::<Arc<[bool]>>();
173        Self {
174            flatbuffer,
175            flatbuffer_loc,
176            array_read_ctx,
177            layout_read_ctx,
178            layouts,
179            allow_unknown,
180            session,
181            cache,
182            indivisible,
183        }
184    }
185
186    /// Return the flatbuffer layout message.
187    fn flatbuffer(&self) -> fbl::Layout<'_> {
188        // SAFETY: flatbuffer_loc is guaranteed to be a valid offset into the flatbuffer
189        // as it was constructed from a validated flatbuffer in ViewedLayoutChildren::try_new.
190        // The lifetime of the returned Layout is tied to self, ensuring the buffer remains valid.
191        unsafe { fbl::Layout::follow(self.flatbuffer.as_ref(), self.flatbuffer_loc) }
192    }
193
194    fn foreign_layout_from_fb(
195        &self,
196        fb_layout: fbl::Layout<'_>,
197        dtype: &DType,
198    ) -> VortexResult<LayoutRef> {
199        let encoding_id = self
200            .layout_read_ctx
201            .resolve(fb_layout.encoding())
202            .ok_or_else(|| vortex_err!("Encoding not found: {}", fb_layout.encoding()))?;
203
204        let children = fb_layout
205            .children()
206            .unwrap_or_default()
207            .iter()
208            .map(|child| self.foreign_layout_from_fb(child, dtype))
209            .collect::<VortexResult<Vec<_>>>()?;
210
211        Ok(new_foreign_layout(
212            encoding_id,
213            dtype.clone(),
214            fb_layout.row_count(),
215            fb_layout
216                .metadata()
217                .map(|m| m.bytes().to_vec())
218                .unwrap_or_default(),
219            fb_layout
220                .segments()
221                .unwrap_or_default()
222                .iter()
223                .map(SegmentId::from)
224                .collect_vec(),
225            children,
226        ))
227    }
228}
229
230impl LayoutChildren for ViewedLayoutChildren {
231    fn to_arc(&self) -> Arc<dyn LayoutChildren> {
232        Arc::new(self.clone())
233    }
234
235    fn child(&self, idx: usize, dtype: &DType) -> VortexResult<LayoutRef> {
236        if idx >= self.nchildren() {
237            vortex_bail!("Child index out of bounds: {} of {}", idx, self.nchildren());
238        }
239
240        let layout_ref = self.cache[idx].get_or_try_init(|| {
241            let fb_child = self.flatbuffer().children().unwrap_or_default().get(idx);
242
243            // SAFETY: same validated flatbuffer; fb_child._tab.loc() is a valid offset
244            // We need this to avoid re-initializing cache here
245            let viewed_children = unsafe {
246                ViewedLayoutChildren::new_unchecked(
247                    self.flatbuffer.clone(),
248                    fb_child._tab.loc(),
249                    self.array_read_ctx.clone(),
250                    self.layout_read_ctx.clone(),
251                    self.layouts.clone(),
252                    self.allow_unknown,
253                    self.session.clone(),
254                )
255            };
256
257            let encoding_id = self
258                .layout_read_ctx
259                .resolve(fb_child.encoding())
260                .ok_or_else(|| {
261                    vortex_err!("Unknown layout encoding index: {}", fb_child.encoding())
262                })?;
263            let Some(encoding) = self.layouts.get(&encoding_id) else {
264                if self.allow_unknown {
265                    return viewed_children.foreign_layout_from_fb(fb_child, dtype);
266                }
267                vortex_bail!("Unknown layout encoding: {encoding_id}");
268            };
269
270            let build_ctx = LayoutBuildContext {
271                session: &self.session,
272                array_read_ctx: &self.array_read_ctx,
273            };
274            encoding.build(
275                dtype,
276                fb_child.row_count(),
277                fb_child
278                    .metadata()
279                    .map(|m| m.bytes())
280                    .unwrap_or_else(|| &[]),
281                fb_child
282                    .segments()
283                    .unwrap_or_default()
284                    .iter()
285                    .map(SegmentId::from)
286                    .collect_vec(),
287                &viewed_children,
288                &build_ctx,
289            )
290        })?;
291        Ok(Arc::clone(layout_ref))
292    }
293
294    fn child_row_count(&self, idx: usize) -> u64 {
295        // Efficiently get the row count of the child at the given index, without a full
296        // deserialization.
297        self.flatbuffer()
298            .children()
299            .unwrap_or_default()
300            .get(idx)
301            .row_count()
302    }
303
304    fn nchildren(&self) -> usize {
305        self.cache.len()
306    }
307
308    fn child_is_indivisible(&self, idx: usize) -> bool {
309        self.indivisible.get(idx).copied().unwrap_or(false)
310    }
311}