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