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