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_error::vortex_ensure!(
90            offsets.row_count().saturating_sub(1) == args.row_count,
91            "List offsets row count does not match parent"
92        );
93        if args.dtype.is_nullable() {
94            let validity = args
95                .children
96                .child(VALIDITY_CHILD_INDEX, &DType::Bool(Nullability::NonNullable))?;
97            vortex_error::vortex_ensure!(
98                validity.row_count() == args.row_count,
99                "List validity row count does not match parent"
100            );
101        }
102        Ok(ListData {
103            offsets_ptype: metadata.offsets_ptype(),
104        })
105    }
106
107    fn nslots(_layout: &Layout<Self>) -> usize {
108        // Elements, offsets, and an always-slotted (optionally present) validity child.
109        VALIDITY_CHILD_INDEX + 1
110    }
111
112    fn slot_to_child(layout: &Layout<Self>, slot: usize) -> Option<usize> {
113        match slot {
114            ELEMENTS_CHILD_INDEX | OFFSETS_CHILD_INDEX => Some(slot),
115            VALIDITY_CHILD_INDEX => layout.dtype().is_nullable().then_some(VALIDITY_CHILD_INDEX),
116            _ => None,
117        }
118    }
119
120    fn child_dtype(layout: &Layout<Self>, idx: usize) -> VortexResult<DType> {
121        match idx {
122            ELEMENTS_CHILD_INDEX => layout
123                .dtype()
124                .as_list_element_opt()
125                .map(|dtype| dtype.as_ref().clone())
126                .ok_or_else(|| vortex_err!("ListLayout requires a List dtype")),
127            OFFSETS_CHILD_INDEX => Ok(DType::Primitive(
128                layout.offsets_ptype,
129                Nullability::NonNullable,
130            )),
131            VALIDITY_CHILD_INDEX if layout.dtype().is_nullable() => {
132                Ok(DType::Bool(Nullability::NonNullable))
133            }
134            _ => vortex_bail!("Invalid child index {idx} for ListLayout"),
135        }
136    }
137
138    fn child_type(layout: &Layout<Self>, idx: usize) -> LayoutChildType {
139        match idx {
140            ELEMENTS_CHILD_INDEX => LayoutChildType::Auxiliary("elements".into()),
141            OFFSETS_CHILD_INDEX => LayoutChildType::Auxiliary("offsets".into()),
142            VALIDITY_CHILD_INDEX if layout.dtype().is_nullable() => {
143                LayoutChildType::Auxiliary("validity".into())
144            }
145            _ => vortex_panic!("Invalid child index {idx} for ListLayout"),
146        }
147    }
148
149    fn new_reader(
150        layout: &Layout<Self>,
151        name: Arc<str>,
152        segment_source: Arc<dyn SegmentSource>,
153        session: &VortexSession,
154        ctx: &LayoutReaderContext,
155    ) -> VortexResult<LayoutReaderRef> {
156        Ok(Arc::new(ListReader::try_new(
157            layout.clone(),
158            name,
159            segment_source,
160            session.clone(),
161            ctx,
162        )?))
163    }
164}
165
166impl Layout<List> {
167    /// Construct a list layout from its children.
168    pub fn new(
169        dtype: DType,
170        elements: LayoutRef,
171        offsets: LayoutRef,
172        validity: Option<LayoutRef>,
173    ) -> Self {
174        let row_count = offsets.row_count().saturating_sub(1);
175        let offsets_ptype = offsets.dtype().as_ptype();
176        let mut children = vec![elements, offsets];
177        children.extend(validity);
178        Self::validate_children(&dtype, children.len()).vortex_expect("invalid list children");
179        LayoutParts::new(
180            List,
181            dtype,
182            row_count,
183            Vec::new(),
184            OwnedLayoutChildren::layout_children(children),
185            ListData { offsets_ptype },
186        )
187        .into_typed()
188    }
189
190    /// Returns the elements child.
191    pub fn elements(&self) -> VortexResult<LayoutRef> {
192        self.slot(ELEMENTS_CHILD_INDEX)?
193            .ok_or_else(|| vortex_err!("ListLayout elements slot is absent"))
194    }
195
196    /// Returns the offsets child.
197    pub fn offsets(&self) -> VortexResult<LayoutRef> {
198        self.slot(OFFSETS_CHILD_INDEX)?
199            .ok_or_else(|| vortex_err!("ListLayout offsets slot is absent"))
200    }
201
202    /// Returns the optional validity child.
203    pub fn validity(&self) -> VortexResult<Option<LayoutRef>> {
204        self.slot(VALIDITY_CHILD_INDEX)
205    }
206
207    /// Returns the integer ptype used by offsets.
208    pub fn offsets_ptype(&self) -> PType {
209        self.offsets_ptype
210    }
211
212    /// Returns the list element dtype.
213    pub fn elements_dtype(&self) -> &DType {
214        self.dtype()
215            .as_list_element_opt()
216            .vortex_expect("ListLayout dtype must be a List")
217    }
218
219    fn validate_children(dtype: &DType, nchildren: usize) -> VortexResult<()> {
220        let expected = NUM_CHILDREN_NON_NULLABLE + usize::from(dtype.is_nullable());
221        vortex_ensure_eq!(nchildren, expected);
222        Ok(())
223    }
224}
225
226#[derive(prost::Message)]
227pub struct ListLayoutMetadata {
228    #[prost(enumeration = "PType", tag = "1")]
229    offsets_ptype: i32,
230}
231
232impl ListLayoutMetadata {
233    pub fn new(offsets_ptype: PType) -> Self {
234        let mut metadata = Self::default();
235        metadata.set_offsets_ptype(offsets_ptype);
236        metadata
237    }
238}