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
40impl Debug for dyn LayoutChildren {
41    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
42        f.debug_struct("LayoutChildren")
43            .field("nchildren", &self.nchildren())
44            .finish()
45    }
46}
47
48impl LayoutChildren for Arc<dyn LayoutChildren> {
49    fn to_arc(&self) -> Arc<dyn LayoutChildren> {
50        Arc::clone(self)
51    }
52
53    fn child(&self, idx: usize, dtype: &DType) -> VortexResult<LayoutRef> {
54        self.as_ref().child(idx, dtype)
55    }
56
57    fn child_row_count(&self, idx: usize) -> u64 {
58        self.as_ref().child_row_count(idx)
59    }
60
61    fn nchildren(&self) -> usize {
62        self.as_ref().nchildren()
63    }
64}
65
66/// An implementation of [`LayoutChildren`] for in-memory owned children.
67#[derive(Clone)]
68pub(crate) struct OwnedLayoutChildren(Vec<LayoutRef>);
69
70impl OwnedLayoutChildren {
71    pub fn layout_children(children: Vec<LayoutRef>) -> Arc<dyn LayoutChildren> {
72        Arc::new(Self(children))
73    }
74}
75
76/// Create an in-memory child adapter from owned layout references.
77pub fn layout_children(children: Vec<LayoutRef>) -> Arc<dyn LayoutChildren> {
78    OwnedLayoutChildren::layout_children(children)
79}
80
81/// In-memory implementation of [`LayoutChildren`].
82impl LayoutChildren for OwnedLayoutChildren {
83    fn to_arc(&self) -> Arc<dyn LayoutChildren> {
84        Arc::new(self.clone())
85    }
86
87    fn child(&self, idx: usize, dtype: &DType) -> VortexResult<LayoutRef> {
88        if idx >= self.0.len() {
89            vortex_bail!("Child index out of bounds: {} of {}", idx, self.0.len());
90        }
91        let child = &self.0[idx];
92        if child.dtype() != dtype {
93            vortex_bail!("Child dtype mismatch: {} != {}", child.dtype(), dtype);
94        }
95        Ok(Arc::clone(child))
96    }
97
98    fn child_row_count(&self, idx: usize) -> u64 {
99        self.0[idx].row_count()
100    }
101
102    fn nchildren(&self) -> usize {
103        self.0.len()
104    }
105}
106
107#[derive(Clone)]
108pub(crate) struct ViewedLayoutChildren {
109    flatbuffer: FlatBuffer,
110    flatbuffer_loc: usize,
111    array_read_ctx: ReadContext,
112    layout_read_ctx: ReadContext,
113    layouts: LayoutRegistry,
114    allow_unknown: bool,
115    session: VortexSession,
116    cache: Arc<[OnceCell<LayoutRef>]>,
117}
118
119impl ViewedLayoutChildren {
120    /// Create a new [`ViewedLayoutChildren`] from the given parameters.
121    ///
122    /// # Safety
123    ///
124    /// Assumes the flatbuffer is validated and that the `flatbuffer_loc` is the correct offset
125    pub(super) unsafe fn new_unchecked(
126        flatbuffer: FlatBuffer,
127        flatbuffer_loc: usize,
128        array_read_ctx: ReadContext,
129        layout_read_ctx: ReadContext,
130        layouts: LayoutRegistry,
131        allow_unknown: bool,
132        session: VortexSession,
133    ) -> Self {
134        // SAFETY: guaranteed by caller
135        let nchildren = unsafe { fbl::Layout::follow(flatbuffer.as_ref(), flatbuffer_loc) }
136            .children()
137            .unwrap_or_default()
138            .len();
139        let cache = vec![OnceCell::new(); nchildren].into_boxed_slice().into();
140        Self {
141            flatbuffer,
142            flatbuffer_loc,
143            array_read_ctx,
144            layout_read_ctx,
145            layouts,
146            allow_unknown,
147            session,
148            cache,
149        }
150    }
151
152    /// Return the flatbuffer layout message.
153    fn flatbuffer(&self) -> fbl::Layout<'_> {
154        // SAFETY: flatbuffer_loc is guaranteed to be a valid offset into the flatbuffer
155        // as it was constructed from a validated flatbuffer in ViewedLayoutChildren::try_new.
156        // The lifetime of the returned Layout is tied to self, ensuring the buffer remains valid.
157        unsafe { fbl::Layout::follow(self.flatbuffer.as_ref(), self.flatbuffer_loc) }
158    }
159
160    fn foreign_layout_from_fb(
161        &self,
162        fb_layout: fbl::Layout<'_>,
163        dtype: &DType,
164    ) -> VortexResult<LayoutRef> {
165        let encoding_id = self
166            .layout_read_ctx
167            .resolve(fb_layout.encoding())
168            .ok_or_else(|| vortex_err!("Encoding not found: {}", fb_layout.encoding()))?;
169
170        let children = fb_layout
171            .children()
172            .unwrap_or_default()
173            .iter()
174            .map(|child| self.foreign_layout_from_fb(child, dtype))
175            .collect::<VortexResult<Vec<_>>>()?;
176
177        Ok(new_foreign_layout(
178            encoding_id,
179            dtype.clone(),
180            fb_layout.row_count(),
181            fb_layout
182                .metadata()
183                .map(|m| m.bytes().to_vec())
184                .unwrap_or_default(),
185            fb_layout
186                .segments()
187                .unwrap_or_default()
188                .iter()
189                .map(SegmentId::from)
190                .collect_vec(),
191            children,
192        ))
193    }
194}
195
196impl LayoutChildren for ViewedLayoutChildren {
197    fn to_arc(&self) -> Arc<dyn LayoutChildren> {
198        Arc::new(self.clone())
199    }
200
201    fn child(&self, idx: usize, dtype: &DType) -> VortexResult<LayoutRef> {
202        if idx >= self.nchildren() {
203            vortex_bail!("Child index out of bounds: {} of {}", idx, self.nchildren());
204        }
205
206        let layout_ref = self.cache[idx].get_or_try_init(|| {
207            let fb_child = self.flatbuffer().children().unwrap_or_default().get(idx);
208
209            // SAFETY: same validated flatbuffer; fb_child._tab.loc() is a valid offset
210            // We need this to avoid re-initializing cache here
211            let viewed_children = unsafe {
212                ViewedLayoutChildren::new_unchecked(
213                    self.flatbuffer.clone(),
214                    fb_child._tab.loc(),
215                    self.array_read_ctx.clone(),
216                    self.layout_read_ctx.clone(),
217                    self.layouts.clone(),
218                    self.allow_unknown,
219                    self.session.clone(),
220                )
221            };
222
223            let encoding_id = self
224                .layout_read_ctx
225                .resolve(fb_child.encoding())
226                .ok_or_else(|| {
227                    vortex_err!("Unknown layout encoding index: {}", fb_child.encoding())
228                })?;
229            let Some(encoding) = self.layouts.find(&encoding_id) else {
230                if self.allow_unknown {
231                    return viewed_children.foreign_layout_from_fb(fb_child, dtype);
232                }
233                vortex_bail!("Unknown layout encoding: {encoding_id}");
234            };
235
236            let build_ctx = LayoutBuildContext {
237                session: &self.session,
238                array_read_ctx: &self.array_read_ctx,
239            };
240            encoding.build(
241                dtype,
242                fb_child.row_count(),
243                fb_child
244                    .metadata()
245                    .map(|m| m.bytes())
246                    .unwrap_or_else(|| &[]),
247                fb_child
248                    .segments()
249                    .unwrap_or_default()
250                    .iter()
251                    .map(SegmentId::from)
252                    .collect_vec(),
253                &viewed_children,
254                &build_ctx,
255            )
256        })?;
257        Ok(Arc::clone(layout_ref))
258    }
259
260    fn child_row_count(&self, idx: usize) -> u64 {
261        // Efficiently get the row count of the child at the given index, without a full
262        // deserialization.
263        self.flatbuffer()
264            .children()
265            .unwrap_or_default()
266            .get(idx)
267            .row_count()
268    }
269
270    fn nchildren(&self) -> usize {
271        self.cache.len()
272    }
273}