Skip to main content

vortex_array/arrays/map/
array.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::fmt::Display;
5use std::fmt::Formatter;
6use std::hash::Hasher;
7use std::sync::Arc;
8
9use vortex_error::VortexExpect;
10use vortex_error::VortexResult;
11use vortex_error::vortex_ensure;
12
13use crate::ArrayEq;
14use crate::ArrayHash;
15use crate::ArrayRef;
16use crate::ArraySlots;
17use crate::EqMode;
18use crate::IntoArray;
19use crate::array::Array;
20use crate::array::ArrayParts;
21use crate::array::TypedArrayRef;
22use crate::array_slots;
23use crate::arrays::ListView;
24use crate::arrays::ListViewArray;
25use crate::arrays::listview::ListViewArrayExt;
26use crate::arrays::map::Map;
27use crate::dtype::DType;
28use crate::dtype::MapDType;
29use crate::validity::Validity;
30
31#[array_slots(Map)]
32pub struct MapSlots {
33    /// The list-view storage of non-null `{key, value}` entry structs.
34    #[slot(0)]
35    pub entries: ArrayRef,
36}
37
38/// Encoding-specific metadata for [`crate::arrays::MapArray`].
39///
40/// All map metadata is represented by the outer [`DType::Map`] and the entries child, so this
41/// value is intentionally empty.
42#[derive(Clone, Debug, Default)]
43pub struct MapData;
44
45impl Display for MapData {
46    fn fmt(&self, _f: &mut Formatter<'_>) -> std::fmt::Result {
47        Ok(())
48    }
49}
50
51impl ArrayEq for MapData {
52    fn array_eq(&self, _other: &Self, _accuracy: EqMode) -> bool {
53        true
54    }
55}
56
57impl ArrayHash for MapData {
58    fn array_hash<H: Hasher>(&self, _state: &mut H, _accuracy: EqMode) {}
59}
60
61impl MapData {
62    pub(crate) fn make_slots(entries: ArrayRef) -> ArraySlots {
63        MapSlots { entries }.into_slots()
64    }
65}
66
67/// The logical and physical inputs used to construct a [`crate::arrays::MapArray`].
68pub struct MapDataParts {
69    /// The key/value type and sortedness assertion for the map.
70    pub map_dtype: MapDType,
71    /// The physical list-view storage of `{key, value}` entry structs.
72    pub entries: ListViewArray,
73}
74
75/// Accessors for the canonical map representation.
76pub trait MapArrayExt: MapArraySlotsExt {
77    /// Returns the entry structs for one map row.
78    fn entries_at(&self, index: usize) -> VortexResult<ArrayRef> {
79        self.entries().as_::<ListView>().list_elements_at(index)
80    }
81
82    /// Returns the number of entries in one map row.
83    fn entry_count_at(&self, index: usize) -> usize {
84        self.entries().as_::<ListView>().size_at(index)
85    }
86
87    /// Returns the outer map validity delegated from the entries list-view.
88    fn map_validity(&self) -> Validity {
89        self.entries().as_::<ListView>().listview_validity()
90    }
91
92    /// Returns this map's key/value type information.
93    fn map_dtype(&self) -> &MapDType {
94        self.as_ref()
95            .dtype()
96            .as_map_opt()
97            .vortex_expect("MapArray requires a map dtype")
98    }
99
100    /// Returns whether producers assert sorted keys within each map value.
101    fn keys_sorted(&self) -> bool {
102        self.map_dtype().keys_sorted()
103    }
104}
105impl<T: TypedArrayRef<Map>> MapArrayExt for T {}
106
107impl Array<Map> {
108    /// Creates a canonical map array from its map dtype and list-view entry storage.
109    ///
110    /// # Panics
111    ///
112    /// Panics if `entries` is not a list of the map dtype's non-nullable `{key, value}` entry
113    /// struct with matching outer nullability.
114    pub fn new(map_dtype: MapDType, entries: ListViewArray) -> Self {
115        Self::try_new(map_dtype, entries).vortex_expect("MapArray construction failed")
116    }
117
118    /// Constructs a canonical map array from its map dtype and list-view entry storage.
119    ///
120    /// # Errors
121    ///
122    /// Returns an error when the entry child is not `ListView<Struct<key, value>>`, has a
123    /// different outer nullability, or has a different length than the outer map array.
124    pub fn try_new(map_dtype: MapDType, entries: ListViewArray) -> VortexResult<Self> {
125        let nullability = entries.nullability();
126        let dtype = DType::Map(map_dtype, nullability);
127        let len = entries.len();
128        let slots = MapData::make_slots(entries.into_array());
129        let parts = ArrayParts::new(Map, dtype, len, MapData).with_slots(slots);
130        Self::try_from_parts(parts)
131    }
132
133    /// Creates a canonical map array without validating its entry storage.
134    ///
135    /// # Safety
136    ///
137    /// The caller must ensure that `entries` has dtype
138    /// `List(Struct { key, value }, entries.nullability())`, where the struct exactly matches
139    /// `map_dtype.entries_dtype()`.
140    pub unsafe fn new_unchecked(map_dtype: MapDType, entries: ListViewArray) -> Self {
141        let nullability = entries.nullability();
142        let dtype = DType::Map(map_dtype, nullability);
143        let len = entries.len();
144        let slots = MapData::make_slots(entries.into_array());
145        let parts = ArrayParts::new(Map, dtype, len, MapData).with_slots(slots);
146        unsafe { Self::from_parts_unchecked(parts) }
147    }
148
149    /// Decomposes this map array into its logical dtype and physical entries child.
150    pub fn into_data_parts(self) -> MapDataParts {
151        let map_dtype = self
152            .dtype()
153            .as_map_opt()
154            .vortex_expect("MapArray requires a map dtype")
155            .clone();
156        let entries = self.entries().clone().downcast::<ListView>();
157        MapDataParts { map_dtype, entries }
158    }
159}
160
161fn expected_entries_dtype(map_dtype: &MapDType, nullability: crate::dtype::Nullability) -> DType {
162    DType::List(Arc::new(map_dtype.entries_dtype()), nullability)
163}
164
165pub(super) fn validate_entries(
166    map_dtype: &MapDType,
167    nullability: crate::dtype::Nullability,
168    len: usize,
169    entries: &ArrayRef,
170) -> VortexResult<()> {
171    vortex_ensure!(
172        entries.is::<ListView>(),
173        "MapArray entries must use vortex.listview encoding, got {}",
174        entries.encoding_id()
175    );
176    vortex_ensure!(
177        entries.len() == len,
178        "MapArray entries length {} does not match outer length {len}",
179        entries.len()
180    );
181
182    let expected_dtype = expected_entries_dtype(map_dtype, nullability);
183    vortex_ensure!(
184        entries.dtype() == &expected_dtype,
185        "MapArray entries dtype {} does not match expected {expected_dtype}",
186        entries.dtype()
187    );
188
189    Ok(())
190}