1use std::hash::Hasher;
5
6use num_traits::AsPrimitive;
7use prost::Message;
8use smallvec::smallvec;
9use vortex_error::VortexResult;
10use vortex_error::vortex_bail;
11use vortex_error::vortex_ensure_eq;
12use vortex_error::vortex_err;
13use vortex_error::vortex_panic;
14use vortex_mask::AllOr;
15use vortex_mask::Mask;
16use vortex_session::VortexSession;
17use vortex_session::registry::CachedId;
18
19use super::DictData;
20use super::DictMetadata;
21use super::DictOwnedExt;
22use super::DictParts;
23use super::array::DictSlots;
24use super::array::DictSlotsView;
25use crate::AnyCanonical;
26use crate::ArrayEq;
27use crate::ArrayHash;
28use crate::ArrayRef;
29use crate::Canonical;
30use crate::CanonicalView;
31use crate::EqMode;
32use crate::IntoArray;
33use crate::array::Array;
34use crate::array::ArrayId;
35use crate::array::ArrayParts;
36use crate::array::ArrayView;
37use crate::array::VTable;
38use crate::array::with_empty_buffers;
39use crate::arrays::ConstantArray;
40use crate::arrays::Primitive;
41use crate::arrays::VarBinView;
42use crate::arrays::dict::DictArrayExt;
43use crate::arrays::dict::DictArraySlotsExt;
44use crate::arrays::dict::compute::rules::PARENT_RULES;
45use crate::arrays::dict::execute::take_canonical;
46use crate::buffer::BufferHandle;
47use crate::builders::ArrayBuilder;
48use crate::builders::VarBinBuilder;
49use crate::builders::VarBinViewBuilder;
50use crate::dtype::DType;
51use crate::dtype::Nullability;
52use crate::dtype::OffsetBuilderPType;
53use crate::dtype::PType;
54use crate::executor::ExecutionCtx;
55use crate::executor::ExecutionResult;
56use crate::match_each_integer_ptype;
57use crate::match_each_varbin_builder;
58use crate::require_child;
59use crate::scalar::Scalar;
60use crate::serde::ArrayChildren;
61
62mod kernel;
63mod operations;
64mod validity;
65
66pub type DictArray = Array<Dict>;
68
69pub(crate) fn initialize(session: &VortexSession) {
70 kernel::initialize(session);
71}
72
73#[derive(Clone, Debug)]
74pub struct Dict;
75
76impl ArrayHash for DictData {
77 fn array_hash<H: Hasher>(&self, _state: &mut H, _accuracy: EqMode) {}
78}
79
80impl ArrayEq for DictData {
81 fn array_eq(&self, _other: &Self, _accuracy: EqMode) -> bool {
82 true
83 }
84}
85
86impl VTable for Dict {
87 type TypedArrayData = DictData;
88
89 type OperationsVTable = Self;
90 type ValidityVTable = Self;
91
92 fn id(&self) -> ArrayId {
93 static ID: CachedId = CachedId::new("vortex.dict");
94 *ID
95 }
96
97 fn validate(
98 &self,
99 _data: &DictData,
100 dtype: &DType,
101 len: usize,
102 slots: &[Option<ArrayRef>],
103 ) -> VortexResult<()> {
104 let view = DictSlotsView::from_slots(slots);
105 let codes = view.codes;
106 let values = view.values;
107 vortex_ensure_eq!(codes.len(), len, "DictArray codes length mismatch");
108 vortex_ensure_eq!(
109 values
110 .dtype()
111 .union_nullability(codes.dtype().nullability()),
112 *dtype,
113 "DictArray dtype does not match codes/values dtype",
114 );
115 Ok(())
116 }
117
118 fn nbuffers(_array: ArrayView<'_, Self>) -> usize {
119 0
120 }
121
122 fn buffer(_array: ArrayView<'_, Self>, idx: usize) -> BufferHandle {
123 vortex_panic!("DictArray buffer index {idx} out of bounds")
124 }
125
126 fn buffer_name(_array: ArrayView<'_, Self>, _idx: usize) -> Option<String> {
127 None
128 }
129
130 fn with_buffers(
131 &self,
132 array: ArrayView<'_, Self>,
133 buffers: &[BufferHandle],
134 ) -> VortexResult<ArrayParts<Self>> {
135 with_empty_buffers(self, array, buffers)
136 }
137
138 fn serialize(
139 array: ArrayView<'_, Self>,
140 _session: &VortexSession,
141 ) -> VortexResult<Option<Vec<u8>>> {
142 Ok(Some(
143 DictMetadata {
144 codes_ptype: PType::try_from(array.codes().dtype())? as i32,
145 values_len: u32::try_from(array.values().len()).map_err(|_| {
146 vortex_err!(
147 "Dictionary values size {} overflowed u32",
148 array.values().len()
149 )
150 })?,
151 is_nullable_codes: Some(array.codes().dtype().is_nullable()),
152 all_values_referenced: Some(array.has_all_values_referenced()),
153 }
154 .encode_to_vec(),
155 ))
156 }
157
158 fn deserialize(
159 &self,
160 dtype: &DType,
161 len: usize,
162 metadata: &[u8],
163 _buffers: &[BufferHandle],
164 children: &dyn ArrayChildren,
165 _session: &VortexSession,
166 ) -> VortexResult<ArrayParts<Self>> {
167 let metadata = DictMetadata::decode(metadata)?;
168 if children.len() != 2 {
169 vortex_bail!(
170 "Expected 2 children for dict encoding, found {}",
171 children.len()
172 )
173 }
174 let codes_nullable = metadata
175 .is_nullable_codes
176 .map(Nullability::from)
177 .unwrap_or_else(|| dtype.nullability());
180 let codes_dtype = DType::Primitive(metadata.codes_ptype(), codes_nullable);
181 let codes = children.get(0, &codes_dtype, len)?;
182 let values = children.get(1, dtype, metadata.values_len as usize)?;
183 let all_values_referenced = metadata.all_values_referenced.unwrap_or(false);
184
185 Ok(ArrayParts::new(
186 self.clone(),
187 dtype.clone(),
188 len,
189 unsafe { DictData::new_unchecked().set_all_values_referenced(all_values_referenced) },
190 smallvec![Some(codes), Some(values)],
191 ))
192 }
193
194 fn slot_name(_array: ArrayView<'_, Self>, idx: usize) -> String {
195 DictSlots::NAMES[idx].to_string()
196 }
197
198 fn execute(array: Array<Self>, ctx: &mut ExecutionCtx) -> VortexResult<ExecutionResult> {
199 if array.is_empty() {
200 let result_dtype = array
201 .dtype()
202 .union_nullability(array.codes().dtype().nullability());
203 return Ok(ExecutionResult::done(Canonical::empty(&result_dtype)));
204 }
205
206 let array = require_child!(array, array.codes(), DictSlots::CODES => Primitive);
207
208 if array.codes().validity()?.definitely_all_null() {
209 return Ok(ExecutionResult::done(ConstantArray::new(
210 Scalar::null(array.dtype().as_nullable()),
211 array.codes().len(),
212 )));
213 }
214
215 let array = require_child!(array, array.values(), DictSlots::VALUES => AnyCanonical);
216
217 let DictParts { values, codes, .. } = array.into_parts();
218
219 Ok(ExecutionResult::done(take_canonical(
220 values.as_::<AnyCanonical>(),
221 codes.as_::<Primitive>(),
222 ctx,
223 )?))
224 }
225
226 fn append_to_builder(
227 array: ArrayView<'_, Self>,
228 builder: &mut dyn ArrayBuilder,
229 ctx: &mut ExecutionCtx,
230 ) -> VortexResult<()> {
231 if !array.is_empty()
232 && let (Some(codes), Some(values)) = (
233 array.codes().as_opt::<Primitive>(),
234 array.values().as_opt::<AnyCanonical>(),
235 )
236 && !codes.validity()?.definitely_all_null()
237 {
238 if let CanonicalView::VarBinView(values) = values
239 && let Some(result) = match_each_varbin_builder!(builder, |builder| {
240 let validity = array.validity()?.execute_mask(array.len(), ctx)?;
241 append_dict_to_varbin(codes, values, validity, builder)
242 })
243 {
244 return result;
245 }
246 if let CanonicalView::VarBinView(values) = values
247 && let Some(builder) = builder.as_any_mut().downcast_mut::<VarBinViewBuilder>()
248 {
249 let validity = array.validity()?.execute_mask(array.len(), ctx)?;
250 return append_dict_to_varbinview(codes, values, validity, builder);
251 }
252 let canonical = take_canonical(values, codes, ctx)?.into_array();
253 canonical.append_to_builder(builder, ctx)?;
254 return Ok(());
255 }
256
257 let canonical = array
258 .array()
259 .clone()
260 .execute::<Canonical>(ctx)?
261 .into_array();
262 canonical.append_to_builder(builder, ctx)?;
263 Ok(())
264 }
265
266 fn reduce_parent(
267 array: ArrayView<'_, Self>,
268 parent: &ArrayRef,
269 child_idx: usize,
270 ) -> VortexResult<Option<ArrayRef>> {
271 PARENT_RULES.evaluate(array, parent, child_idx)
272 }
273}
274
275fn append_dict_to_varbinview(
284 codes: ArrayView<'_, Primitive>,
285 values: ArrayView<'_, VarBinView>,
286 validity: Mask,
287 builder: &mut VarBinViewBuilder,
288) -> VortexResult<()> {
289 let views = values.views();
290 let buffers = values
291 .data_buffers()
292 .iter()
293 .map(|buffer| buffer.as_host().clone())
294 .collect::<Vec<_>>();
295
296 match_each_integer_ptype!(codes.ptype(), |C| {
297 let codes = codes.as_slice::<C>();
298 builder.append_views_gathered(buffers, views, &validity, |row| {
299 AsPrimitive::<usize>::as_(codes[row])
300 });
301 });
302 Ok(())
303}
304
305fn append_dict_to_varbin<O: OffsetBuilderPType>(
312 codes: ArrayView<'_, Primitive>,
313 values: ArrayView<'_, VarBinView>,
314 validity: Mask,
315 builder: &mut VarBinBuilder<O>,
316) -> VortexResult<()>
317where
318 usize: AsPrimitive<O>,
319{
320 let len = codes.as_ref().len();
321
322 let views = values.views();
324 let buffers = values
325 .data_buffers()
326 .iter()
327 .map(|buffer| buffer.as_host().as_slice())
328 .collect::<Vec<_>>();
329
330 match_each_integer_ptype!(codes.ptype(), |C| {
331 let codes = codes.as_slice::<C>();
332 let view = |row: usize| &views[AsPrimitive::<usize>::as_(codes[row])];
333
334 let num_bytes = match validity.bit_buffer() {
337 AllOr::All => (0..len).map(|row| view(row).len() as usize).sum(),
338 AllOr::None => {
339 builder.push_nulls(len);
340 return Ok(());
341 }
342 AllOr::Some(bits) => {
343 let mut total = 0;
344 bits.for_each_set_index(|row| total += view(row).len() as usize);
345 total
346 }
347 };
348
349 builder.append_valid_slices(num_bytes, &validity, |row| view(row).bytes(&buffers))
350 })
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356 use crate::VortexSessionExecute;
357 use crate::array_session;
358 use crate::arrays::PrimitiveArray;
359 use crate::arrays::VarBinViewArray;
360 use crate::arrays::dict::DictArray;
361 use crate::assert_arrays_eq;
362 use crate::dtype::Nullability::Nullable;
363
364 const LONG: &str = "a string that is far too long to be inlined in a view";
365
366 #[test]
367 fn append_to_builder_gathers_through_the_dictionary() -> VortexResult<()> {
368 let mut ctx = array_session().create_execution_ctx();
369 let dict = DictArray::try_new(
370 PrimitiveArray::from_option_iter([Some(0u32), Some(2), None, Some(1), Some(0)])
371 .into_array(),
372 VarBinViewArray::from_iter([Some(LONG), None, Some("short")], DType::Utf8(Nullable))
373 .into_array(),
374 )?;
375
376 let mut builder = VarBinBuilder::<i32>::new_in(DType::Utf8(Nullable), ctx.allocator());
377 dict.append_to_builder(&mut builder, &mut ctx)?;
378
379 let expected = VarBinViewArray::from_iter(
380 [Some(LONG), Some("short"), None, None, Some(LONG)],
381 DType::Utf8(Nullable),
382 );
383 assert_arrays_eq!(builder.finish_into_varbin(), expected, &mut ctx);
384 Ok(())
385 }
386
387 #[test]
391 fn append_to_view_builder_gathers_through_the_dictionary() -> VortexResult<()> {
392 let mut ctx = array_session().create_execution_ctx();
393 let dict = DictArray::try_new(
394 PrimitiveArray::from_option_iter([Some(0u32), Some(2), None, Some(1), Some(0)])
395 .into_array(),
396 VarBinViewArray::from_iter([Some(LONG), None, Some("short")], DType::Utf8(Nullable))
397 .into_array(),
398 )?;
399
400 let mut builder = VarBinViewBuilder::with_capacity_in(
401 DType::Utf8(Nullable),
402 8,
403 vortex_buffer::BufferAllocatorRef::statically_allocated(),
404 );
405 builder.append_value(LONG);
406 dict.append_to_builder(&mut builder, &mut ctx)?;
407
408 let expected = VarBinViewArray::from_iter(
409 [
410 Some(LONG),
411 Some(LONG),
412 Some("short"),
413 None,
414 None,
415 Some(LONG),
416 ],
417 DType::Utf8(Nullable),
418 );
419 assert_arrays_eq!(builder.finish_into_varbinview(), expected, &mut ctx);
420 Ok(())
421 }
422
423 #[test]
426 fn append_to_dedup_view_builder_adopts_the_dictionary_once() -> VortexResult<()> {
427 let mut ctx = array_session().create_execution_ctx();
428 let values = VarBinViewArray::from_iter([Some(LONG), Some("short")], DType::Utf8(Nullable));
429 assert_eq!(values.data_buffers().len(), 1);
430
431 let first = DictArray::try_new(
432 PrimitiveArray::from_option_iter([Some(0u32), Some(1)]).into_array(),
433 values.clone().into_array(),
434 )?;
435 let second = DictArray::try_new(
436 PrimitiveArray::from_option_iter([Some(1u32), Some(0)]).into_array(),
437 values.into_array(),
438 )?;
439
440 let mut builder = VarBinViewBuilder::with_buffer_deduplication_in(
441 DType::Utf8(Nullable),
442 8,
443 vortex_buffer::BufferAllocatorRef::statically_allocated(),
444 );
445 first.append_to_builder(&mut builder, &mut ctx)?;
446 second.append_to_builder(&mut builder, &mut ctx)?;
447 assert_eq!(builder.completed_block_count(), 1);
448
449 let expected = VarBinViewArray::from_iter(
450 [Some(LONG), Some("short"), Some("short"), Some(LONG)],
451 DType::Utf8(Nullable),
452 );
453 assert_arrays_eq!(builder.finish_into_varbinview(), expected, &mut ctx);
454 Ok(())
455 }
456}