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