Skip to main content

vortex_layout/layouts/struct_/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4mod reader;
5
6use std::sync::Arc;
7
8use reader::StructReader;
9use vortex_array::DeserializeMetadata;
10use vortex_array::EmptyMetadata;
11use vortex_array::dtype::DType;
12use vortex_array::dtype::Field;
13use vortex_array::dtype::FieldMask;
14use vortex_array::dtype::Nullability;
15use vortex_array::dtype::StructFields;
16use vortex_error::VortexExpect;
17use vortex_error::VortexResult;
18use vortex_error::vortex_bail;
19use vortex_error::vortex_ensure;
20use vortex_error::vortex_err;
21use vortex_session::SessionExt;
22use vortex_session::VortexSession;
23use vortex_session::registry::CachedId;
24
25use crate::LayoutBuildContext;
26use crate::LayoutChildType;
27use crate::LayoutEncodingRef;
28use crate::LayoutId;
29use crate::LayoutReaderRef;
30use crate::LayoutRef;
31use crate::VTable;
32use crate::children::LayoutChildren;
33use crate::children::OwnedLayoutChildren;
34use crate::segments::SegmentId;
35use crate::segments::SegmentSource;
36use crate::vtable;
37
38vtable!(Struct);
39
40impl VTable for Struct {
41    type Layout = StructLayout;
42    type Encoding = StructLayoutEncoding;
43    type Metadata = EmptyMetadata;
44
45    fn id(_encoding: &Self::Encoding) -> LayoutId {
46        static ID: CachedId = CachedId::new("vortex.struct");
47        *ID
48    }
49
50    fn encoding(_layout: &Self::Layout) -> LayoutEncodingRef {
51        LayoutEncodingRef::new_ref(StructLayoutEncoding.as_ref())
52    }
53
54    fn row_count(layout: &Self::Layout) -> u64 {
55        layout.row_count
56    }
57
58    fn dtype(layout: &Self::Layout) -> &DType {
59        &layout.dtype
60    }
61
62    fn metadata(_layout: &Self::Layout) -> Self::Metadata {
63        EmptyMetadata
64    }
65
66    fn segment_ids(_layout: &Self::Layout) -> Vec<SegmentId> {
67        vec![]
68    }
69
70    fn nchildren(layout: &Self::Layout) -> usize {
71        let validity_children = if layout.dtype.is_nullable() { 1 } else { 0 };
72        layout.struct_fields().nfields() + validity_children
73    }
74
75    fn child(layout: &Self::Layout, index: usize) -> VortexResult<LayoutRef> {
76        let schema_index = if layout.dtype.is_nullable() {
77            index.saturating_sub(1)
78        } else {
79            index
80        };
81
82        let child_dtype = if index == 0 && layout.dtype.is_nullable() {
83            DType::Bool(Nullability::NonNullable)
84        } else {
85            layout
86                .struct_fields()
87                .field_by_index(schema_index)
88                .ok_or_else(|| vortex_err!("Missing field {schema_index}"))?
89        };
90
91        layout.children.child(index, &child_dtype)
92    }
93
94    fn child_type(layout: &Self::Layout, idx: usize) -> LayoutChildType {
95        let schema_index = if layout.dtype.is_nullable() {
96            idx.saturating_sub(1)
97        } else {
98            idx
99        };
100
101        if idx == 0 && layout.dtype.is_nullable() {
102            LayoutChildType::Auxiliary("validity".into())
103        } else {
104            LayoutChildType::Field(
105                layout
106                    .struct_fields()
107                    .field_name(schema_index)
108                    .vortex_expect("Field index out of bounds")
109                    .clone(),
110            )
111        }
112    }
113
114    fn new_reader(
115        layout: &Self::Layout,
116        name: Arc<str>,
117        segment_source: Arc<dyn SegmentSource>,
118        session: &VortexSession,
119        ctx: &crate::LayoutReaderContext,
120    ) -> VortexResult<LayoutReaderRef> {
121        Ok(Arc::new(StructReader::try_new(
122            layout.clone(),
123            name,
124            segment_source,
125            session.session(),
126            ctx.clone(),
127        )?))
128    }
129
130    fn build(
131        _encoding: &Self::Encoding,
132        dtype: &DType,
133        row_count: u64,
134        _metadata: &<Self::Metadata as DeserializeMetadata>::Output,
135        _segment_ids: Vec<SegmentId>,
136        children: &dyn LayoutChildren,
137        _build_ctx: &LayoutBuildContext<'_>,
138    ) -> VortexResult<Self::Layout> {
139        let struct_dt = dtype
140            .as_struct_fields_opt()
141            .ok_or_else(|| vortex_err!("Expected struct dtype"))?;
142
143        let expected_children = struct_dt.nfields() + (dtype.is_nullable() as usize);
144        vortex_ensure!(
145            children.nchildren() == expected_children,
146            "Struct layout has {} children, but dtype has {} fields",
147            children.nchildren(),
148            struct_dt.nfields()
149        );
150
151        Ok(StructLayout {
152            row_count,
153            dtype: dtype.clone(),
154            children: children.to_arc(),
155        })
156    }
157
158    fn with_children(layout: &mut Self::Layout, children: Vec<LayoutRef>) -> VortexResult<()> {
159        let struct_dt = layout
160            .dtype
161            .as_struct_fields_opt()
162            .ok_or_else(|| vortex_err!("Expected struct dtype"))?;
163
164        let expected_children = struct_dt.nfields() + (layout.dtype.is_nullable() as usize);
165        vortex_ensure!(
166            children.len() == expected_children,
167            "StructLayout expects {} children, got {}",
168            expected_children,
169            children.len()
170        );
171
172        layout.children = OwnedLayoutChildren::layout_children(children);
173        Ok(())
174    }
175}
176
177#[derive(Debug)]
178pub struct StructLayoutEncoding;
179
180/// Decomposes a struct-typed column into one child per field, enabling columnar projection.
181///
182/// Queries that only need a subset of fields can skip reading the rest entirely.
183#[derive(Clone, Debug)]
184pub struct StructLayout {
185    row_count: u64,
186    dtype: DType,
187    children: Arc<dyn LayoutChildren>,
188}
189
190impl StructLayout {
191    pub fn new(row_count: u64, dtype: DType, children: Vec<LayoutRef>) -> Self {
192        Self {
193            row_count,
194            dtype,
195            children: OwnedLayoutChildren::layout_children(children),
196        }
197    }
198
199    pub fn struct_fields(&self) -> &StructFields {
200        self.dtype
201            .as_struct_fields_opt()
202            .vortex_expect("Struct layout dtype must be a struct")
203    }
204
205    #[inline]
206    pub fn row_count(&self) -> u64 {
207        self.row_count
208    }
209
210    #[inline]
211    pub fn children(&self) -> &Arc<dyn LayoutChildren> {
212        &self.children
213    }
214
215    pub fn matching_fields<F>(&self, field_mask: &[FieldMask], mut per_child: F) -> VortexResult<()>
216    where
217        F: FnMut(FieldMask, usize) -> VortexResult<()>,
218    {
219        // If the field mask contains an `All` fields, then enumerate all fields.
220        if field_mask.iter().any(|mask| mask.matches_all()) {
221            for idx in 0..self.struct_fields().nfields() {
222                per_child(FieldMask::All, idx)?;
223            }
224            return Ok(());
225        }
226
227        // Enumerate each field in the mask
228        for path in field_mask {
229            let Some(field) = path.starting_field()? else {
230                // skip fields not in mask
231                continue;
232            };
233            let Field::Name(field_name) = field else {
234                vortex_bail!("Expected field name, got {field:?}");
235            };
236            let idx = self
237                .struct_fields()
238                .find(field_name)
239                .ok_or_else(|| vortex_err!("Field not found: {field_name}"))?;
240
241            per_child(path.clone().step_into()?, idx)?;
242        }
243
244        Ok(())
245    }
246}