vortex_array/arrays/dict/
array.rs1use std::fmt::Display;
5use std::fmt::Formatter;
6
7use num_traits::AsPrimitive;
8use smallvec::smallvec;
9use vortex_buffer::BitBuffer;
10use vortex_error::VortexExpect;
11use vortex_error::VortexResult;
12use vortex_error::vortex_bail;
13use vortex_error::vortex_ensure;
14use vortex_mask::AllOr;
15
16use crate::ArrayRef;
17use crate::ArraySlots;
18use crate::ExecutionCtx;
19use crate::array::Array;
20use crate::array::ArrayParts;
21use crate::array::TypedArrayRef;
22use crate::array_slots;
23use crate::arrays::Dict;
24use crate::arrays::PrimitiveArray;
25use crate::dtype::DType;
26use crate::dtype::PType;
27use crate::match_each_integer_ptype;
28
29#[derive(Clone, prost::Message)]
30pub struct DictMetadata {
31 #[prost(uint32, tag = "1")]
32 pub(super) values_len: u32,
33 #[prost(enumeration = "PType", tag = "2")]
34 pub(super) codes_ptype: i32,
35 #[prost(optional, bool, tag = "3")]
37 pub(super) is_nullable_codes: Option<bool>,
38 #[prost(optional, bool, tag = "4")]
42 pub(super) all_values_referenced: Option<bool>,
43}
44
45#[array_slots(Dict)]
46pub struct DictSlots {
47 pub codes: ArrayRef,
49 pub values: ArrayRef,
51}
52
53#[derive(Debug, Clone)]
54pub struct DictData {
55 pub(super) all_values_referenced: bool,
61}
62
63impl Display for DictData {
64 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
65 write!(f, "all_values_referenced: {}", self.all_values_referenced)
66 }
67}
68
69impl DictData {
70 pub unsafe fn new_unchecked() -> Self {
77 Self {
78 all_values_referenced: false,
79 }
80 }
81
82 pub unsafe fn set_all_values_referenced(mut self, all_values_referenced: bool) -> Self {
92 self.all_values_referenced = all_values_referenced;
93 self
94 }
95
96 pub fn new(codes_dtype: &DType) -> Self {
101 Self::try_new(codes_dtype).vortex_expect("DictArray new")
102 }
103
104 pub(crate) fn try_new(codes_dtype: &DType) -> VortexResult<Self> {
116 if !codes_dtype.is_int() {
117 vortex_bail!(MismatchedTypes: "int", codes_dtype);
118 }
119
120 Ok(unsafe { Self::new_unchecked() })
121 }
122}
123
124pub trait DictArrayExt: TypedArrayRef<Dict> + DictArraySlotsExt {
125 #[inline]
126 fn has_all_values_referenced(&self) -> bool {
127 self.all_values_referenced
128 }
129
130 fn validate_all_values_referenced(&self, ctx: &mut ExecutionCtx) -> VortexResult<()> {
131 if self.has_all_values_referenced() {
132 if !self.codes().is_host() {
133 return Ok(());
134 }
135
136 let referenced_mask = self.compute_referenced_values_mask(true, ctx)?;
137 let all_referenced = referenced_mask.true_count() == referenced_mask.len();
138
139 vortex_ensure!(all_referenced, "value in dict not referenced");
140 }
141
142 Ok(())
143 }
144
145 fn compute_referenced_values_mask(
146 &self,
147 referenced: bool,
148 ctx: &mut ExecutionCtx,
149 ) -> VortexResult<BitBuffer> {
150 let codes = self.codes();
151 let codes_validity = codes.validity()?.execute_mask(codes.len(), ctx)?;
152 let codes_primitive = codes.clone().execute::<PrimitiveArray>(ctx)?;
153 let values_len = self.values().len();
154
155 let init_value = !referenced;
156 let referenced_value = referenced;
157
158 let mut values_vec = vec![init_value; values_len];
159 match codes_validity.bit_buffer() {
160 AllOr::All => {
161 match_each_integer_ptype!(codes_primitive.ptype(), |P| {
162 for idx in codes_primitive.as_slice::<P>() {
163 let idxu: usize = idx.as_();
164 values_vec[idxu] = referenced_value;
165 }
166 });
167 }
168 AllOr::None => {}
169 AllOr::Some(mask) => {
170 match_each_integer_ptype!(codes_primitive.ptype(), |P| {
171 let codes = codes_primitive.as_slice::<P>();
172 mask.set_indices().for_each(|idx| {
173 let idxu: usize = codes[idx].as_();
174 values_vec[idxu] = referenced_value;
175 });
176 });
177 }
178 }
179
180 Ok(BitBuffer::from(values_vec))
181 }
182}
183impl<T: TypedArrayRef<Dict>> DictArrayExt for T {}
184
185pub struct DictParts {
187 pub dtype: DType,
188 pub codes: ArrayRef,
189 pub values: ArrayRef,
190}
191
192pub trait DictOwnedExt {
193 fn into_parts(self) -> DictParts;
194}
195
196impl DictOwnedExt for Array<Dict> {
197 fn into_parts(self) -> DictParts {
198 match self.try_into_parts() {
199 Ok(array_parts) => {
200 let slots = DictSlots::from_slots(array_parts.slots);
201 DictParts {
202 dtype: array_parts.dtype,
203 codes: slots.codes,
204 values: slots.values,
205 }
206 }
207 Err(array) => {
208 let slots = DictSlotsView::from_slots(array.slots());
209 DictParts {
210 dtype: array.dtype().clone(),
211 codes: slots.codes.clone(),
212 values: slots.values.clone(),
213 }
214 }
215 }
216 }
217}
218
219impl Array<Dict> {
220 pub fn new(codes: ArrayRef, values: ArrayRef) -> Self {
222 Self::try_new(codes, values).vortex_expect("DictArray new")
223 }
224
225 pub fn try_new(codes: ArrayRef, values: ArrayRef) -> VortexResult<Self> {
227 let dtype = values
228 .dtype()
229 .union_nullability(codes.dtype().nullability());
230 let len = codes.len();
231 let data = DictData::try_new(codes.dtype())?;
232 Array::try_from_parts(
233 ArrayParts::new(Dict, dtype, len, data)
234 .with_slots(smallvec![Some(codes), Some(values)]),
235 )
236 }
237
238 pub unsafe fn new_unchecked(codes: ArrayRef, values: ArrayRef) -> Self {
244 let dtype = values
245 .dtype()
246 .union_nullability(codes.dtype().nullability());
247 let len = codes.len();
248 let data = unsafe { DictData::new_unchecked() };
249 unsafe {
250 Array::from_parts_unchecked(
251 ArrayParts::new(Dict, dtype, len, data)
252 .with_slots(smallvec![Some(codes), Some(values)]),
253 )
254 }
255 }
256
257 pub unsafe fn set_all_values_referenced(self, all_values_referenced: bool) -> Self {
263 let dtype = self.dtype().clone();
264 let len = self.len();
265 let slots: ArraySlots = self.slots().iter().cloned().collect();
266 let data = unsafe {
267 self.into_data()
268 .set_all_values_referenced(all_values_referenced)
269 };
270 unsafe {
271 Array::from_parts_unchecked(ArrayParts::new(Dict, dtype, len, data).with_slots(slots))
272 }
273 }
274}
275
276#[cfg(test)]
277mod test {
278 use rand::RngExt;
279 use rand::SeedableRng;
280 use rand::distr::Distribution;
281 use rand::distr::StandardUniform;
282 use rand::prelude::StdRng;
283 use vortex_buffer::BitBuffer;
284 use vortex_buffer::buffer;
285 use vortex_error::VortexExpect;
286 use vortex_error::VortexResult;
287 use vortex_error::vortex_panic;
288 use vortex_mask::AllOr;
289
290 use crate::ArrayRef;
291 use crate::IntoArray;
292 use crate::VortexSessionExecute;
293 use crate::array_session;
294 use crate::arrays::ChunkedArray;
295 use crate::arrays::DictArray;
296 use crate::arrays::PrimitiveArray;
297 use crate::assert_arrays_eq;
298 use crate::builders::builder_with_capacity;
299 use crate::dtype::DType;
300 use crate::dtype::NativePType;
301 use crate::dtype::Nullability::NonNullable;
302 use crate::dtype::PType;
303 use crate::dtype::UnsignedPType;
304 use crate::validity::Validity;
305
306 #[test]
307 fn nullable_codes_validity() {
308 let dict = DictArray::try_new(
309 PrimitiveArray::new(
310 buffer![0u32, 1, 2, 2, 1],
311 Validity::from(BitBuffer::from(vec![true, false, true, false, true])),
312 )
313 .into_array(),
314 PrimitiveArray::new(buffer![3, 6, 9], Validity::AllValid).into_array(),
315 )
316 .unwrap();
317 let mask = dict
318 .as_ref()
319 .validity()
320 .unwrap()
321 .execute_mask(
322 dict.as_ref().len(),
323 &mut array_session().create_execution_ctx(),
324 )
325 .unwrap();
326 let AllOr::Some(indices) = mask.indices() else {
327 vortex_panic!("Expected indices from mask")
328 };
329 assert_eq!(indices, [0, 2, 4]);
330 }
331
332 #[test]
333 fn nullable_values_validity() {
334 let dict = DictArray::try_new(
335 buffer![0u32, 1, 2, 2, 1].into_array(),
336 PrimitiveArray::new(
337 buffer![3, 6, 9],
338 Validity::from(BitBuffer::from(vec![true, false, false])),
339 )
340 .into_array(),
341 )
342 .unwrap();
343 let mask = dict
344 .as_ref()
345 .validity()
346 .unwrap()
347 .execute_mask(
348 dict.as_ref().len(),
349 &mut array_session().create_execution_ctx(),
350 )
351 .unwrap();
352 let AllOr::Some(indices) = mask.indices() else {
353 vortex_panic!("Expected indices from mask")
354 };
355 assert_eq!(indices, [0]);
356 }
357
358 #[test]
359 fn nullable_codes_and_values() {
360 let dict = DictArray::try_new(
361 PrimitiveArray::new(
362 buffer![0u32, 1, 2, 2, 1],
363 Validity::from(BitBuffer::from(vec![true, false, true, false, true])),
364 )
365 .into_array(),
366 PrimitiveArray::new(
367 buffer![3, 6, 9],
368 Validity::from(BitBuffer::from(vec![false, true, true])),
369 )
370 .into_array(),
371 )
372 .unwrap();
373 let mask = dict
374 .as_ref()
375 .validity()
376 .unwrap()
377 .execute_mask(
378 dict.as_ref().len(),
379 &mut array_session().create_execution_ctx(),
380 )
381 .unwrap();
382 let AllOr::Some(indices) = mask.indices() else {
383 vortex_panic!("Expected indices from mask")
384 };
385 assert_eq!(indices, [2, 4]);
386 }
387
388 #[test]
389 fn nullable_codes_and_non_null_values() {
390 let dict = DictArray::try_new(
391 PrimitiveArray::new(
392 buffer![0u32, 1, 2, 2, 1],
393 Validity::from(BitBuffer::from(vec![true, false, true, false, true])),
394 )
395 .into_array(),
396 PrimitiveArray::new(buffer![3, 6, 9], Validity::NonNullable).into_array(),
397 )
398 .unwrap();
399 let mask = dict
400 .as_ref()
401 .validity()
402 .unwrap()
403 .execute_mask(
404 dict.as_ref().len(),
405 &mut array_session().create_execution_ctx(),
406 )
407 .unwrap();
408 let AllOr::Some(indices) = mask.indices() else {
409 vortex_panic!("Expected indices from mask")
410 };
411 assert_eq!(indices, [0, 2, 4]);
412 }
413
414 fn make_dict_primitive_chunks<T: NativePType, Code: UnsignedPType>(
415 len: usize,
416 unique_values: usize,
417 chunk_count: usize,
418 ) -> ArrayRef
419 where
420 StandardUniform: Distribution<T>,
421 {
422 let mut rng = StdRng::seed_from_u64(0);
423
424 (0..chunk_count)
425 .map(|_| {
426 let values = (0..unique_values)
427 .map(|_| rng.random::<T>())
428 .collect::<PrimitiveArray>();
429 let codes = (0..len)
430 .map(|_| {
431 Code::from(rng.random_range(0..unique_values)).vortex_expect("valid value")
432 })
433 .collect::<PrimitiveArray>();
434
435 DictArray::try_new(codes.into_array(), values.into_array())
436 .vortex_expect("DictArray creation should succeed in arbitrary impl")
437 .into_array()
438 })
439 .collect::<ChunkedArray>()
440 .into_array()
441 }
442
443 #[test]
444 fn test_dict_array_from_primitive_chunks() -> VortexResult<()> {
445 let mut ctx = array_session().create_execution_ctx();
446 let len = 2;
447 let chunk_count = 2;
448 let array = make_dict_primitive_chunks::<u64, u64>(len, 2, chunk_count);
449
450 let mut builder = builder_with_capacity(
451 &DType::Primitive(PType::U64, NonNullable),
452 len * chunk_count,
453 );
454 array.append_to_builder(
455 builder.as_mut(),
456 &mut array_session().create_execution_ctx(),
457 )?;
458
459 let into_prim = array.execute::<PrimitiveArray>(&mut ctx)?;
460 let prim_into = builder.finish_into_canonical(&mut ctx).into_primitive();
461
462 assert_arrays_eq!(into_prim, prim_into, &mut ctx);
463 Ok(())
464 }
465
466 #[cfg_attr(miri, ignore)]
467 #[test]
468 fn test_dict_metadata() {
469 use prost::Message;
470
471 use super::DictMetadata;
472 use crate::test_harness::check_metadata;
473
474 check_metadata(
475 "dict.metadata",
476 &DictMetadata {
477 codes_ptype: PType::U64 as i32,
478 values_len: u32::MAX,
479 is_nullable_codes: None,
480 all_values_referenced: None,
481 }
482 .encode_to_vec(),
483 );
484 }
485}