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.
5
6mod expr;
7mod reader;
8pub mod writer;
9
10use std::sync::Arc;
11
12use reader::ListReader;
13use vortex_array::ProstMetadata;
14use vortex_array::dtype::DType;
15use vortex_array::dtype::Nullability;
16use vortex_array::dtype::PType;
17use vortex_error::VortexExpect;
18use vortex_error::VortexResult;
19use vortex_error::vortex_bail;
20use vortex_error::vortex_ensure_eq;
21use vortex_error::vortex_err;
22use vortex_error::vortex_panic;
23use vortex_session::VortexSession;
24use vortex_session::registry::CachedId;
25
26use crate::Layout;
27use crate::LayoutChildType;
28use crate::LayoutDeserializeArgs;
29use crate::LayoutId;
30use crate::LayoutParts;
31use crate::LayoutReaderContext;
32use crate::LayoutReaderRef;
33use crate::LayoutRef;
34use crate::VTable;
35use crate::children::OwnedLayoutChildren;
36use crate::segments::SegmentSource;
37
38/// Child index of the elements layout.
39pub const ELEMENTS_CHILD_INDEX: usize = 0;
40/// Child index of the offsets layout.
41pub const OFFSETS_CHILD_INDEX: usize = 1;
42/// Child index of the optional validity layout.
43pub const VALIDITY_CHILD_INDEX: usize = 2;
44/// Number of children for a non-nullable list.
45pub const NUM_CHILDREN_NON_NULLABLE: usize = 2;
46
47/// List layout vtable.
48#[derive(Clone, Debug)]
49pub struct List;
50
51/// Backwards-compatible name for the list layout plugin.
52pub use List as ListLayoutEncoding;
53
54/// List-layout-specific data.
55#[derive(Clone, Debug)]
56pub struct ListData {
57    offsets_ptype: PType,
58}
59
60/// A list layout shredded into elements, offsets, and optional validity children.
61pub type ListLayout = Layout<List>;
62
63impl VTable for List {
64    type LayoutData = ListData;
65    type Metadata = ProstMetadata<ListLayoutMetadata>;
66
67    fn id(&self) -> LayoutId {
68        static ID: CachedId = CachedId::new("vortex.list");
69        *ID
70    }
71
72    fn metadata(layout: &Layout<Self>) -> Self::Metadata {
73        ProstMetadata(ListLayoutMetadata::new(layout.offsets_ptype))
74    }
75
76    fn deserialize(
77        &self,
78        args: &LayoutDeserializeArgs<'_>,
79        metadata: &ListLayoutMetadata,
80    ) -> VortexResult<Self::LayoutData> {
81        ListLayout::validate_children(args.dtype, args.children.nchildren())?;
82        let elements_dtype = args
83            .dtype
84            .as_list_element_opt()
85            .ok_or_else(|| vortex_err!("ListLayout requires a List dtype, got {}", args.dtype))?;
86        args.children.child(ELEMENTS_CHILD_INDEX, elements_dtype)?;
87        let offsets_dtype = DType::Primitive(metadata.offsets_ptype(), Nullability::NonNullable);
88        let offsets = args.children.child(OFFSETS_CHILD_INDEX, &offsets_dtype)?;
89        vortex_ensure_eq!(
90            offsets.row_count().saturating_sub(1),
91            args.row_count,
92            "List offsets row count does not match parent"
93        );
94        if args.dtype.is_nullable() {
95            let validity = args
96                .children
97                .child(VALIDITY_CHILD_INDEX, &DType::Bool(Nullability::NonNullable))?;
98            vortex_ensure_eq!(
99                validity.row_count(),
100                args.row_count,
101                "List validity row count does not match parent"
102            );
103        }
104        Ok(ListData {
105            offsets_ptype: metadata.offsets_ptype(),
106        })
107    }
108
109    fn nslots(_layout: &Layout<Self>) -> usize {
110        // Elements, offsets, and an always-slotted (optionally present) validity child.
111        VALIDITY_CHILD_INDEX + 1
112    }
113
114    fn slot_to_child(layout: &Layout<Self>, slot: usize) -> Option<usize> {
115        match slot {
116            ELEMENTS_CHILD_INDEX | OFFSETS_CHILD_INDEX => Some(slot),
117            VALIDITY_CHILD_INDEX => layout.dtype().is_nullable().then_some(VALIDITY_CHILD_INDEX),
118            _ => None,
119        }
120    }
121
122    fn child_dtype(layout: &Layout<Self>, idx: usize) -> VortexResult<DType> {
123        match idx {
124            ELEMENTS_CHILD_INDEX => layout
125                .dtype()
126                .as_list_element_opt()
127                .map(|dtype| dtype.as_ref().clone())
128                .ok_or_else(|| vortex_err!("ListLayout requires a List dtype")),
129            OFFSETS_CHILD_INDEX => Ok(DType::Primitive(
130                layout.offsets_ptype,
131                Nullability::NonNullable,
132            )),
133            VALIDITY_CHILD_INDEX if layout.dtype().is_nullable() => {
134                Ok(DType::Bool(Nullability::NonNullable))
135            }
136            _ => vortex_bail!("Invalid child index {idx} for ListLayout"),
137        }
138    }
139
140    fn child_type(layout: &Layout<Self>, idx: usize) -> LayoutChildType {
141        match idx {
142            ELEMENTS_CHILD_INDEX => LayoutChildType::Auxiliary("elements".into()),
143            OFFSETS_CHILD_INDEX => LayoutChildType::Auxiliary("offsets".into()),
144            VALIDITY_CHILD_INDEX if layout.dtype().is_nullable() => {
145                LayoutChildType::Auxiliary("validity".into())
146            }
147            _ => vortex_panic!("Invalid child index {idx} for ListLayout"),
148        }
149    }
150
151    fn new_reader(
152        layout: &Layout<Self>,
153        name: Arc<str>,
154        segment_source: Arc<dyn SegmentSource>,
155        session: &VortexSession,
156        ctx: &LayoutReaderContext,
157    ) -> VortexResult<LayoutReaderRef> {
158        Ok(Arc::new(ListReader::try_new(
159            layout.clone(),
160            name,
161            segment_source,
162            session.clone(),
163            ctx,
164        )?))
165    }
166}
167
168impl Layout<List> {
169    /// Construct a list layout from its children.
170    pub fn new(
171        dtype: DType,
172        elements: LayoutRef,
173        offsets: LayoutRef,
174        validity: Option<LayoutRef>,
175    ) -> Self {
176        let row_count = offsets.row_count().saturating_sub(1);
177        let offsets_ptype = offsets.dtype().as_ptype();
178        let mut children = vec![elements, offsets];
179        children.extend(validity);
180        Self::validate_children(&dtype, children.len()).vortex_expect("invalid list children");
181        LayoutParts::new(
182            List,
183            dtype,
184            row_count,
185            Vec::new(),
186            OwnedLayoutChildren::layout_children(children),
187            ListData { offsets_ptype },
188        )
189        .into_typed()
190    }
191
192    /// Returns the elements child.
193    pub fn elements(&self) -> VortexResult<LayoutRef> {
194        self.slot(ELEMENTS_CHILD_INDEX)?
195            .ok_or_else(|| vortex_err!("ListLayout elements slot is absent"))
196    }
197
198    /// Returns the offsets child.
199    pub fn offsets(&self) -> VortexResult<LayoutRef> {
200        self.slot(OFFSETS_CHILD_INDEX)?
201            .ok_or_else(|| vortex_err!("ListLayout offsets slot is absent"))
202    }
203
204    /// Returns the optional validity child.
205    pub fn validity(&self) -> VortexResult<Option<LayoutRef>> {
206        self.slot(VALIDITY_CHILD_INDEX)
207    }
208
209    /// Returns the integer ptype used by offsets.
210    pub fn offsets_ptype(&self) -> PType {
211        self.offsets_ptype
212    }
213
214    /// Returns the list element dtype.
215    pub fn elements_dtype(&self) -> &DType {
216        self.dtype()
217            .as_list_element_opt()
218            .vortex_expect("ListLayout dtype must be a List")
219    }
220
221    fn validate_children(dtype: &DType, nchildren: usize) -> VortexResult<()> {
222        let expected = NUM_CHILDREN_NON_NULLABLE + usize::from(dtype.is_nullable());
223        vortex_ensure_eq!(nchildren, expected);
224        Ok(())
225    }
226}
227
228#[derive(prost::Message)]
229pub struct ListLayoutMetadata {
230    #[prost(enumeration = "PType", tag = "1")]
231    offsets_ptype: i32,
232}
233
234impl ListLayoutMetadata {
235    pub fn new(offsets_ptype: PType) -> Self {
236        let mut metadata = Self::default();
237        metadata.set_offsets_ptype(offsets_ptype);
238        metadata
239    }
240}