Skip to main content

vortex_layout/layouts/list/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4//! An experimental structural layout for list-typed columns. Note that this is expected to change.
5//!
6//! [`ListLayout`] decomposes a list column into independently configurable child layouts:
7//! `elements`, `offsets`, and, for nullable lists, `validity`. Keeping the children independent allows
8//! each child to use its own configurable layout and lets nested list elements (e.g. with `List<List>`) be decomposed recursively.
9//!
10//! This provides benefits such as:
11//! * Reading only the children needed to evaluate an expression. For example, `ListLength` does
12//!   not need to read list elements.
13//! * Restricting element reads to the range covered by the selected outer rows, avoiding elements
14//!   belonging exclusively to unselected leading or trailing lists.
15//! * Allowing each child to use its own compression, chunking, and pruning strategy.
16
17mod expr;
18mod reader;
19pub mod writer;
20
21use std::sync::Arc;
22
23use reader::ListReader;
24use vortex_array::DeserializeMetadata;
25use vortex_array::ProstMetadata;
26use vortex_array::dtype::DType;
27use vortex_array::dtype::Nullability;
28use vortex_array::dtype::PType;
29use vortex_error::VortexExpect;
30use vortex_error::VortexResult;
31use vortex_error::vortex_bail;
32use vortex_error::vortex_ensure_eq;
33use vortex_error::vortex_err;
34use vortex_error::vortex_panic;
35use vortex_session::VortexSession;
36use vortex_session::registry::CachedId;
37
38use crate::LayoutBuildContext;
39use crate::LayoutChildType;
40use crate::LayoutEncodingRef;
41use crate::LayoutId;
42use crate::LayoutReaderContext;
43use crate::LayoutReaderRef;
44use crate::LayoutRef;
45use crate::VTable;
46use crate::children::LayoutChildren;
47use crate::segments::SegmentId;
48use crate::segments::SegmentSource;
49use crate::vtable;
50
51/// Child index of the `elements` layout.
52pub const ELEMENTS_CHILD_INDEX: usize = 0;
53/// Child index of the `offsets` layout.
54pub const OFFSETS_CHILD_INDEX: usize = 1;
55/// Child index of the `validity` layout (only present when the list dtype is nullable).
56pub const VALIDITY_CHILD_INDEX: usize = 2;
57
58/// Number of children when the list dtype is non-nullable.
59pub const NUM_CHILDREN_NON_NULLABLE: usize = 2;
60
61vtable!(List);
62
63impl VTable for List {
64    type Layout = ListLayout;
65    type Encoding = ListLayoutEncoding;
66    type Metadata = ProstMetadata<ListLayoutMetadata>;
67
68    fn id(_encoding: &Self::Encoding) -> LayoutId {
69        static ID: CachedId = CachedId::new("vortex.list");
70        *ID
71    }
72
73    fn encoding(_layout: &Self::Layout) -> LayoutEncodingRef {
74        LayoutEncodingRef::new_ref(ListLayoutEncoding.as_ref())
75    }
76
77    fn row_count(layout: &Self::Layout) -> u64 {
78        layout.row_count()
79    }
80
81    fn dtype(layout: &Self::Layout) -> &DType {
82        &layout.dtype
83    }
84
85    fn metadata(layout: &Self::Layout) -> Self::Metadata {
86        ProstMetadata(ListLayoutMetadata::new(layout.offsets_ptype()))
87    }
88
89    fn segment_ids(_layout: &Self::Layout) -> Vec<SegmentId> {
90        vec![]
91    }
92
93    fn nchildren(layout: &Self::Layout) -> usize {
94        if layout.dtype.is_nullable() {
95            NUM_CHILDREN_NON_NULLABLE + 1
96        } else {
97            NUM_CHILDREN_NON_NULLABLE
98        }
99    }
100
101    fn child(layout: &Self::Layout, idx: usize) -> VortexResult<LayoutRef> {
102        match (idx, layout.validity.as_ref()) {
103            (ELEMENTS_CHILD_INDEX, _) => Ok(Arc::clone(&layout.elements)),
104            (OFFSETS_CHILD_INDEX, _) => Ok(Arc::clone(&layout.offsets)),
105            (VALIDITY_CHILD_INDEX, Some(validity)) => Ok(Arc::clone(validity)),
106            _ => vortex_bail!("Invalid child index {idx} for ListLayout"),
107        }
108    }
109
110    fn child_type(layout: &Self::Layout, idx: usize) -> LayoutChildType {
111        match (idx, layout.validity.is_some()) {
112            (ELEMENTS_CHILD_INDEX, _) => LayoutChildType::Auxiliary("elements".into()),
113            (OFFSETS_CHILD_INDEX, _) => LayoutChildType::Auxiliary("offsets".into()),
114            (VALIDITY_CHILD_INDEX, true) => LayoutChildType::Auxiliary("validity".into()),
115            _ => vortex_panic!("Invalid child index {idx} for ListLayout"),
116        }
117    }
118
119    fn new_reader(
120        layout: &Self::Layout,
121        name: Arc<str>,
122        segment_source: Arc<dyn SegmentSource>,
123        session: &VortexSession,
124        ctx: &LayoutReaderContext,
125    ) -> VortexResult<LayoutReaderRef> {
126        Ok(Arc::new(ListReader::try_new(
127            layout.clone(),
128            name,
129            segment_source,
130            session.clone(),
131            ctx,
132        )?))
133    }
134
135    fn build(
136        _encoding: &Self::Encoding,
137        dtype: &DType,
138        _row_count: u64,
139        metadata: &<Self::Metadata as DeserializeMetadata>::Output,
140        _segment_ids: Vec<SegmentId>,
141        children: &dyn LayoutChildren,
142        _ctx: &LayoutBuildContext<'_>,
143    ) -> VortexResult<Self::Layout> {
144        validate_children(dtype, children.nchildren())?;
145
146        let elements_dtype = dtype
147            .as_list_element_opt()
148            .ok_or_else(|| vortex_err!("ListLayout requires a List dtype, got {dtype}"))?;
149        let elements = children.child(ELEMENTS_CHILD_INDEX, elements_dtype.as_ref())?;
150
151        let offsets_dtype = DType::Primitive(metadata.offsets_ptype(), Nullability::NonNullable);
152        let offsets = children.child(OFFSETS_CHILD_INDEX, &offsets_dtype)?;
153
154        let validity = dtype
155            .is_nullable()
156            .then(|| children.child(VALIDITY_CHILD_INDEX, &DType::Bool(Nullability::NonNullable)))
157            .transpose()?;
158
159        Ok(ListLayout {
160            dtype: dtype.clone(),
161            elements,
162            offsets,
163            validity,
164        })
165    }
166
167    fn with_children(layout: &mut Self::Layout, children: Vec<LayoutRef>) -> VortexResult<()> {
168        validate_children(layout.dtype(), children.len())?;
169
170        let mut iter = children.into_iter();
171        layout.elements = iter
172            .next()
173            .ok_or_else(|| vortex_err!("missing elements child"))?;
174        layout.offsets = iter
175            .next()
176            .ok_or_else(|| vortex_err!("missing offsets child"))?;
177        layout.validity = layout
178            .dtype
179            .is_nullable()
180            .then(|| {
181                iter.next()
182                    .ok_or_else(|| vortex_err!("missing validity child"))
183            })
184            .transpose()?;
185        Ok(())
186    }
187}
188
189/// Validates expected number of children based on `dtype`
190fn validate_children(dtype: &DType, n_children: usize) -> VortexResult<()> {
191    let expected = if dtype.is_nullable() {
192        NUM_CHILDREN_NON_NULLABLE + 1
193    } else {
194        NUM_CHILDREN_NON_NULLABLE
195    };
196
197    vortex_ensure_eq!(n_children, expected);
198    Ok(())
199}
200
201#[derive(Debug)]
202pub struct ListLayoutEncoding;
203
204/// Stores a list-typed array by shredding `elements`, `offsets`, and optional `validity` children.
205#[derive(Clone, Debug)]
206pub struct ListLayout {
207    dtype: DType,
208    elements: LayoutRef,
209    offsets: LayoutRef,
210    validity: Option<LayoutRef>,
211}
212
213impl ListLayout {
214    /// Construct a new `ListLayout` from its components.
215    ///
216    /// # Invariants
217    ///
218    /// - `dtype` must be a [`DType::List`].
219    /// - `validity` must be `Some` iff `dtype.is_nullable()`.
220    /// - `offsets.dtype()` must be a non-nullable integer.
221    /// - `offsets.row_count()` is the Arrow-canonical `n+1` for `n` lists (or `0` for empty).
222    /// - When present, `validity.row_count() == offsets.row_count().saturating_sub(1)`.
223    pub fn new(
224        dtype: DType,
225        elements: LayoutRef,
226        offsets: LayoutRef,
227        validity: Option<LayoutRef>,
228    ) -> Self {
229        Self {
230            dtype,
231            elements,
232            offsets,
233            validity,
234        }
235    }
236
237    /// Number of lists in this layout.
238    #[inline]
239    pub fn row_count(&self) -> u64 {
240        self.offsets.row_count().saturating_sub(1)
241    }
242
243    #[inline]
244    pub fn elements(&self) -> &LayoutRef {
245        &self.elements
246    }
247
248    #[inline]
249    pub fn offsets(&self) -> &LayoutRef {
250        &self.offsets
251    }
252
253    #[inline]
254    pub fn validity(&self) -> Option<&LayoutRef> {
255        self.validity.as_ref()
256    }
257
258    /// The integer type used for the `offsets` child layout.
259    #[inline]
260    pub fn offsets_ptype(&self) -> PType {
261        self.offsets.dtype().as_ptype()
262    }
263
264    /// The dtype of the inner elements column.
265    pub fn elements_dtype(&self) -> &DType {
266        self.dtype
267            .as_list_element_opt()
268            .vortex_expect("ListLayout dtype must be a List")
269    }
270}
271
272#[derive(prost::Message)]
273pub struct ListLayoutMetadata {
274    #[prost(enumeration = "PType", tag = "1")]
275    offsets_ptype: i32,
276}
277
278impl ListLayoutMetadata {
279    pub fn new(offsets_ptype: PType) -> Self {
280        let mut metadata = Self::default();
281        metadata.set_offsets_ptype(offsets_ptype);
282        metadata
283    }
284}