vortex_array/arrays/masked/vtable/
mod.rs1mod canonical;
4mod operations;
5mod validity;
6
7use std::hash::Hasher;
8
9use smallvec::smallvec;
10use vortex_error::VortexExpect;
11use vortex_error::VortexResult;
12use vortex_error::vortex_bail;
13use vortex_error::vortex_ensure;
14use vortex_error::vortex_ensure_eq;
15use vortex_error::vortex_panic;
16use vortex_session::VortexSession;
17use vortex_session::registry::CachedId;
18
19use crate::AnyCanonical;
20use crate::ArrayEq;
21use crate::ArrayHash;
22use crate::ArrayParts;
23use crate::ArrayRef;
24use crate::Canonical;
25use crate::EqMode;
26use crate::IntoArray;
27use crate::VortexSessionExecute;
28use crate::array::Array;
29use crate::array::ArrayId;
30use crate::array::ArrayView;
31use crate::array::VTable;
32use crate::array::validity_to_child;
33use crate::array::with_empty_buffers;
34use crate::arrays::ConstantArray;
35use crate::arrays::masked::MaskedArrayExt;
36use crate::arrays::masked::MaskedArraySlotsExt;
37use crate::arrays::masked::MaskedData;
38use crate::arrays::masked::array::MaskedSlots;
39use crate::arrays::masked::compute::rules::PARENT_RULES;
40use crate::arrays::masked::mask_validity_canonical;
41use crate::buffer::BufferHandle;
42use crate::dtype::DType;
43use crate::executor::ExecutionCtx;
44use crate::executor::ExecutionResult;
45use crate::legacy_session;
46use crate::require_child;
47use crate::scalar::Scalar;
48use crate::serde::ArrayChildren;
49use crate::validity::Validity;
50pub type MaskedArray = Array<Masked>;
52
53#[derive(Clone, Debug)]
54pub struct Masked;
55
56impl ArrayHash for MaskedData {
57 fn array_hash<H: Hasher>(&self, _state: &mut H, _accuracy: EqMode) {}
58}
59
60impl ArrayEq for MaskedData {
61 fn array_eq(&self, _other: &Self, _accuracy: EqMode) -> bool {
62 true
63 }
64}
65
66impl VTable for Masked {
67 type TypedArrayData = MaskedData;
68
69 type OperationsVTable = Self;
70 type ValidityVTable = Self;
71
72 fn id(&self) -> ArrayId {
73 static ID: CachedId = CachedId::new("vortex.masked");
74 *ID
75 }
76
77 #[expect(clippy::disallowed_methods)]
78 fn validate(
79 &self,
80 _data: &MaskedData,
81 dtype: &DType,
82 len: usize,
83 slots: &[Option<ArrayRef>],
84 ) -> VortexResult<()> {
85 vortex_ensure!(
86 slots[MaskedSlots::CHILD].is_some(),
87 "MaskedArray child slot must be present"
88 );
89 let child = slots[MaskedSlots::CHILD]
90 .as_ref()
91 .vortex_expect("validated child slot");
92 vortex_ensure_eq!(child.len(), len, "MaskedArray child length mismatch");
93 vortex_ensure_eq!(
94 child.dtype().as_nullable(),
95 *dtype,
96 "MaskedArray dtype does not match child and validity",
97 );
98 vortex_ensure!(
99 child.all_valid(&mut legacy_session().create_execution_ctx())?,
100 "MaskedArray children must not have nulls",
101 );
102 Ok(())
103 }
104
105 fn nbuffers(_array: ArrayView<'_, Self>) -> usize {
106 0
107 }
108
109 fn buffer(_array: ArrayView<'_, Self>, _idx: usize) -> BufferHandle {
110 vortex_panic!("MaskedArray has no buffers")
111 }
112
113 fn buffer_name(_array: ArrayView<'_, Self>, _idx: usize) -> Option<String> {
114 None
115 }
116
117 fn with_buffers(
118 &self,
119 array: ArrayView<'_, Self>,
120 buffers: &[BufferHandle],
121 ) -> VortexResult<ArrayParts<Self>> {
122 with_empty_buffers(self, array, buffers)
123 }
124
125 fn serialize(
126 _array: ArrayView<'_, Self>,
127 _session: &VortexSession,
128 ) -> VortexResult<Option<Vec<u8>>> {
129 Ok(Some(vec![]))
130 }
131
132 #[allow(clippy::disallowed_methods)]
133 fn deserialize(
134 &self,
135 dtype: &DType,
136 len: usize,
137 metadata: &[u8],
138
139 buffers: &[BufferHandle],
140 children: &dyn ArrayChildren,
141 _session: &VortexSession,
142 ) -> VortexResult<ArrayParts<Self>> {
143 if !metadata.is_empty() {
144 vortex_bail!(
145 "MaskedArray expects empty metadata, got {} bytes",
146 metadata.len()
147 );
148 }
149 if !buffers.is_empty() {
150 vortex_bail!("Expected 0 buffer, got {}", buffers.len());
151 }
152
153 vortex_ensure!(
154 children.len() == 1 || children.len() == 2,
155 "`MaskedArray::build` expects 1 or 2 children, got {}",
156 children.len()
157 );
158
159 let child = children.get(0, &dtype.as_nonnullable(), len)?;
160
161 let validity = if children.len() == 2 {
162 let validity = children.get(1, &Validity::DTYPE, len)?;
163 Validity::Array(validity)
164 } else {
165 Validity::from(dtype.nullability())
166 };
167
168 let validity_slot = validity_to_child(&validity, len);
169 let data = MaskedData::try_new(
170 len,
171 child.all_valid(&mut legacy_session().create_execution_ctx())?,
172 validity,
173 )?;
174 Ok(ArrayParts::new(
175 self.clone(),
176 dtype.clone(),
177 len,
178 data,
179 smallvec![Some(child), validity_slot],
180 ))
181 }
182
183 fn execute(array: Array<Self>, ctx: &mut ExecutionCtx) -> VortexResult<ExecutionResult> {
184 let array = require_child!(array, array.child(), MaskedSlots::CHILD => AnyCanonical);
185
186 let validity = array.masked_validity();
187
188 if validity.definitely_all_null() {
190 return Ok(ExecutionResult::done(
191 ConstantArray::new(Scalar::null(array.dtype().as_nullable()), array.len())
192 .into_array(),
193 ));
194 }
195
196 let child = Canonical::from(array.child().as_::<AnyCanonical>());
203 Ok(ExecutionResult::done(
204 mask_validity_canonical(child, validity, ctx)?.into_array(),
205 ))
206 }
207
208 fn reduce_parent(
209 array: ArrayView<'_, Self>,
210 parent: &ArrayRef,
211 child_idx: usize,
212 ) -> VortexResult<Option<ArrayRef>> {
213 PARENT_RULES.evaluate(array, parent, child_idx)
214 }
215 fn slot_name(_array: ArrayView<'_, Self>, idx: usize) -> String {
216 MaskedSlots::NAMES[idx].to_string()
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use rstest::rstest;
223 use vortex_buffer::ByteBufferMut;
224 use vortex_error::VortexError;
225 use vortex_session::registry::ReadContext;
226
227 use crate::ArrayContext;
228 use crate::Canonical;
229 use crate::IntoArray;
230 use crate::VortexSessionExecute;
231 use crate::array_session;
232 use crate::arrays::Masked;
233 use crate::arrays::MaskedArray;
234 use crate::arrays::PrimitiveArray;
235 use crate::dtype::Nullability;
236 use crate::serde::SerializeOptions;
237 use crate::serde::SerializedArray;
238 use crate::validity::Validity;
239
240 #[rstest]
241 #[case(
242 MaskedArray::try_new(
243 PrimitiveArray::from_iter([1i32, 2, 3]).into_array(),
244 Validity::AllValid
245 ).unwrap()
246 )]
247 #[case(
248 MaskedArray::try_new(
249 PrimitiveArray::from_iter([1i32, 2, 3, 4, 5]).into_array(),
250 Validity::from_iter([true, true, false, true, false])
251 ).unwrap()
252 )]
253 #[case(
254 MaskedArray::try_new(
255 PrimitiveArray::from_iter(0..100).into_array(),
256 Validity::from_iter((0..100).map(|i| i % 3 != 0))
257 ).unwrap()
258 )]
259 fn test_serde_roundtrip(#[case] array: MaskedArray) {
260 let dtype = array.dtype().clone();
261 let len = array.len();
262
263 let ctx = ArrayContext::empty();
264 let serialized = array
265 .clone()
266 .into_array()
267 .serialize(&ctx, &array_session(), &SerializeOptions::default())
268 .unwrap();
269
270 let mut concat = ByteBufferMut::empty();
272 for buf in serialized {
273 concat.extend_from_slice(buf.as_ref());
274 }
275 let concat = concat.freeze();
276
277 let parts = SerializedArray::try_from(concat).unwrap();
278 let decoded = parts
279 .decode(
280 &dtype,
281 len,
282 &ReadContext::new(ctx.to_ids()),
283 &array_session(),
284 )
285 .unwrap();
286
287 assert!(decoded.is::<Masked>());
288 assert_eq!(
289 array.as_ref().display_values().to_string(),
290 decoded.display_values().to_string()
291 );
292 }
293
294 #[test]
300 fn test_execute_with_all_valid_preserves_nullable_dtype() -> Result<(), VortexError> {
301 let child = PrimitiveArray::from_iter([1i32, 2, 3]).into_array();
305 assert_eq!(child.dtype().nullability(), Nullability::NonNullable);
306
307 let array = MaskedArray::try_new(child, Validity::AllValid)?;
308 assert_eq!(array.dtype().nullability(), Nullability::Nullable);
309
310 let mut ctx = array_session().create_execution_ctx();
312 let result: Canonical = array.into_array().execute(&mut ctx)?;
313
314 assert_eq!(
315 result.dtype().nullability(),
316 Nullability::Nullable,
317 "MaskedArray execute should produce Nullable dtype"
318 );
319
320 Ok(())
321 }
322}