Skip to main content

lance_encoding/array_encoding/
strategy.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright The Lance Authors
3
4use 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/// Field-to-column composition for the `pb::ArrayEncoding` grammar.
51#[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                // Direct field encoders have historically accepted arrays whose top-level
92                // nullability differs from the declared field. Keep that API behavior while
93                // still making the v2.0 struct-validity invariant unavoidable.
94                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    /// Create the field strategy for the `pb::ArrayEncoding` grammar.
124    ///
125    /// ```
126    /// use lance_encoding::encoder::ArrayFieldEncodingStrategy;
127    ///
128    /// let strategy = ArrayFieldEncodingStrategy::new();
129    /// ```
130    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/// Page-encoding selection for the `pb::ArrayEncoding` grammar.
628#[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                // User requested FSST
657                let raw_encoder = Box::new(BinaryEncoder::try_new(bin_indices_encoder, None)?);
658                Ok(Box::new(FsstArrayEncoder::new(raw_encoder)))
659            } else {
660                // Generic compression
661                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                        // We need to pass arrays to this method to figure out what kind of compression to
706                        // use but we haven't actually calculated the indices yet.  For now, we just assume
707                        // worst case and use the full range.  In the future maybe we can pass in statistics
708                        // instead of the actual data
709                        &[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            // TODO: for signed integers, I intend to make it a cascaded encoding, a sparse array for the negative values and very wide(bit-width) values,
754            // then a bitpacked array for the narrow(bit-width) values, I need `BitpackedForNeg` to be merged first, I am
755            // thinking about putting this sparse array in the metadata so bitpacking remain using one page buffer only.
756            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
773// check whether we want to use dictionary encoding or not
774// by applying a threshold on cardinality
775// returns true if cardinality < threshold but false if the total number of rows is less than the threshold
776// The choice to use 100 is just a heuristic for now
777// hyperloglog is used for cardinality estimation
778// error rate = 1.04 / sqrt(2^p), where p is the precision
779// and error rate is 1.04 / sqrt(2^12) = 1.56%
780fn 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    // make sure no array has an empty string
811    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    // find first non-zero value in lengths
861    let first_non_zero = lengths.iter().position(|&x| x != 0);
862    if let Some(first_non_zero) = first_non_zero {
863        // make sure all lengths are equal to first_non_zero length or zero
864        if !lengths
865            .iter()
866            .all(|&x| x == 0 || x == lengths[first_non_zero])
867        {
868            return None;
869        }
870
871        // set the byte width
872        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}