1use std::{collections::HashMap, sync::Arc};
5
6pub const DICT_FIXED_WIDTH_BITS_PER_VALUE: u64 = 128;
8pub const DICT_INDICES_BITS_PER_VALUE: u64 = 32;
10
11use arrow_array::{
12 Array, DictionaryArray, PrimitiveArray, UInt64Array,
13 cast::AsArray,
14 types::{
15 ArrowDictionaryKeyType, Int8Type, Int16Type, Int32Type, Int64Type, UInt8Type, UInt16Type,
16 UInt32Type, UInt64Type,
17 },
18};
19use arrow_buffer::ArrowNativeType;
20use arrow_schema::DataType;
21use arrow_select::take::TakeOptions;
22use lance_core::{Error, Result, error::LanceOptionExt, utils::hash::U8SliceKey};
23
24use crate::{
25 buffer::LanceBuffer,
26 data::{BlockInfo, DataBlock, FixedWidthDataBlock, VariableWidthBlock},
27 statistics::{ComputeStat, GetStat, Stat},
28};
29
30fn normalize_dict_nulls_impl<K: ArrowDictionaryKeyType>(
32 array: Arc<dyn Array>,
33) -> Result<Arc<dyn Array>> {
34 let dict_array = array.as_dictionary_opt::<K>().expect_ok()?;
37
38 if dict_array.values().null_count() == 0 {
39 return Ok(array);
40 }
41
42 let mut mapping = vec![None; dict_array.values().len()];
43 let mut skipped = 0;
44 let mut valid_indices = Vec::with_capacity(dict_array.values().len());
45 for (old_idx, is_valid) in dict_array.values().nulls().expect_ok()?.iter().enumerate() {
46 if is_valid {
47 mapping[old_idx] = Some(K::Native::from_usize(old_idx - skipped).expect_ok()?);
49 valid_indices.push(old_idx as u64);
50 } else {
51 skipped += 1;
52 mapping[old_idx] = None;
53 }
54 }
55
56 let mut keys_builder = PrimitiveArray::<K>::builder(dict_array.keys().len());
57 for key in dict_array.keys().iter() {
58 if let Some(key) = key {
59 if let Some(mapped) = mapping[key.to_usize().expect_ok()?] {
60 keys_builder.append_value(mapped);
62 } else {
63 keys_builder.append_null();
65 }
66 } else {
67 keys_builder.append_null();
69 }
70 }
71 let keys = keys_builder.finish();
72
73 let valid_indices = UInt64Array::from(valid_indices);
74 let values = arrow_select::take::take(
75 dict_array.values(),
76 &valid_indices,
77 Some(TakeOptions {
78 check_bounds: false,
79 }),
80 )?;
81
82 Ok(Arc::new(DictionaryArray::new(keys, values)) as Arc<dyn Array>)
83}
84
85pub fn normalize_dict_nulls(array: Arc<dyn Array>) -> Result<Arc<dyn Array>> {
92 match array.data_type() {
93 DataType::Dictionary(key_type, _) => match key_type.as_ref() {
94 DataType::UInt8 => normalize_dict_nulls_impl::<UInt8Type>(array),
95 DataType::UInt16 => normalize_dict_nulls_impl::<UInt16Type>(array),
96 DataType::UInt32 => normalize_dict_nulls_impl::<UInt32Type>(array),
97 DataType::UInt64 => normalize_dict_nulls_impl::<UInt64Type>(array),
98 DataType::Int8 => normalize_dict_nulls_impl::<Int8Type>(array),
99 DataType::Int16 => normalize_dict_nulls_impl::<Int16Type>(array),
100 DataType::Int32 => normalize_dict_nulls_impl::<Int32Type>(array),
101 DataType::Int64 => normalize_dict_nulls_impl::<Int64Type>(array),
102 _ => Err(Error::not_supported_source(
103 format!("Unsupported dictionary key type: {}", key_type).into(),
104 )),
105 },
106 _ => Err(Error::internal(format!(
107 "Data type is not a dictionary: {}",
108 array.data_type()
109 ))),
110 }
111}
112
113fn clear_out_of_range_null_keys_impl<K: ArrowDictionaryKeyType>(
114 array: Arc<dyn Array>,
115) -> Result<Arc<dyn Array>> {
116 let dict_array = array.as_dictionary_opt::<K>().expect_ok()?;
117 let num_values = dict_array.values().len();
118 let Some(nulls) = dict_array.keys().nulls() else {
119 return Ok(array);
120 };
121
122 if num_values == 0 {
125 return Ok(array);
126 }
127
128 let has_out_of_range_null_key = dict_array
129 .keys()
130 .values()
131 .iter()
132 .zip(nulls.iter())
133 .any(|(key, is_valid)| !is_valid && key.to_usize().is_none_or(|key| key >= num_values));
134 if !has_out_of_range_null_key {
135 return Ok(array);
136 }
137
138 let keys = PrimitiveArray::<K>::from_iter(dict_array.keys().iter());
141 let values = dict_array.values().clone();
142 Ok(Arc::new(DictionaryArray::<K>::try_new(keys, values)?) as Arc<dyn Array>)
143}
144
145pub(super) fn clear_out_of_range_null_keys(array: Arc<dyn Array>) -> Result<Arc<dyn Array>> {
151 match array.data_type() {
152 DataType::Dictionary(key_type, _) => match key_type.as_ref() {
153 DataType::UInt8 => clear_out_of_range_null_keys_impl::<UInt8Type>(array),
154 DataType::UInt16 => clear_out_of_range_null_keys_impl::<UInt16Type>(array),
155 DataType::UInt32 => clear_out_of_range_null_keys_impl::<UInt32Type>(array),
156 DataType::UInt64 => clear_out_of_range_null_keys_impl::<UInt64Type>(array),
157 DataType::Int8 => clear_out_of_range_null_keys_impl::<Int8Type>(array),
158 DataType::Int16 => clear_out_of_range_null_keys_impl::<Int16Type>(array),
159 DataType::Int32 => clear_out_of_range_null_keys_impl::<Int32Type>(array),
160 DataType::Int64 => clear_out_of_range_null_keys_impl::<Int64Type>(array),
161 _ => Err(Error::not_supported_source(
162 format!("Unsupported dictionary key type: {}", key_type).into(),
163 )),
164 },
165 _ => Err(Error::internal(format!(
166 "Data type is not a dictionary: {}",
167 array.data_type()
168 ))),
169 }
170}
171
172fn dict_encode_variable_width<T>(
173 variable_width_data_block: &VariableWidthBlock,
174 bits_per_offset: u8,
175 max_dict_entries: u32,
176 max_encoded_size: usize,
177) -> Option<(DataBlock, DataBlock)>
178where
179 T: ArrowNativeType,
180 usize: TryFrom<T>,
181{
182 use std::collections::hash_map::Entry;
183 let mut map = HashMap::new();
184 let offsets = variable_width_data_block
185 .offsets
186 .borrow_to_typed_slice::<T>();
187 let offsets = offsets.as_ref();
188
189 let max_len = variable_width_data_block
190 .get_stat(Stat::MaxLength)
191 .expect("VariableWidth DataBlock should have valid `Stat::MaxLength` statistics");
192 let max_len = max_len.as_primitive::<UInt64Type>().value(0);
193
194 let max_dict_data_len = variable_width_data_block.data.len();
195 let max_len: usize = max_len.try_into().unwrap_or(usize::MAX);
196 let dict_data_capacity = max_len
197 .saturating_mul(32)
198 .max(1024)
199 .min(max_dict_data_len)
200 .min(max_encoded_size);
201
202 let mut dictionary_buffer: Vec<u8> = Vec::with_capacity(dict_data_capacity);
203 let mut dictionary_offsets_buffer = vec![T::default()];
204 let mut curr_idx = 0;
205 let mut indices_buffer = Vec::with_capacity(variable_width_data_block.num_values as usize);
206 let bytes_per_offset = (bits_per_offset / 8) as usize;
207
208 for window in offsets.windows(2) {
209 let start = usize::try_from(window[0]).ok()?;
210 let end = usize::try_from(window[1]).ok()?;
211 if start > end || end > variable_width_data_block.data.len() {
212 return None;
213 }
214
215 let key = &variable_width_data_block.data[start..end];
216
217 let idx = match map.entry(U8SliceKey(key)) {
218 Entry::Occupied(entry) => *entry.get(),
219 Entry::Vacant(entry) => {
220 if max_dict_entries == 0 || curr_idx as u32 >= max_dict_entries {
221 return None;
222 }
223 if curr_idx == i32::MAX {
224 return None;
225 }
226 dictionary_buffer.extend_from_slice(key);
227 let dict_offset = T::from_usize(dictionary_buffer.len())?;
228 dictionary_offsets_buffer.push(dict_offset);
229 let idx = curr_idx;
230 entry.insert(idx);
231 curr_idx += 1;
232 idx
233 }
234 };
235
236 indices_buffer.push(idx);
237
238 let indices_bytes = indices_buffer
239 .len()
240 .saturating_mul(DICT_INDICES_BITS_PER_VALUE as usize / 8);
241 let offsets_bytes = dictionary_offsets_buffer
242 .len()
243 .saturating_mul(bytes_per_offset);
244 let encoded_size = dictionary_buffer
245 .len()
246 .saturating_add(indices_bytes)
247 .saturating_add(offsets_bytes);
248 if encoded_size > max_encoded_size {
249 return None;
250 }
251 }
252
253 let mut dictionary_data_block = DataBlock::VariableWidth(VariableWidthBlock {
254 data: LanceBuffer::reinterpret_vec(dictionary_buffer),
255 offsets: LanceBuffer::reinterpret_vec(dictionary_offsets_buffer),
256 bits_per_offset,
257 num_values: curr_idx as u64,
258 block_info: BlockInfo::default(),
259 });
260 dictionary_data_block.compute_stat();
261
262 let mut indices_data_block = DataBlock::FixedWidth(FixedWidthDataBlock {
263 data: LanceBuffer::reinterpret_vec(indices_buffer),
264 bits_per_value: DICT_INDICES_BITS_PER_VALUE,
265 num_values: variable_width_data_block.num_values,
266 block_info: BlockInfo::default(),
267 });
268 indices_data_block.compute_stat();
269
270 Some((indices_data_block, dictionary_data_block))
271}
272
273pub fn dictionary_encode(
279 data_block: &DataBlock,
280 max_dict_entries: u32,
281 max_encoded_size: usize,
282) -> Option<(DataBlock, DataBlock)> {
283 match data_block {
284 DataBlock::FixedWidth(fixed_width_data_block) => {
285 use std::collections::hash_map::Entry;
286
287 let bytes_per_value = match fixed_width_data_block.bits_per_value {
288 64 => 8usize,
289 128 => 16usize,
290 _ => return None,
291 };
292
293 match fixed_width_data_block.bits_per_value {
294 64 => {
295 let mut map = HashMap::new();
296 let u64_slice = fixed_width_data_block.data.borrow_to_typed_slice::<u64>();
297 let u64_slice = u64_slice.as_ref();
298 let mut dictionary_buffer =
299 Vec::with_capacity((fixed_width_data_block.num_values as usize).min(1024));
300 let mut indices_buffer =
301 Vec::with_capacity(fixed_width_data_block.num_values as usize);
302 let mut curr_idx: i32 = 0;
303
304 for &value in u64_slice.iter() {
305 let idx = match map.entry(value) {
306 Entry::Occupied(entry) => *entry.get(),
307 Entry::Vacant(entry) => {
308 if max_dict_entries == 0 || curr_idx as u32 >= max_dict_entries {
309 return None;
310 }
311 if curr_idx == i32::MAX {
312 return None;
313 }
314 dictionary_buffer.push(value);
315 let idx = curr_idx;
316 entry.insert(idx);
317 curr_idx += 1;
318 idx
319 }
320 };
321 indices_buffer.push(idx);
322 let dict_bytes = dictionary_buffer.len().saturating_mul(bytes_per_value);
323 let indices_bytes = indices_buffer
324 .len()
325 .saturating_mul(DICT_INDICES_BITS_PER_VALUE as usize / 8);
326 let encoded_size = dict_bytes.saturating_add(indices_bytes);
327 if encoded_size > max_encoded_size {
328 return None;
329 }
330 }
331
332 let mut dictionary_data_block = DataBlock::FixedWidth(FixedWidthDataBlock {
333 data: LanceBuffer::reinterpret_vec(dictionary_buffer),
334 bits_per_value: 64,
335 num_values: curr_idx as u64,
336 block_info: BlockInfo::default(),
337 });
338 dictionary_data_block.compute_stat();
339 let mut indices_data_block = DataBlock::FixedWidth(FixedWidthDataBlock {
340 data: LanceBuffer::reinterpret_vec(indices_buffer),
341 bits_per_value: DICT_INDICES_BITS_PER_VALUE,
342 num_values: fixed_width_data_block.num_values,
343 block_info: BlockInfo::default(),
344 });
345 indices_data_block.compute_stat();
346
347 Some((indices_data_block, dictionary_data_block))
348 }
349 128 => {
350 let mut map = HashMap::new();
352 let u128_slice = fixed_width_data_block.data.borrow_to_typed_slice::<u128>();
353 let u128_slice = u128_slice.as_ref();
354 let mut dictionary_buffer =
355 Vec::with_capacity((fixed_width_data_block.num_values as usize).min(1024));
356 let mut indices_buffer =
357 Vec::with_capacity(fixed_width_data_block.num_values as usize);
358 let mut curr_idx: i32 = 0;
359
360 for &value in u128_slice.iter() {
361 let idx = match map.entry(value) {
362 Entry::Occupied(entry) => *entry.get(),
363 Entry::Vacant(entry) => {
364 if max_dict_entries == 0 || curr_idx as u32 >= max_dict_entries {
365 return None;
366 }
367 if curr_idx == i32::MAX {
368 return None;
369 }
370 dictionary_buffer.push(value);
371 let idx = curr_idx;
372 entry.insert(idx);
373 curr_idx += 1;
374 idx
375 }
376 };
377 indices_buffer.push(idx);
378 let dict_bytes = dictionary_buffer.len().saturating_mul(bytes_per_value);
379 let indices_bytes = indices_buffer
380 .len()
381 .saturating_mul(DICT_INDICES_BITS_PER_VALUE as usize / 8);
382 let encoded_size = dict_bytes.saturating_add(indices_bytes);
383 if encoded_size > max_encoded_size {
384 return None;
385 }
386 }
387
388 let mut dictionary_data_block = DataBlock::FixedWidth(FixedWidthDataBlock {
389 data: LanceBuffer::reinterpret_vec(dictionary_buffer),
390 bits_per_value: DICT_FIXED_WIDTH_BITS_PER_VALUE,
391 num_values: curr_idx as u64,
392 block_info: BlockInfo::default(),
393 });
394 dictionary_data_block.compute_stat();
395 let mut indices_data_block = DataBlock::FixedWidth(FixedWidthDataBlock {
396 data: LanceBuffer::reinterpret_vec(indices_buffer),
397 bits_per_value: DICT_INDICES_BITS_PER_VALUE,
398 num_values: fixed_width_data_block.num_values,
399 block_info: BlockInfo::default(),
400 });
401 indices_data_block.compute_stat();
402
403 Some((indices_data_block, dictionary_data_block))
404 }
405 _ => None,
406 }
407 }
408 DataBlock::VariableWidth(variable_width_data_block) => {
409 match variable_width_data_block.bits_per_offset {
410 32 => dict_encode_variable_width::<u32>(
411 variable_width_data_block,
412 32,
413 max_dict_entries,
414 max_encoded_size,
415 ),
416 64 => dict_encode_variable_width::<u64>(
417 variable_width_data_block,
418 64,
419 max_dict_entries,
420 max_encoded_size,
421 ),
422 _ => None,
423 }
424 }
425 _ => None,
426 }
427}
428
429#[cfg(test)]
430mod tests {
431 use super::*;
432 use crate::{
433 buffer::LanceBuffer,
434 data::{BlockInfo, FixedWidthDataBlock},
435 };
436 use arrow_array::{Array, StringArray};
437 use std::sync::Arc;
438
439 #[test]
440 fn test_dictionary_encode_abort_fixed_width() {
441 let num_values = 120u64;
444
445 let mut data = Vec::with_capacity(num_values as usize);
447 for i in 0..num_values {
448 data.push(i as u128);
449 }
450
451 let mut data_block = DataBlock::FixedWidth(FixedWidthDataBlock {
452 bits_per_value: DICT_FIXED_WIDTH_BITS_PER_VALUE,
453 data: LanceBuffer::reinterpret_vec(data),
454 num_values,
455 block_info: BlockInfo::default(),
456 });
457
458 data_block.compute_stat();
460
461 let max_encoded_size = usize::try_from(data_block.data_size()).unwrap_or(usize::MAX);
463 let result = dictionary_encode(&data_block, 1000, max_encoded_size);
464 assert!(
465 result.is_none(),
466 "Dictionary encoding should abort for high cardinality u128 data"
467 );
468 }
469
470 #[test]
471 fn test_dictionary_encode_success_fixed_width() {
472 let num_values = 120u64;
474 let cardinality = 3u64;
475
476 let mut data = Vec::with_capacity(num_values as usize);
478 for i in 0..num_values {
479 data.push((i % cardinality) as u128);
480 }
481
482 let mut data_block = DataBlock::FixedWidth(FixedWidthDataBlock {
483 bits_per_value: DICT_FIXED_WIDTH_BITS_PER_VALUE,
484 data: LanceBuffer::reinterpret_vec(data),
485 num_values,
486 block_info: BlockInfo::default(),
487 });
488
489 data_block.compute_stat();
491
492 let max_encoded_size = usize::try_from(data_block.data_size()).unwrap_or(usize::MAX);
494 let result = dictionary_encode(&data_block, 1000, max_encoded_size);
495 assert!(
496 result.is_some(),
497 "Dictionary encoding should succeed for low cardinality u128 data"
498 );
499
500 if let Some((indices, dictionary)) = result {
501 if let DataBlock::FixedWidth(indices_block) = indices {
503 assert_eq!(indices_block.num_values, num_values);
504 assert_eq!(indices_block.bits_per_value, DICT_INDICES_BITS_PER_VALUE);
505 } else {
506 panic!("Expected FixedWidth indices block");
507 }
508
509 if let DataBlock::FixedWidth(dict_block) = dictionary {
511 assert_eq!(dict_block.num_values, cardinality);
512 assert_eq!(dict_block.bits_per_value, DICT_FIXED_WIDTH_BITS_PER_VALUE);
513 } else {
514 panic!("Expected FixedWidth dictionary block");
515 }
516 }
517 }
518
519 #[test]
520 fn test_dictionary_encode_abort_variable_width() {
521 let num_values = 120u64;
524 let mut values = Vec::with_capacity(num_values as usize);
525 for i in 0..num_values {
526 values.push(format!("unique_value_{:04}", i));
527 }
528 let array = StringArray::from(values);
529 let data_block = DataBlock::from_array(Arc::new(array) as Arc<dyn Array>);
531
532 let max_encoded_size = usize::try_from(data_block.data_size()).unwrap_or(usize::MAX);
534 let result = dictionary_encode(&data_block, 10, max_encoded_size);
535 assert!(
536 result.is_none(),
537 "Dictionary encoding should abort for high cardinality string data"
538 );
539 }
540
541 #[test]
542 fn test_dictionary_encode_success_low_cardinality() {
543 let num_values = 120u64;
545 let cardinality = 3u64;
546
547 let mut values = Vec::with_capacity(num_values as usize);
548 for i in 0..num_values {
549 values.push(format!("value_{}", i % cardinality));
550 }
551
552 let array = StringArray::from(values);
553 let data_block = DataBlock::from_array(Arc::new(array) as Arc<dyn Array>);
554
555 let max_encoded_size = usize::try_from(data_block.data_size()).unwrap_or(usize::MAX);
557 let result = dictionary_encode(&data_block, 100, max_encoded_size);
558 assert!(
559 result.is_some(),
560 "Dictionary encoding should succeed for low cardinality data"
561 );
562
563 if let Some((indices, dictionary)) = result {
564 if let DataBlock::FixedWidth(indices_block) = indices {
566 assert_eq!(indices_block.num_values, num_values);
567 assert_eq!(indices_block.bits_per_value, DICT_INDICES_BITS_PER_VALUE);
568 } else {
569 panic!("Expected FixedWidth indices block");
570 }
571
572 if let DataBlock::VariableWidth(dict_block) = dictionary {
574 assert_eq!(dict_block.num_values, cardinality);
575 } else {
576 panic!("Expected VariableWidth dictionary block");
577 }
578 }
579 }
580
581 #[test]
582 fn test_dictionary_encode_invalid_offset_width_returns_none() {
583 let array = StringArray::from(vec!["a", "b", "c", "a"]);
584 let data_block = DataBlock::from_array(Arc::new(array) as Arc<dyn Array>);
585 let invalid_block = match data_block {
586 DataBlock::VariableWidth(mut var) => {
587 var.bits_per_offset = 16;
588 DataBlock::VariableWidth(var)
589 }
590 other => panic!("Expected VariableWidth data block, got {:?}", other),
591 };
592 let max_encoded_size = usize::try_from(invalid_block.data_size()).unwrap_or(usize::MAX);
593 assert!(dictionary_encode(&invalid_block, 100, max_encoded_size).is_none());
594 }
595
596 #[test]
597 fn test_dictionary_encode_respects_size_limit() {
598 let num_values = 10_000u64;
599 let cardinality = 50u64;
600
601 let mut values = Vec::with_capacity(num_values as usize);
602 for i in 0..num_values {
603 values.push(format!("value_{:08}", i % cardinality));
604 }
605
606 let array = StringArray::from(values);
607 let data_block = DataBlock::from_array(Arc::new(array) as Arc<dyn Array>);
608
609 let full_size = usize::try_from(data_block.data_size()).unwrap_or(usize::MAX);
610 let too_small_limit = full_size / 10;
611 assert!(dictionary_encode(&data_block, 1000, too_small_limit).is_none());
612 assert!(dictionary_encode(&data_block, 1000, full_size).is_some());
613 }
614
615 #[test]
616 fn test_dictionary_encode_respects_entry_limit() {
617 let num_values = 10_000u64;
618 let cardinality = 200u64;
619
620 let mut values = Vec::with_capacity(num_values as usize);
621 for i in 0..num_values {
622 values.push(format!("value_{:08}", i % cardinality));
623 }
624
625 let array = StringArray::from(values);
626 let data_block = DataBlock::from_array(Arc::new(array) as Arc<dyn Array>);
627
628 let max_encoded_size = usize::try_from(data_block.data_size()).unwrap_or(usize::MAX);
629 assert!(dictionary_encode(&data_block, 10, max_encoded_size).is_none());
630 assert!(dictionary_encode(&data_block, 500, max_encoded_size).is_some());
631 }
632}