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