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;
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!(codes.len() == len, "DictArray codes length mismatch");
108 vortex_ensure!(
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(self.clone(), dtype.clone(), len, unsafe {
186 DictData::new_unchecked().set_all_values_referenced(all_values_referenced)
187 })
188 .with_slots(smallvec![Some(codes), Some(values)]))
189 }
190
191 fn slot_name(_array: ArrayView<'_, Self>, idx: usize) -> String {
192 DictSlots::NAMES[idx].to_string()
193 }
194
195 fn execute(array: Array<Self>, ctx: &mut ExecutionCtx) -> VortexResult<ExecutionResult> {
196 if array.is_empty() {
197 let result_dtype = array
198 .dtype()
199 .union_nullability(array.codes().dtype().nullability());
200 return Ok(ExecutionResult::done(Canonical::empty(&result_dtype)));
201 }
202
203 let array = require_child!(array, array.codes(), DictSlots::CODES => Primitive);
204
205 if array.codes().validity()?.definitely_all_null() {
206 return Ok(ExecutionResult::done(ConstantArray::new(
207 Scalar::null(array.dtype().as_nullable()),
208 array.codes().len(),
209 )));
210 }
211
212 let array = require_child!(array, array.values(), DictSlots::VALUES => AnyCanonical);
213
214 let DictParts { values, codes, .. } = array.into_parts();
215
216 Ok(ExecutionResult::done(take_canonical(
217 values.as_::<AnyCanonical>(),
218 codes.as_::<Primitive>(),
219 ctx,
220 )?))
221 }
222
223 fn append_to_builder(
224 array: ArrayView<'_, Self>,
225 builder: &mut dyn ArrayBuilder,
226 ctx: &mut ExecutionCtx,
227 ) -> VortexResult<()> {
228 if !array.is_empty()
229 && let (Some(codes), Some(values)) = (
230 array.codes().as_opt::<Primitive>(),
231 array.values().as_opt::<AnyCanonical>(),
232 )
233 && !codes.validity()?.definitely_all_null()
234 {
235 if let CanonicalView::VarBinView(values) = values
236 && let Some(result) = match_each_varbin_builder!(builder, |builder| {
237 let validity = array.validity()?.execute_mask(array.len(), ctx)?;
238 append_dict_to_varbin(codes, values, validity, builder)
239 })
240 {
241 return result;
242 }
243 if let CanonicalView::VarBinView(values) = values
244 && let Some(builder) = builder.as_any_mut().downcast_mut::<VarBinViewBuilder>()
245 {
246 let validity = array.validity()?.execute_mask(array.len(), ctx)?;
247 return append_dict_to_varbinview(codes, values, validity, builder);
248 }
249 let canonical = take_canonical(values, codes, ctx)?.into_array();
250 canonical.append_to_builder(builder, ctx)?;
251 return Ok(());
252 }
253
254 let canonical = array
255 .array()
256 .clone()
257 .execute::<Canonical>(ctx)?
258 .into_array();
259 canonical.append_to_builder(builder, ctx)?;
260 Ok(())
261 }
262
263 fn reduce_parent(
264 array: ArrayView<'_, Self>,
265 parent: &ArrayRef,
266 child_idx: usize,
267 ) -> VortexResult<Option<ArrayRef>> {
268 PARENT_RULES.evaluate(array, parent, child_idx)
269 }
270}
271
272fn append_dict_to_varbinview(
281 codes: ArrayView<'_, Primitive>,
282 values: ArrayView<'_, VarBinView>,
283 validity: Mask,
284 builder: &mut VarBinViewBuilder,
285) -> VortexResult<()> {
286 let views = values.views();
287 let buffers = values
288 .data_buffers()
289 .iter()
290 .map(|buffer| buffer.as_host().clone())
291 .collect::<Vec<_>>();
292
293 match_each_integer_ptype!(codes.ptype(), |C| {
294 let codes = codes.as_slice::<C>();
295 builder.append_views_gathered(buffers, views, &validity, |row| {
296 AsPrimitive::<usize>::as_(codes[row])
297 });
298 });
299 Ok(())
300}
301
302fn append_dict_to_varbin<O: OffsetBuilderPType>(
309 codes: ArrayView<'_, Primitive>,
310 values: ArrayView<'_, VarBinView>,
311 validity: Mask,
312 builder: &mut VarBinBuilder<O>,
313) -> VortexResult<()>
314where
315 usize: AsPrimitive<O>,
316{
317 let len = codes.as_ref().len();
318
319 let views = values.views();
321 let buffers = values
322 .data_buffers()
323 .iter()
324 .map(|buffer| buffer.as_host().as_slice())
325 .collect::<Vec<_>>();
326
327 match_each_integer_ptype!(codes.ptype(), |C| {
328 let codes = codes.as_slice::<C>();
329 let view = |row: usize| &views[AsPrimitive::<usize>::as_(codes[row])];
330
331 let num_bytes = match validity.bit_buffer() {
334 AllOr::All => (0..len).map(|row| view(row).len() as usize).sum(),
335 AllOr::None => {
336 builder.push_nulls(len);
337 return Ok(());
338 }
339 AllOr::Some(bits) => {
340 let mut total = 0;
341 bits.for_each_set_index(|row| total += view(row).len() as usize);
342 total
343 }
344 };
345
346 builder.append_valid_slices(num_bytes, &validity, |row| view(row).bytes(&buffers))
347 })
348}
349
350#[cfg(test)]
351mod tests {
352 use super::*;
353 use crate::VortexSessionExecute;
354 use crate::array_session;
355 use crate::arrays::PrimitiveArray;
356 use crate::arrays::VarBinViewArray;
357 use crate::arrays::dict::DictArray;
358 use crate::assert_arrays_eq;
359 use crate::dtype::Nullability::Nullable;
360
361 const LONG: &str = "a string that is far too long to be inlined in a view";
362
363 #[test]
364 fn append_to_builder_gathers_through_the_dictionary() -> VortexResult<()> {
365 let mut ctx = array_session().create_execution_ctx();
366 let dict = DictArray::try_new(
367 PrimitiveArray::from_option_iter([Some(0u32), Some(2), None, Some(1), Some(0)])
368 .into_array(),
369 VarBinViewArray::from_iter([Some(LONG), None, Some("short")], DType::Utf8(Nullable))
370 .into_array(),
371 )?;
372
373 let mut builder = VarBinBuilder::<i32>::new(DType::Utf8(Nullable));
374 dict.append_to_builder(&mut builder, &mut ctx)?;
375
376 let expected = VarBinViewArray::from_iter(
377 [Some(LONG), Some("short"), None, None, Some(LONG)],
378 DType::Utf8(Nullable),
379 );
380 assert_arrays_eq!(builder.finish_into_varbin(), expected, &mut ctx);
381 Ok(())
382 }
383
384 #[test]
388 fn append_to_view_builder_gathers_through_the_dictionary() -> VortexResult<()> {
389 let mut ctx = array_session().create_execution_ctx();
390 let dict = DictArray::try_new(
391 PrimitiveArray::from_option_iter([Some(0u32), Some(2), None, Some(1), Some(0)])
392 .into_array(),
393 VarBinViewArray::from_iter([Some(LONG), None, Some("short")], DType::Utf8(Nullable))
394 .into_array(),
395 )?;
396
397 let mut builder = VarBinViewBuilder::with_capacity(DType::Utf8(Nullable), 8);
398 builder.append_value(LONG);
399 dict.append_to_builder(&mut builder, &mut ctx)?;
400
401 let expected = VarBinViewArray::from_iter(
402 [
403 Some(LONG),
404 Some(LONG),
405 Some("short"),
406 None,
407 None,
408 Some(LONG),
409 ],
410 DType::Utf8(Nullable),
411 );
412 assert_arrays_eq!(builder.finish_into_varbinview(), expected, &mut ctx);
413 Ok(())
414 }
415
416 #[test]
419 fn append_to_dedup_view_builder_adopts_the_dictionary_once() -> VortexResult<()> {
420 let mut ctx = array_session().create_execution_ctx();
421 let values = VarBinViewArray::from_iter([Some(LONG), Some("short")], DType::Utf8(Nullable));
422 assert_eq!(values.data_buffers().len(), 1);
423
424 let first = DictArray::try_new(
425 PrimitiveArray::from_option_iter([Some(0u32), Some(1)]).into_array(),
426 values.clone().into_array(),
427 )?;
428 let second = DictArray::try_new(
429 PrimitiveArray::from_option_iter([Some(1u32), Some(0)]).into_array(),
430 values.into_array(),
431 )?;
432
433 let mut builder = VarBinViewBuilder::with_buffer_deduplication(DType::Utf8(Nullable), 8);
434 first.append_to_builder(&mut builder, &mut ctx)?;
435 second.append_to_builder(&mut builder, &mut ctx)?;
436 assert_eq!(builder.completed_block_count(), 1);
437
438 let expected = VarBinViewArray::from_iter(
439 [Some(LONG), Some("short"), Some("short"), Some(LONG)],
440 DType::Utf8(Nullable),
441 );
442 assert_arrays_eq!(builder.finish_into_varbinview(), expected, &mut ctx);
443 Ok(())
444 }
445}