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(
235 ArrayParts::new(Dict, dtype, len, data)
236 .with_slots(smallvec![Some(codes), Some(values)]),
237 )
238 }
239
240 pub unsafe fn new_unchecked(codes: ArrayRef, values: ArrayRef) -> Self {
246 let dtype = values
247 .dtype()
248 .union_nullability(codes.dtype().nullability());
249 let len = codes.len();
250 let data = unsafe { DictData::new_unchecked() };
251 unsafe {
252 Array::from_parts_unchecked(
253 ArrayParts::new(Dict, dtype, len, data)
254 .with_slots(smallvec![Some(codes), Some(values)]),
255 )
256 }
257 }
258
259 pub unsafe fn set_all_values_referenced(self, all_values_referenced: bool) -> Self {
265 let dtype = self.dtype().clone();
266 let len = self.len();
267 let slots: ArraySlots = self.slots().iter().cloned().collect();
268 let data = unsafe {
269 self.into_data()
270 .set_all_values_referenced(all_values_referenced)
271 };
272 unsafe {
273 Array::from_parts_unchecked(ArrayParts::new(Dict, dtype, len, data).with_slots(slots))
274 }
275 }
276}
277
278#[cfg(test)]
279mod test {
280 use rand::RngExt;
281 use rand::SeedableRng;
282 use rand::distr::Distribution;
283 use rand::distr::StandardUniform;
284 use rand::prelude::StdRng;
285 use vortex_buffer::BitBuffer;
286 use vortex_buffer::buffer;
287 use vortex_error::VortexExpect;
288 use vortex_error::VortexResult;
289 use vortex_error::vortex_panic;
290 use vortex_mask::AllOr;
291
292 use crate::ArrayRef;
293 use crate::IntoArray;
294 use crate::VortexSessionExecute;
295 use crate::array_session;
296 use crate::arrays::ChunkedArray;
297 use crate::arrays::DictArray;
298 use crate::arrays::PrimitiveArray;
299 use crate::arrays::VarBinViewArray;
300 use crate::assert_arrays_eq;
301 use crate::builders::VarBinBuilder;
302 use crate::builders::builder_with_capacity;
303 use crate::dtype::DType;
304 use crate::dtype::NativePType;
305 use crate::dtype::Nullability::NonNullable;
306 use crate::dtype::PType;
307 use crate::dtype::UnsignedPType;
308 use crate::validity::Validity;
309
310 #[test]
311 fn nullable_codes_validity() {
312 let dict = DictArray::try_new(
313 PrimitiveArray::new(
314 buffer![0u32, 1, 2, 2, 1],
315 Validity::from(BitBuffer::from(vec![true, false, true, false, true])),
316 )
317 .into_array(),
318 PrimitiveArray::new(buffer![3, 6, 9], Validity::AllValid).into_array(),
319 )
320 .unwrap();
321 let mask = dict
322 .as_ref()
323 .validity()
324 .unwrap()
325 .execute_mask(
326 dict.as_ref().len(),
327 &mut array_session().create_execution_ctx(),
328 )
329 .unwrap();
330 let AllOr::Some(indices) = mask.indices() else {
331 vortex_panic!("Expected indices from mask")
332 };
333 assert_eq!(indices, [0, 2, 4]);
334 }
335
336 #[test]
337 fn nullable_values_validity() {
338 let dict = DictArray::try_new(
339 buffer![0u32, 1, 2, 2, 1].into_array(),
340 PrimitiveArray::new(
341 buffer![3, 6, 9],
342 Validity::from(BitBuffer::from(vec![true, false, false])),
343 )
344 .into_array(),
345 )
346 .unwrap();
347 let mask = dict
348 .as_ref()
349 .validity()
350 .unwrap()
351 .execute_mask(
352 dict.as_ref().len(),
353 &mut array_session().create_execution_ctx(),
354 )
355 .unwrap();
356 let AllOr::Some(indices) = mask.indices() else {
357 vortex_panic!("Expected indices from mask")
358 };
359 assert_eq!(indices, [0]);
360 }
361
362 #[test]
363 fn nullable_codes_and_values() {
364 let dict = DictArray::try_new(
365 PrimitiveArray::new(
366 buffer![0u32, 1, 2, 2, 1],
367 Validity::from(BitBuffer::from(vec![true, false, true, false, true])),
368 )
369 .into_array(),
370 PrimitiveArray::new(
371 buffer![3, 6, 9],
372 Validity::from(BitBuffer::from(vec![false, true, true])),
373 )
374 .into_array(),
375 )
376 .unwrap();
377 let mask = dict
378 .as_ref()
379 .validity()
380 .unwrap()
381 .execute_mask(
382 dict.as_ref().len(),
383 &mut array_session().create_execution_ctx(),
384 )
385 .unwrap();
386 let AllOr::Some(indices) = mask.indices() else {
387 vortex_panic!("Expected indices from mask")
388 };
389 assert_eq!(indices, [2, 4]);
390 }
391
392 #[test]
393 fn nullable_codes_and_non_null_values() {
394 let dict = DictArray::try_new(
395 PrimitiveArray::new(
396 buffer![0u32, 1, 2, 2, 1],
397 Validity::from(BitBuffer::from(vec![true, false, true, false, true])),
398 )
399 .into_array(),
400 PrimitiveArray::new(buffer![3, 6, 9], Validity::NonNullable).into_array(),
401 )
402 .unwrap();
403 let mask = dict
404 .as_ref()
405 .validity()
406 .unwrap()
407 .execute_mask(
408 dict.as_ref().len(),
409 &mut array_session().create_execution_ctx(),
410 )
411 .unwrap();
412 let AllOr::Some(indices) = mask.indices() else {
413 vortex_panic!("Expected indices from mask")
414 };
415 assert_eq!(indices, [0, 2, 4]);
416 }
417
418 fn make_dict_primitive_chunks<T: NativePType, Code: UnsignedPType>(
419 len: usize,
420 unique_values: usize,
421 chunk_count: usize,
422 ) -> ArrayRef
423 where
424 StandardUniform: Distribution<T>,
425 {
426 let mut rng = StdRng::seed_from_u64(0);
427
428 (0..chunk_count)
429 .map(|_| {
430 let values = (0..unique_values)
431 .map(|_| rng.random::<T>())
432 .collect::<PrimitiveArray>();
433 let codes = (0..len)
434 .map(|_| {
435 Code::from(rng.random_range(0..unique_values)).vortex_expect("valid value")
436 })
437 .collect::<PrimitiveArray>();
438
439 DictArray::try_new(codes.into_array(), values.into_array())
440 .vortex_expect("DictArray creation should succeed in arbitrary impl")
441 .into_array()
442 })
443 .collect::<ChunkedArray>()
444 .into_array()
445 }
446
447 #[test]
448 fn test_dict_utf8_append_to_varbin_builder() -> VortexResult<()> {
449 let values = VarBinViewArray::from_iter_str(["zero", "one", "two"]);
450 let dict = DictArray::try_new(buffer![2u8, 0, 2, 1].into_array(), values.into_array())?;
451 let expected = VarBinViewArray::from_iter_str(["two", "zero", "two", "one"]);
452 let mut builder = VarBinBuilder::<i32>::with_capacity(dict.dtype().clone(), dict.len());
453 let mut ctx = array_session().create_execution_ctx();
454
455 dict.into_array()
456 .append_to_builder(&mut builder, &mut ctx)?;
457
458 assert_arrays_eq!(builder.finish_into_varbin(), expected, &mut ctx);
459 Ok(())
460 }
461
462 #[test]
463 fn test_dict_array_from_primitive_chunks() -> VortexResult<()> {
464 let mut ctx = array_session().create_execution_ctx();
465 let len = 2;
466 let chunk_count = 2;
467 let array = make_dict_primitive_chunks::<u64, u64>(len, 2, chunk_count);
468
469 let mut builder = builder_with_capacity(
470 &DType::Primitive(PType::U64, NonNullable),
471 len * chunk_count,
472 );
473 array.append_to_builder(
474 builder.as_mut(),
475 &mut array_session().create_execution_ctx(),
476 )?;
477
478 let into_prim = array.execute::<PrimitiveArray>(&mut ctx)?;
479 let prim_into = builder.finish_into_canonical(&mut ctx).into_primitive();
480
481 assert_arrays_eq!(into_prim, prim_into, &mut ctx);
482 Ok(())
483 }
484
485 #[cfg_attr(miri, ignore)]
486 #[test]
487 fn test_dict_metadata() {
488 use prost::Message;
489
490 use super::DictMetadata;
491 use crate::test_harness::check_metadata;
492
493 check_metadata(
494 "dict.metadata",
495 &DictMetadata {
496 codes_ptype: PType::U64 as i32,
497 values_len: u32::MAX,
498 is_nullable_codes: None,
499 all_values_referenced: None,
500 }
501 .encode_to_vec(),
502 );
503 }
504}