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