1use std::{collections::HashMap, env, hash::RandomState, sync::Arc};
5
6use arrow_array::{
7 Array, ArrayRef, GenericListArray, OffsetSizeTrait, UInt8Array, cast::AsArray, make_array,
8};
9use arrow_buffer::{BooleanBuffer, NullBuffer};
10use arrow_data::ArrayData;
11use arrow_schema::DataType;
12use hyperloglogplus::{HyperLogLog, HyperLogLogPlus};
13use lance_arrow::BLOB_META_KEY;
14
15use crate::{
16 array_encoding::{
17 logical::{
18 blob::BlobFieldEncoder, list::ListFieldEncoder, primitive::PrimitiveFieldEncoder,
19 },
20 physical::{
21 basic::BasicEncoder,
22 binary::BinaryEncoder,
23 dictionary::{AlreadyDictionaryEncoder, DictionaryEncoder},
24 fixed_size_list::FslEncoder,
25 fsst::FsstArrayEncoder,
26 packed_struct::PackedStructEncoder,
27 },
28 },
29 constants::{
30 COMPRESSION_LEVEL_META_KEY, COMPRESSION_META_KEY, PACKED_STRUCT_LEGACY_META_KEY,
31 PACKED_STRUCT_META_KEY,
32 },
33 encoder::{
34 ArrayEncoder, ArrayEncodingStrategy, ColumnIndexSequence, EncodeTask, EncodedColumn,
35 FieldEncoder, FieldEncodingContext, FieldEncodingStrategy, OutOfLineBuffers,
36 },
37 encodings::{
38 logical::r#struct::StructFieldEncoder,
39 physical::{
40 block::{CompressionConfig, CompressionScheme},
41 value::ValueEncoder,
42 },
43 },
44};
45
46use lance_core::datatypes::{BLOB_DESC_FIELD, Field};
47use lance_core::utils::parse::str_is_truthy;
48use lance_core::{Error, Result};
49
50#[derive(Debug)]
52pub struct ArrayFieldEncodingStrategy {
53 array_encoding_strategy: Arc<dyn ArrayEncodingStrategy>,
54}
55
56struct ValidatingFieldEncoder {
57 inner: Box<dyn FieldEncoder>,
58 field: Field,
59 prepared_array: Option<ArrayRef>,
60}
61
62impl ValidatingFieldEncoder {
63 fn new(inner: Box<dyn FieldEncoder>, field: Field) -> Self {
64 Self {
65 inner,
66 field,
67 prepared_array: None,
68 }
69 }
70}
71
72impl FieldEncoder for ValidatingFieldEncoder {
73 fn prepare_array(&mut self, array: ArrayRef) -> Result<ArrayRef> {
74 ArrayFieldEncodingStrategy::validate_v2_0_array(array.as_ref(), &self.field, true)?;
75 let array = ArrayFieldEncodingStrategy::clear_unreachable_v2_0_fsl_struct_validity(array)?;
76 self.prepared_array = Some(array.clone());
77 Ok(array)
78 }
79
80 fn maybe_encode(
81 &mut self,
82 array: ArrayRef,
83 external_buffers: &mut OutOfLineBuffers,
84 repdef: crate::repdef::RepDefBuilder,
85 row_number: u64,
86 num_rows: u64,
87 ) -> Result<Vec<EncodeTask>> {
88 let array = match self.prepared_array.take() {
89 Some(prepared) if Arc::ptr_eq(&prepared, &array) => prepared,
90 _ => {
91 ArrayFieldEncodingStrategy::validate_v2_0_array(
95 array.as_ref(),
96 &self.field,
97 false,
98 )?;
99 ArrayFieldEncodingStrategy::clear_unreachable_v2_0_fsl_struct_validity(array)?
100 }
101 };
102 self.inner
103 .maybe_encode(array, external_buffers, repdef, row_number, num_rows)
104 }
105
106 fn flush(&mut self, external_buffers: &mut OutOfLineBuffers) -> Result<Vec<EncodeTask>> {
107 self.inner.flush(external_buffers)
108 }
109
110 fn finish(
111 &mut self,
112 external_buffers: &mut OutOfLineBuffers,
113 ) -> futures::future::BoxFuture<'_, Result<Vec<EncodedColumn>>> {
114 self.inner.finish(external_buffers)
115 }
116
117 fn num_columns(&self) -> u32 {
118 self.inner.num_columns()
119 }
120}
121
122impl ArrayFieldEncodingStrategy {
123 pub fn new() -> Self {
131 Self {
132 array_encoding_strategy: Arc::new(ArrayStrategy),
133 }
134 }
135
136 fn is_primitive_type(data_type: &DataType) -> bool {
137 matches!(
138 data_type,
139 DataType::Boolean
140 | DataType::Date32
141 | DataType::Date64
142 | DataType::Decimal128(_, _)
143 | DataType::Decimal256(_, _)
144 | DataType::Duration(_)
145 | DataType::Float16
146 | DataType::Float32
147 | DataType::Float64
148 | DataType::Int16
149 | DataType::Int32
150 | DataType::Int64
151 | DataType::Int8
152 | DataType::Interval(_)
153 | DataType::Null
154 | DataType::Time32(_)
155 | DataType::Time64(_)
156 | DataType::Timestamp(_, _)
157 | DataType::UInt16
158 | DataType::UInt32
159 | DataType::UInt64
160 | DataType::UInt8
161 | DataType::FixedSizeBinary(_)
162 | DataType::FixedSizeList(_, _)
163 | DataType::Binary
164 | DataType::LargeBinary
165 | DataType::Utf8
166 | DataType::LargeUtf8,
167 )
168 }
169
170 fn validate_v2_0_array(
171 array: &dyn Array,
172 field: &Field,
173 enforce_field_nullability: bool,
174 ) -> Result<()> {
175 Self::validate_v2_0_array_reachability(array, field, enforce_field_nullability, None)
176 }
177
178 fn field_requires_v2_0_validation(field: &Field, enforce_field_nullability: bool) -> bool {
179 field.logical_type.is_struct()
180 || (enforce_field_nullability && !field.nullable)
181 || field
182 .children
183 .iter()
184 .any(|child| Self::field_requires_v2_0_validation(child, enforce_field_nullability))
185 }
186
187 fn is_reachable(reachable: Option<&BooleanBuffer>, index: usize) -> bool {
188 reachable.map(|mask| mask.value(index)).unwrap_or(true)
189 }
190
191 fn positional_child_reachability(
192 array: &dyn Array,
193 reachable: Option<&BooleanBuffer>,
194 ) -> Option<BooleanBuffer> {
195 if reachable.is_none() && array.null_count() == 0 {
196 return None;
197 }
198 Some(BooleanBuffer::from_iter((0..array.len()).map(|index| {
199 Self::is_reachable(reachable, index) && array.is_valid(index)
200 })))
201 }
202
203 fn list_child_reachability<O: OffsetSizeTrait>(
204 array: &GenericListArray<O>,
205 reachable: Option<&BooleanBuffer>,
206 ) -> Option<BooleanBuffer> {
207 let values_len = array.values().len();
208 let offsets = array.offsets();
209 if reachable.is_none()
210 && array.null_count() == 0
211 && offsets.first().map(|offset| offset.as_usize()) == Some(0)
212 && offsets.last().map(|offset| offset.as_usize()) == Some(values_len)
213 {
214 return None;
215 }
216
217 let mut child_reachable = vec![false; values_len];
218 for index in 0..array.len() {
219 if Self::is_reachable(reachable, index) && array.is_valid(index) {
220 let start = offsets[index].as_usize();
221 let end = offsets[index + 1].as_usize();
222 for is_reachable in child_reachable.iter_mut().take(end).skip(start) {
223 *is_reachable = true;
224 }
225 }
226 }
227 Some(BooleanBuffer::from_iter(child_reachable))
228 }
229
230 fn map_child_reachability(
231 array: &arrow_array::MapArray,
232 reachable: Option<&BooleanBuffer>,
233 ) -> Option<BooleanBuffer> {
234 let values_len = array.entries().len();
235 let offsets = array.offsets();
236 if reachable.is_none()
237 && array.null_count() == 0
238 && offsets.first().copied() == Some(0)
239 && offsets.last().copied() == Some(values_len as i32)
240 {
241 return None;
242 }
243
244 let mut child_reachable = vec![false; values_len];
245 for index in 0..array.len() {
246 if Self::is_reachable(reachable, index) && array.is_valid(index) {
247 let start = offsets[index] as usize;
248 let end = offsets[index + 1] as usize;
249 for is_reachable in child_reachable.iter_mut().take(end).skip(start) {
250 *is_reachable = true;
251 }
252 }
253 }
254 Some(BooleanBuffer::from_iter(child_reachable))
255 }
256
257 fn fixed_size_list_child_reachability(
258 array: &arrow_array::FixedSizeListArray,
259 reachable: Option<&BooleanBuffer>,
260 ) -> Option<BooleanBuffer> {
261 let values_len = array.values().len();
262 let dimension = array.value_length() as usize;
263 let (first_value, final_value) = if array.is_empty() {
264 (0, 0)
265 } else {
266 (
267 array.value_offset(0) as usize,
268 array.value_offset(array.len() - 1) as usize + dimension,
269 )
270 };
271 if reachable.is_none()
272 && array.null_count() == 0
273 && first_value == 0
274 && final_value == values_len
275 {
276 return None;
277 }
278
279 let mut child_reachable = vec![false; values_len];
280 for index in 0..array.len() {
281 if Self::is_reachable(reachable, index) && array.is_valid(index) {
282 let start = array.value_offset(index) as usize;
283 for is_reachable in child_reachable.iter_mut().skip(start).take(dimension) {
284 *is_reachable = true;
285 }
286 }
287 }
288 Some(BooleanBuffer::from_iter(child_reachable))
289 }
290
291 fn validate_v2_0_array_reachability(
292 array: &dyn Array,
293 field: &Field,
294 enforce_field_nullability: bool,
295 reachable: Option<&BooleanBuffer>,
296 ) -> Result<()> {
297 if !Self::field_requires_v2_0_validation(field, enforce_field_nullability) {
298 return Ok(());
299 }
300
301 let reachable_null_count = if array.null_count() == 0 {
302 0
303 } else if let Some(reachable) = reachable {
304 (0..array.len())
305 .filter(|index| reachable.value(*index) && array.is_null(*index))
306 .count()
307 } else {
308 array.null_count()
309 };
310
311 if enforce_field_nullability && !field.nullable && reachable_null_count > 0 {
312 return Err(Error::invalid_input(format!(
313 "The field `{}` contained null values even though the field is marked non-null in the schema",
314 field.name
315 )));
316 }
317 if field.logical_type.is_struct() && reachable_null_count > 0 {
318 return Err(Error::invalid_input(format!(
319 "The struct field `{}` contains {} null value(s), but Lance file version 2.0 does not encode struct validity; use file version 2.1 or later",
320 field.name, reachable_null_count
321 )));
322 }
323
324 match array.data_type() {
325 DataType::Struct(_) => {
326 let child_reachable = Self::positional_child_reachability(array, reachable);
327 for (child_field, child_array) in
328 field.children.iter().zip(array.as_struct().columns())
329 {
330 Self::validate_v2_0_array_reachability(
331 child_array.as_ref(),
332 child_field,
333 enforce_field_nullability,
334 child_reachable.as_ref(),
335 )?;
336 }
337 }
338 DataType::List(_) => {
339 let list_array = array.as_list::<i32>();
340 if let Some(child_field) = field.children.first() {
341 let child_reachable = Self::list_child_reachability(list_array, reachable);
342 Self::validate_v2_0_array_reachability(
343 list_array.values().as_ref(),
344 child_field,
345 enforce_field_nullability,
346 child_reachable.as_ref(),
347 )?;
348 }
349 }
350 DataType::LargeList(_) => {
351 let list_array = array.as_list::<i64>();
352 if let Some(child_field) = field.children.first() {
353 let child_reachable = Self::list_child_reachability(list_array, reachable);
354 Self::validate_v2_0_array_reachability(
355 list_array.values().as_ref(),
356 child_field,
357 enforce_field_nullability,
358 child_reachable.as_ref(),
359 )?;
360 }
361 }
362 DataType::Map(_, _) => {
363 let map_array = array.as_map();
364 if let Some(child_field) = field.children.first() {
365 let child_reachable = Self::map_child_reachability(map_array, reachable);
366 Self::validate_v2_0_array_reachability(
367 map_array.entries(),
368 child_field,
369 enforce_field_nullability,
370 child_reachable.as_ref(),
371 )?;
372 }
373 }
374 DataType::FixedSizeList(_, _) => {
375 let list_array = array.as_fixed_size_list();
376 if let Some(child_field) = field.children.first() {
377 let child_reachable =
378 Self::fixed_size_list_child_reachability(list_array, reachable);
379 Self::validate_v2_0_array_reachability(
380 list_array.values().as_ref(),
381 child_field,
382 enforce_field_nullability,
383 child_reachable.as_ref(),
384 )?;
385 }
386 }
387 _ => {
388 let array_data = array.to_data();
389 for (child_field, child_data) in field.children.iter().zip(array_data.child_data())
390 {
391 let child_array = make_array(child_data.clone());
392 let child_reachable = (child_array.len() == array.len())
393 .then(|| Self::positional_child_reachability(array, reachable))
394 .flatten();
395 Self::validate_v2_0_array_reachability(
396 child_array.as_ref(),
397 child_field,
398 enforce_field_nullability,
399 child_reachable.as_ref(),
400 )?;
401 }
402 }
403 }
404 Ok(())
405 }
406
407 fn clear_unreachable_v2_0_fsl_struct_validity(array: ArrayRef) -> Result<ArrayRef> {
408 fn force_valid_under_null_struct(
409 data: ArrayData,
410 struct_nulls: &NullBuffer,
411 ) -> Result<ArrayData> {
412 let Some(child_nulls) = data.nulls() else {
413 return Ok(data);
414 };
415 if !(0..data.len())
416 .any(|index| struct_nulls.is_null(index) && child_nulls.is_null(index))
417 {
418 return Ok(data);
419 }
420
421 let nulls =
422 NullBuffer::new(BooleanBuffer::from_iter((0..data.len()).map(|index| {
423 struct_nulls.is_null(index) || child_nulls.is_valid(index)
424 })));
425 let nulls = (nulls.null_count() > 0).then_some(nulls);
426 Ok(data.into_builder().nulls(nulls).build()?)
427 }
428
429 fn strip_struct_validity(data: ArrayData) -> Result<(ArrayData, bool)> {
430 let struct_fields = match data.data_type() {
431 DataType::Struct(fields) => Some(fields.clone()),
432 _ => None,
433 };
434 let struct_nulls = struct_fields
435 .as_ref()
436 .and_then(|_| data.nulls())
437 .filter(|nulls| nulls.null_count() > 0)
438 .cloned();
439 let mut children_changed = false;
440 let children = data
441 .child_data()
442 .iter()
443 .cloned()
444 .enumerate()
445 .map(|(index, child)| {
446 let (mut child, changed) = strip_struct_validity(child)?;
447 children_changed |= changed;
448 if let (Some(fields), Some(struct_nulls)) = (&struct_fields, &struct_nulls)
449 && !fields[index].is_nullable()
450 {
451 let original_null_count = child.null_count();
452 child = force_valid_under_null_struct(child, struct_nulls)?;
453 children_changed |= child.null_count() != original_null_count;
454 }
455 Ok(child)
456 })
457 .collect::<Result<Vec<_>>>()?;
458 if struct_nulls.is_none() && !children_changed {
459 return Ok((data, false));
460 }
461
462 let mut builder = data.into_builder().child_data(children);
463 if struct_nulls.is_some() {
464 builder = builder.nulls(None);
465 }
466 Ok((builder.build()?, true))
467 }
468
469 fn normalize(data: ArrayData) -> Result<(ArrayData, bool)> {
470 let is_fixed_size_list = matches!(data.data_type(), DataType::FixedSizeList(_, _));
471 let mut children_changed = false;
472 let children = data
473 .child_data()
474 .iter()
475 .cloned()
476 .map(|child| {
477 let (child, changed) = if is_fixed_size_list {
478 strip_struct_validity(child)?
479 } else {
480 normalize(child)?
481 };
482 children_changed |= changed;
483 Ok(child)
484 })
485 .collect::<Result<Vec<_>>>()?;
486 if !children_changed {
487 return Ok((data, false));
488 }
489 Ok((data.into_builder().child_data(children).build()?, true))
490 }
491
492 let (data, changed) = normalize(array.to_data())?;
493 if changed {
494 Ok(make_array(data))
495 } else {
496 Ok(array)
497 }
498 }
499
500 fn create_field_encoder_raw(
501 &self,
502 field: &Field,
503 column_index: &mut ColumnIndexSequence,
504 context: &FieldEncodingContext<'_>,
505 ) -> Result<Box<dyn FieldEncoder>> {
506 let options = context.options;
507 let data_type = field.data_type();
508 if Self::is_primitive_type(&data_type) {
509 let column_index = column_index.next_column_index(field.id as u32);
510 if field.metadata.contains_key(BLOB_META_KEY) {
511 let mut packed_meta = HashMap::new();
512 packed_meta.insert(PACKED_STRUCT_META_KEY.to_string(), "true".to_string());
513 let desc_field =
514 Field::try_from(BLOB_DESC_FIELD.clone().with_metadata(packed_meta)).unwrap();
515 let desc_encoder = Box::new(PrimitiveFieldEncoder::try_new(
516 options,
517 self.array_encoding_strategy.clone(),
518 column_index,
519 desc_field,
520 )?);
521 Ok(Box::new(BlobFieldEncoder::new(desc_encoder)))
522 } else {
523 Ok(Box::new(PrimitiveFieldEncoder::try_new(
524 options,
525 self.array_encoding_strategy.clone(),
526 column_index,
527 field.clone(),
528 )?))
529 }
530 } else {
531 match data_type {
532 DataType::List(_child) | DataType::LargeList(_child) => {
533 let list_idx = column_index.next_column_index(field.id as u32);
534 let inner_encoding =
535 self.create_field_encoder_raw(&field.children[0], column_index, context)?;
536 let offsets_encoder =
537 Arc::new(BasicEncoder::new(Box::new(ValueEncoder::default())));
538 Ok(Box::new(ListFieldEncoder::new(
539 inner_encoding,
540 offsets_encoder,
541 options.cache_bytes_per_column,
542 options.keep_original_array,
543 list_idx,
544 )))
545 }
546 DataType::Struct(_) => {
547 let field_metadata = &field.metadata;
548 if field_metadata
549 .get(PACKED_STRUCT_LEGACY_META_KEY)
550 .map(|v| str_is_truthy(v))
551 .unwrap_or(field_metadata.contains_key(PACKED_STRUCT_META_KEY))
552 {
553 Ok(Box::new(PrimitiveFieldEncoder::try_new(
554 options,
555 self.array_encoding_strategy.clone(),
556 column_index.next_column_index(field.id as u32),
557 field.clone(),
558 )?))
559 } else {
560 let header_idx = column_index.next_column_index(field.id as u32);
561 let children_encoders = field
562 .children
563 .iter()
564 .map(|field| {
565 self.create_field_encoder_raw(field, column_index, context)
566 })
567 .collect::<Result<Vec<_>>>()?;
568 Ok(Box::new(StructFieldEncoder::new(
569 children_encoders,
570 header_idx,
571 )))
572 }
573 }
574 DataType::Dictionary(_, value_type) => {
575 if Self::is_primitive_type(&value_type) {
576 Ok(Box::new(PrimitiveFieldEncoder::try_new(
577 options,
578 self.array_encoding_strategy.clone(),
579 column_index.next_column_index(field.id as u32),
580 field.clone(),
581 )?))
582 } else {
583 Err(Error::not_supported_source(format!(
584 "cannot encode a dictionary column whose value type is a logical type ({})",
585 value_type
586 ).into()))
587 }
588 }
589 _ => Err(Error::not_supported_source(
590 format!(
591 "Lance v2.0 has no field encoding for '{}' with data type {}",
592 field.name,
593 field.data_type()
594 )
595 .into(),
596 )),
597 }
598 }
599 }
600}
601
602impl Default for ArrayFieldEncodingStrategy {
603 fn default() -> Self {
604 Self::new()
605 }
606}
607
608impl FieldEncodingStrategy for ArrayFieldEncodingStrategy {
609 fn validate_array(&self, array: &dyn Array, field: &Field) -> Result<()> {
610 Self::validate_v2_0_array(array, field, true)
611 }
612
613 fn create_field_encoder(
614 &self,
615 field: &Field,
616 column_index: &mut ColumnIndexSequence,
617 context: &FieldEncodingContext<'_>,
618 ) -> Result<Box<dyn FieldEncoder>> {
619 let encoder = self.create_field_encoder_raw(field, column_index, context)?;
620 Ok(Box::new(ValidatingFieldEncoder::new(
621 encoder,
622 field.clone(),
623 )))
624 }
625}
626
627#[derive(Debug)]
629struct ArrayStrategy;
630
631impl ArrayStrategy {
632 fn get_field_compression(field_meta: &HashMap<String, String>) -> Option<CompressionConfig> {
633 let compression = field_meta.get(COMPRESSION_META_KEY)?;
634 let compression_scheme = compression.parse::<CompressionScheme>();
635 match compression_scheme {
636 Ok(compression_scheme) => Some(CompressionConfig::new(
637 compression_scheme,
638 field_meta
639 .get(COMPRESSION_LEVEL_META_KEY)
640 .and_then(|level| level.parse().ok()),
641 )),
642 Err(_) => None,
643 }
644 }
645
646 fn default_binary_encoder(
647 arrays: &[ArrayRef],
648 field_meta: Option<&HashMap<String, String>>,
649 data_size: u64,
650 ) -> Result<Box<dyn ArrayEncoder>> {
651 let bin_indices_encoder =
652 Self::choose_array_encoder(arrays, &DataType::UInt64, data_size, false, None)?;
653
654 if let Some(compression) = field_meta.and_then(Self::get_field_compression) {
655 if compression.scheme() == CompressionScheme::Fsst {
656 let raw_encoder = Box::new(BinaryEncoder::try_new(bin_indices_encoder, None)?);
658 Ok(Box::new(FsstArrayEncoder::new(raw_encoder)))
659 } else {
660 Ok(Box::new(BinaryEncoder::try_new(
662 bin_indices_encoder,
663 Some(compression),
664 )?))
665 }
666 } else {
667 Ok(Box::new(BinaryEncoder::try_new(bin_indices_encoder, None)?))
668 }
669 }
670
671 fn choose_array_encoder(
672 arrays: &[ArrayRef],
673 data_type: &DataType,
674 data_size: u64,
675 use_dict_encoding: bool,
676 field_meta: Option<&HashMap<String, String>>,
677 ) -> Result<Box<dyn ArrayEncoder>> {
678 match data_type {
679 DataType::FixedSizeList(inner, dimension) => {
680 Ok(Box::new(BasicEncoder::new(Box::new(FslEncoder::new(
681 Self::choose_array_encoder(
682 arrays,
683 inner.data_type(),
684 data_size,
685 use_dict_encoding,
686 None,
687 )?,
688 *dimension as u32,
689 )))))
690 }
691 DataType::Dictionary(key_type, value_type) => {
692 let key_encoder =
693 Self::choose_array_encoder(arrays, key_type, data_size, false, None)?;
694 let value_encoder =
695 Self::choose_array_encoder(arrays, value_type, data_size, false, None)?;
696
697 Ok(Box::new(AlreadyDictionaryEncoder::new(
698 key_encoder,
699 value_encoder,
700 )))
701 }
702 DataType::Utf8 | DataType::LargeUtf8 | DataType::Binary | DataType::LargeBinary => {
703 if use_dict_encoding {
704 let dict_indices_encoder = Self::choose_array_encoder(
705 &[Arc::new(UInt8Array::from_iter_values(0_u8..255_u8))],
710 &DataType::UInt8,
711 data_size,
712 false,
713 None,
714 )?;
715 let dict_items_encoder = Self::choose_array_encoder(
716 arrays,
717 &DataType::Utf8,
718 data_size,
719 false,
720 None,
721 )?;
722
723 Ok(Box::new(DictionaryEncoder::new(
724 dict_indices_encoder,
725 dict_items_encoder,
726 )))
727 } else {
728 Self::default_binary_encoder(arrays, field_meta, data_size)
729 }
730 }
731 DataType::Struct(fields) => {
732 let num_fields = fields.len();
733 let mut inner_encoders = Vec::new();
734
735 for i in 0..num_fields {
736 let inner_datatype = fields[i].data_type();
737 let inner_encoder = Self::choose_array_encoder(
738 arrays,
739 inner_datatype,
740 data_size,
741 use_dict_encoding,
742 None,
743 )?;
744 inner_encoders.push(inner_encoder);
745 }
746
747 Ok(Box::new(PackedStructEncoder::new(inner_encoders)))
748 }
749 DataType::UInt8 | DataType::UInt16 | DataType::UInt32 | DataType::UInt64 => Ok(
750 Box::new(BasicEncoder::new(Box::new(ValueEncoder::default()))),
751 ),
752
753 DataType::Int8 | DataType::Int16 | DataType::Int32 | DataType::Int64 => Ok(Box::new(
757 BasicEncoder::new(Box::new(ValueEncoder::default())),
758 )),
759 _ => Ok(Box::new(BasicEncoder::new(Box::new(
760 ValueEncoder::default(),
761 )))),
762 }
763 }
764}
765
766fn get_dict_encoding_threshold() -> u64 {
767 env::var("LANCE_DICT_ENCODING_THRESHOLD")
768 .ok()
769 .and_then(|val| val.parse().ok())
770 .unwrap_or(100)
771}
772
773fn check_dict_encoding(arrays: &[ArrayRef], threshold: u64) -> bool {
781 let num_total_rows = arrays.iter().map(|arr| arr.len()).sum::<usize>();
782 if num_total_rows < threshold as usize {
783 return false;
784 }
785 const PRECISION: u8 = 12;
786
787 let mut hll: HyperLogLogPlus<String, RandomState> =
788 HyperLogLogPlus::new(PRECISION, RandomState::new()).unwrap();
789
790 for arr in arrays {
791 let string_array = arrow_array::cast::as_string_array(arr);
792 for value in string_array.iter().flatten() {
793 hll.insert(value);
794 let estimated_cardinality = hll.count() as u64;
795 if estimated_cardinality >= threshold {
796 return false;
797 }
798 }
799 }
800
801 true
802}
803
804#[cfg(test)]
805fn check_fixed_size_encoding(arrays: &[ArrayRef]) -> Option<u64> {
806 if arrays.is_empty() {
807 return None;
808 }
809
810 if !arrays.iter().all(|arr| {
812 if let Some(arr) = arr.as_string_opt::<i32>() {
813 arr.iter().flatten().all(|s| !s.is_empty())
814 } else if let Some(arr) = arr.as_binary_opt::<i32>() {
815 arr.iter().flatten().all(|s| !s.is_empty())
816 } else if let Some(arr) = arr.as_string_opt::<i64>() {
817 arr.iter().flatten().all(|s| !s.is_empty())
818 } else if let Some(arr) = arr.as_binary_opt::<i64>() {
819 arr.iter().flatten().all(|s| !s.is_empty())
820 } else {
821 panic!("wrong dtype");
822 }
823 }) {
824 return None;
825 }
826
827 let lengths = arrays
828 .iter()
829 .flat_map(|arr| {
830 if let Some(arr) = arr.as_string_opt::<i32>() {
831 let offsets = arr.offsets().inner();
832 offsets
833 .windows(2)
834 .map(|w| (w[1] - w[0]) as u64)
835 .collect::<Vec<_>>()
836 } else if let Some(arr) = arr.as_binary_opt::<i32>() {
837 let offsets = arr.offsets().inner();
838 offsets
839 .windows(2)
840 .map(|w| (w[1] - w[0]) as u64)
841 .collect::<Vec<_>>()
842 } else if let Some(arr) = arr.as_string_opt::<i64>() {
843 let offsets = arr.offsets().inner();
844 offsets
845 .windows(2)
846 .map(|w| (w[1] - w[0]) as u64)
847 .collect::<Vec<_>>()
848 } else if let Some(arr) = arr.as_binary_opt::<i64>() {
849 let offsets = arr.offsets().inner();
850 offsets
851 .windows(2)
852 .map(|w| (w[1] - w[0]) as u64)
853 .collect::<Vec<_>>()
854 } else {
855 panic!("wrong dtype");
856 }
857 })
858 .collect::<Vec<_>>();
859
860 let first_non_zero = lengths.iter().position(|&x| x != 0);
862 if let Some(first_non_zero) = first_non_zero {
863 if !lengths
865 .iter()
866 .all(|&x| x == 0 || x == lengths[first_non_zero])
867 {
868 return None;
869 }
870
871 Some(lengths[first_non_zero])
873 } else {
874 None
875 }
876}
877
878impl ArrayEncodingStrategy for ArrayStrategy {
879 fn create_array_encoder(
880 &self,
881 arrays: &[ArrayRef],
882 field: &Field,
883 ) -> Result<Box<dyn ArrayEncoder>> {
884 let data_size = arrays
885 .iter()
886 .map(|arr| arr.get_buffer_memory_size() as u64)
887 .sum::<u64>();
888 let data_type = arrays[0].data_type();
889
890 let use_dict_encoding = data_type == &DataType::Utf8
891 && check_dict_encoding(arrays, get_dict_encoding_threshold());
892
893 Self::choose_array_encoder(
894 arrays,
895 data_type,
896 data_size,
897 use_dict_encoding,
898 Some(&field.metadata),
899 )
900 }
901}
902
903#[cfg(test)]
904mod tests {
905 use super::{
906 ArrayEncodingStrategy, ArrayFieldEncodingStrategy, ArrayStrategy, check_dict_encoding,
907 check_fixed_size_encoding,
908 };
909 use crate::constants::{COMPRESSION_LEVEL_META_KEY, COMPRESSION_META_KEY};
910 use crate::encoder::{BatchEncoder, EncodingOptions};
911 use arrow_array::{ArrayRef, StringArray};
912 use arrow_schema::{DataType, Field, Fields, Schema as ArrowSchema};
913 use lance_core::{Error, datatypes::Schema};
914 use std::collections::HashMap;
915 use std::sync::Arc;
916
917 #[test]
918 fn test_unsupported_field_type_returns_error() {
919 let entries = Field::new(
920 "entries",
921 DataType::Struct(Fields::from(vec![
922 Field::new("key", DataType::Utf8, false),
923 Field::new("value", DataType::Int32, true),
924 ])),
925 false,
926 );
927 let arrow_schema = ArrowSchema::new(vec![Field::new(
928 "attributes",
929 DataType::Map(Arc::new(entries), false),
930 true,
931 )]);
932 let schema = Schema::try_from(&arrow_schema).unwrap();
933
934 let error = BatchEncoder::try_new(
935 &schema,
936 &ArrayFieldEncodingStrategy::new(),
937 &EncodingOptions::default(),
938 )
939 .err()
940 .unwrap();
941
942 assert!(matches!(error, Error::NotSupported { .. }));
943 assert!(error.to_string().contains("attributes"));
944 assert!(error.to_string().contains("Map"));
945 }
946
947 fn is_dict_encoding_applicable(arr: Vec<Option<&str>>, threshold: u64) -> bool {
948 let arr = StringArray::from(arr);
949 let arr = Arc::new(arr) as ArrayRef;
950 check_dict_encoding(&[arr], threshold)
951 }
952
953 #[test]
954 fn test_dict_encoding_should_be_applied_if_cardinality_less_than_threshold() {
955 assert!(is_dict_encoding_applicable(
956 vec![Some("a"), Some("b"), Some("a"), Some("b")],
957 3,
958 ));
959 }
960
961 #[test]
962 fn test_dict_encoding_should_not_be_applied_if_cardinality_larger_than_threshold() {
963 assert!(!is_dict_encoding_applicable(
964 vec![Some("a"), Some("b"), Some("c"), Some("d")],
965 3,
966 ));
967 }
968
969 #[test]
970 fn test_dict_encoding_should_not_be_applied_if_cardinality_equal_to_threshold() {
971 assert!(!is_dict_encoding_applicable(
972 vec![Some("a"), Some("b"), Some("c"), Some("a")],
973 3,
974 ));
975 }
976
977 #[test]
978 fn test_dict_encoding_should_not_be_applied_for_empty_arrays() {
979 assert!(!is_dict_encoding_applicable(vec![], 3));
980 }
981
982 #[test]
983 fn test_dict_encoding_should_not_be_applied_for_smaller_than_threshold_arrays() {
984 assert!(!is_dict_encoding_applicable(vec![Some("a"), Some("a")], 3));
985 }
986
987 fn is_fixed_size_encoding_applicable(arrays: Vec<Vec<Option<&str>>>) -> bool {
988 let mut final_arrays = Vec::new();
989 for arr in arrays {
990 let arr = StringArray::from(arr);
991 let arr = Arc::new(arr) as ArrayRef;
992 final_arrays.push(arr);
993 }
994
995 check_fixed_size_encoding(&final_arrays).is_some()
996 }
997
998 #[test]
999 fn test_fixed_size_binary_encoding_applicable() {
1000 assert!(!is_fixed_size_encoding_applicable(vec![vec![]]));
1001
1002 assert!(is_fixed_size_encoding_applicable(vec![vec![
1003 Some("a"),
1004 Some("b")
1005 ]]));
1006
1007 assert!(!is_fixed_size_encoding_applicable(vec![vec![
1008 Some("abc"),
1009 Some("de")
1010 ]]));
1011
1012 assert!(is_fixed_size_encoding_applicable(vec![vec![
1013 Some("pqr"),
1014 None
1015 ]]));
1016
1017 assert!(!is_fixed_size_encoding_applicable(vec![vec![
1018 Some("pqr"),
1019 Some("")
1020 ]]));
1021
1022 assert!(!is_fixed_size_encoding_applicable(vec![vec![
1023 Some(""),
1024 Some("")
1025 ]]));
1026 }
1027
1028 #[test]
1029 fn test_fixed_size_binary_encoding_applicable_multiple_arrays() {
1030 assert!(is_fixed_size_encoding_applicable(vec![
1031 vec![Some("a"), Some("b")],
1032 vec![Some("c"), Some("d")]
1033 ]));
1034
1035 assert!(!is_fixed_size_encoding_applicable(vec![
1036 vec![Some("ab"), Some("bc")],
1037 vec![Some("c"), Some("d")]
1038 ]));
1039
1040 assert!(!is_fixed_size_encoding_applicable(vec![
1041 vec![Some("ab"), None],
1042 vec![None, Some("d")]
1043 ]));
1044
1045 assert!(is_fixed_size_encoding_applicable(vec![
1046 vec![Some("a"), None],
1047 vec![None, Some("d")]
1048 ]));
1049
1050 assert!(!is_fixed_size_encoding_applicable(vec![
1051 vec![Some(""), None],
1052 vec![None, Some("")]
1053 ]));
1054
1055 assert!(!is_fixed_size_encoding_applicable(vec![
1056 vec![None, None],
1057 vec![None, None]
1058 ]));
1059 }
1060
1061 fn verify_array_encoder(
1062 array: ArrayRef,
1063 field_meta: Option<HashMap<String, String>>,
1064 expected_encoder: &str,
1065 ) {
1066 let encoding_strategy = ArrayStrategy;
1067 let mut field = Field::new("test_field", array.data_type().clone(), true);
1068 if let Some(field_meta) = field_meta {
1069 field.set_metadata(field_meta);
1070 }
1071 let lance_field = lance_core::datatypes::Field::try_from(field).unwrap();
1072 let encoder_result = encoding_strategy.create_array_encoder(&[array], &lance_field);
1073 assert!(encoder_result.is_ok());
1074 let encoder = encoder_result.unwrap();
1075 assert_eq!(format!("{:?}", encoder).as_str(), expected_encoder);
1076 }
1077
1078 #[test]
1079 fn test_choose_encoder_for_zstd_compressed_string_field() {
1080 verify_array_encoder(
1081 Arc::new(StringArray::from(vec!["a", "bb", "ccc"])),
1082 Some(HashMap::from([(
1083 COMPRESSION_META_KEY.to_string(),
1084 "zstd".to_string(),
1085 )])),
1086 "BinaryEncoder { indices_encoder: BasicEncoder { values_encoder: ValueEncoder }, compression_config: Some(CompressionConfig { scheme: Zstd, level: None }), buffer_compressor: Some(ZstdBufferCompressor { compression_level: 0 }) }",
1087 );
1088 }
1089
1090 #[test]
1091 fn test_choose_encoder_for_zstd_compression_level() {
1092 verify_array_encoder(
1093 Arc::new(StringArray::from(vec!["a", "bb", "ccc"])),
1094 Some(HashMap::from([
1095 (COMPRESSION_META_KEY.to_string(), "zstd".to_string()),
1096 (COMPRESSION_LEVEL_META_KEY.to_string(), "22".to_string()),
1097 ])),
1098 "BinaryEncoder { indices_encoder: BasicEncoder { values_encoder: ValueEncoder }, compression_config: Some(CompressionConfig { scheme: Zstd, level: Some(22) }), buffer_compressor: Some(ZstdBufferCompressor { compression_level: 22 }) }",
1099 );
1100 }
1101}