1use std::{collections::HashMap, env, hash::RandomState, sync::Arc};
5
6#[cfg(test)]
7use arrow_array::cast::AsArray;
8use arrow_array::{ArrayRef, UInt8Array};
9use arrow_schema::DataType;
10use hyperloglogplus::{HyperLogLog, HyperLogLogPlus};
11
12use crate::{
13 array_encoding::{
14 logical::{
15 blob::BlobFieldEncoder, list::ListFieldEncoder, primitive::PrimitiveFieldEncoder,
16 },
17 physical::{
18 basic::BasicEncoder,
19 binary::BinaryEncoder,
20 dictionary::{AlreadyDictionaryEncoder, DictionaryEncoder},
21 fixed_size_list::FslEncoder,
22 fsst::FsstArrayEncoder,
23 packed_struct::PackedStructEncoder,
24 },
25 },
26 constants::{
27 COMPRESSION_LEVEL_META_KEY, COMPRESSION_META_KEY, PACKED_STRUCT_LEGACY_META_KEY,
28 PACKED_STRUCT_META_KEY,
29 },
30 encoder::{
31 ArrayEncoder, ArrayEncodingStrategy, ColumnIndexSequence, FieldEncoder,
32 FieldEncodingContext, FieldEncodingStrategy,
33 },
34 encodings::{
35 logical::r#struct::StructFieldEncoder,
36 physical::{
37 block::{CompressionConfig, CompressionScheme},
38 value::ValueEncoder,
39 },
40 },
41};
42
43use lance_arrow::BLOB_META_KEY;
44use lance_core::datatypes::{BLOB_DESC_FIELD, Field};
45use lance_core::{Error, Result};
46
47#[derive(Debug)]
49pub struct ArrayFieldEncodingStrategy {
50 array_encoding_strategy: Arc<dyn ArrayEncodingStrategy>,
51}
52
53impl ArrayFieldEncodingStrategy {
54 pub fn new() -> Self {
62 Self {
63 array_encoding_strategy: Arc::new(ArrayStrategy),
64 }
65 }
66
67 fn is_primitive_type(data_type: &DataType) -> bool {
68 matches!(
69 data_type,
70 DataType::Boolean
71 | DataType::Date32
72 | DataType::Date64
73 | DataType::Decimal128(_, _)
74 | DataType::Decimal256(_, _)
75 | DataType::Duration(_)
76 | DataType::Float16
77 | DataType::Float32
78 | DataType::Float64
79 | DataType::Int16
80 | DataType::Int32
81 | DataType::Int64
82 | DataType::Int8
83 | DataType::Interval(_)
84 | DataType::Null
85 | DataType::Time32(_)
86 | DataType::Time64(_)
87 | DataType::Timestamp(_, _)
88 | DataType::UInt16
89 | DataType::UInt32
90 | DataType::UInt64
91 | DataType::UInt8
92 | DataType::FixedSizeBinary(_)
93 | DataType::FixedSizeList(_, _)
94 | DataType::Binary
95 | DataType::LargeBinary
96 | DataType::Utf8
97 | DataType::LargeUtf8,
98 )
99 }
100}
101
102impl Default for ArrayFieldEncodingStrategy {
103 fn default() -> Self {
104 Self::new()
105 }
106}
107
108impl FieldEncodingStrategy for ArrayFieldEncodingStrategy {
109 fn create_field_encoder(
110 &self,
111 field: &Field,
112 column_index: &mut ColumnIndexSequence,
113 context: &FieldEncodingContext<'_>,
114 ) -> Result<Box<dyn FieldEncoder>> {
115 let options = context.options;
116 let data_type = field.data_type();
117 if Self::is_primitive_type(&data_type) {
118 let column_index = column_index.next_column_index(field.id as u32);
119 if field.metadata.contains_key(BLOB_META_KEY) {
120 let mut packed_meta = HashMap::new();
121 packed_meta.insert(PACKED_STRUCT_META_KEY.to_string(), "true".to_string());
122 let desc_field =
123 Field::try_from(BLOB_DESC_FIELD.clone().with_metadata(packed_meta)).unwrap();
124 let desc_encoder = Box::new(PrimitiveFieldEncoder::try_new(
125 options,
126 self.array_encoding_strategy.clone(),
127 column_index,
128 desc_field,
129 )?);
130 Ok(Box::new(BlobFieldEncoder::new(desc_encoder)))
131 } else {
132 Ok(Box::new(PrimitiveFieldEncoder::try_new(
133 options,
134 self.array_encoding_strategy.clone(),
135 column_index,
136 field.clone(),
137 )?))
138 }
139 } else {
140 match data_type {
141 DataType::List(_child) | DataType::LargeList(_child) => {
142 let list_idx = column_index.next_column_index(field.id as u32);
143 let inner_encoding = context.strategy.create_field_encoder(
144 &field.children[0],
145 column_index,
146 context,
147 )?;
148 let offsets_encoder =
149 Arc::new(BasicEncoder::new(Box::new(ValueEncoder::default())));
150 Ok(Box::new(ListFieldEncoder::new(
151 inner_encoding,
152 offsets_encoder,
153 options.cache_bytes_per_column,
154 options.keep_original_array,
155 list_idx,
156 )))
157 }
158 DataType::Struct(_) => {
159 let field_metadata = &field.metadata;
160 if field_metadata
161 .get(PACKED_STRUCT_LEGACY_META_KEY)
162 .map(|v| v == "true")
163 .unwrap_or(field_metadata.contains_key(PACKED_STRUCT_META_KEY))
164 {
165 Ok(Box::new(PrimitiveFieldEncoder::try_new(
166 options,
167 self.array_encoding_strategy.clone(),
168 column_index.next_column_index(field.id as u32),
169 field.clone(),
170 )?))
171 } else {
172 let header_idx = column_index.next_column_index(field.id as u32);
173 let children_encoders = field
174 .children
175 .iter()
176 .map(|field| {
177 context
178 .strategy
179 .create_field_encoder(field, column_index, context)
180 })
181 .collect::<Result<Vec<_>>>()?;
182 Ok(Box::new(StructFieldEncoder::new(
183 children_encoders,
184 header_idx,
185 )))
186 }
187 }
188 DataType::Dictionary(_, value_type) => {
189 if Self::is_primitive_type(&value_type) {
191 Ok(Box::new(PrimitiveFieldEncoder::try_new(
192 options,
193 self.array_encoding_strategy.clone(),
194 column_index.next_column_index(field.id as u32),
195 field.clone(),
196 )?))
197 } else {
198 Err(Error::not_supported_source(format!("cannot encode a dictionary column whose value type is a logical type ({})", value_type).into()))
204 }
205 }
206 _ => todo!("Implement encoding for field {}", field),
207 }
208 }
209 }
210}
211
212#[derive(Debug)]
214struct ArrayStrategy;
215
216impl ArrayStrategy {
217 fn get_field_compression(field_meta: &HashMap<String, String>) -> Option<CompressionConfig> {
218 let compression = field_meta.get(COMPRESSION_META_KEY)?;
219 let compression_scheme = compression.parse::<CompressionScheme>();
220 match compression_scheme {
221 Ok(compression_scheme) => Some(CompressionConfig::new(
222 compression_scheme,
223 field_meta
224 .get(COMPRESSION_LEVEL_META_KEY)
225 .and_then(|level| level.parse().ok()),
226 )),
227 Err(_) => None,
228 }
229 }
230
231 fn default_binary_encoder(
232 arrays: &[ArrayRef],
233 field_meta: Option<&HashMap<String, String>>,
234 data_size: u64,
235 ) -> Result<Box<dyn ArrayEncoder>> {
236 let bin_indices_encoder =
237 Self::choose_array_encoder(arrays, &DataType::UInt64, data_size, false, None)?;
238
239 if let Some(compression) = field_meta.and_then(Self::get_field_compression) {
240 if compression.scheme() == CompressionScheme::Fsst {
241 let raw_encoder = Box::new(BinaryEncoder::try_new(bin_indices_encoder, None)?);
243 Ok(Box::new(FsstArrayEncoder::new(raw_encoder)))
244 } else {
245 Ok(Box::new(BinaryEncoder::try_new(
247 bin_indices_encoder,
248 Some(compression),
249 )?))
250 }
251 } else {
252 Ok(Box::new(BinaryEncoder::try_new(bin_indices_encoder, None)?))
253 }
254 }
255
256 fn choose_array_encoder(
257 arrays: &[ArrayRef],
258 data_type: &DataType,
259 data_size: u64,
260 use_dict_encoding: bool,
261 field_meta: Option<&HashMap<String, String>>,
262 ) -> Result<Box<dyn ArrayEncoder>> {
263 match data_type {
264 DataType::FixedSizeList(inner, dimension) => {
265 Ok(Box::new(BasicEncoder::new(Box::new(FslEncoder::new(
266 Self::choose_array_encoder(
267 arrays,
268 inner.data_type(),
269 data_size,
270 use_dict_encoding,
271 None,
272 )?,
273 *dimension as u32,
274 )))))
275 }
276 DataType::Dictionary(key_type, value_type) => {
277 let key_encoder =
278 Self::choose_array_encoder(arrays, key_type, data_size, false, None)?;
279 let value_encoder =
280 Self::choose_array_encoder(arrays, value_type, data_size, false, None)?;
281
282 Ok(Box::new(AlreadyDictionaryEncoder::new(
283 key_encoder,
284 value_encoder,
285 )))
286 }
287 DataType::Utf8 | DataType::LargeUtf8 | DataType::Binary | DataType::LargeBinary => {
288 if use_dict_encoding {
289 let dict_indices_encoder = Self::choose_array_encoder(
290 &[Arc::new(UInt8Array::from_iter_values(0_u8..255_u8))],
295 &DataType::UInt8,
296 data_size,
297 false,
298 None,
299 )?;
300 let dict_items_encoder = Self::choose_array_encoder(
301 arrays,
302 &DataType::Utf8,
303 data_size,
304 false,
305 None,
306 )?;
307
308 Ok(Box::new(DictionaryEncoder::new(
309 dict_indices_encoder,
310 dict_items_encoder,
311 )))
312 } else {
313 Self::default_binary_encoder(arrays, field_meta, data_size)
314 }
315 }
316 DataType::Struct(fields) => {
317 let num_fields = fields.len();
318 let mut inner_encoders = Vec::new();
319
320 for i in 0..num_fields {
321 let inner_datatype = fields[i].data_type();
322 let inner_encoder = Self::choose_array_encoder(
323 arrays,
324 inner_datatype,
325 data_size,
326 use_dict_encoding,
327 None,
328 )?;
329 inner_encoders.push(inner_encoder);
330 }
331
332 Ok(Box::new(PackedStructEncoder::new(inner_encoders)))
333 }
334 DataType::UInt8 | DataType::UInt16 | DataType::UInt32 | DataType::UInt64 => Ok(
335 Box::new(BasicEncoder::new(Box::new(ValueEncoder::default()))),
336 ),
337
338 DataType::Int8 | DataType::Int16 | DataType::Int32 | DataType::Int64 => Ok(Box::new(
342 BasicEncoder::new(Box::new(ValueEncoder::default())),
343 )),
344 _ => Ok(Box::new(BasicEncoder::new(Box::new(
345 ValueEncoder::default(),
346 )))),
347 }
348 }
349}
350
351fn get_dict_encoding_threshold() -> u64 {
352 env::var("LANCE_DICT_ENCODING_THRESHOLD")
353 .ok()
354 .and_then(|val| val.parse().ok())
355 .unwrap_or(100)
356}
357
358fn check_dict_encoding(arrays: &[ArrayRef], threshold: u64) -> bool {
366 let num_total_rows = arrays.iter().map(|arr| arr.len()).sum::<usize>();
367 if num_total_rows < threshold as usize {
368 return false;
369 }
370 const PRECISION: u8 = 12;
371
372 let mut hll: HyperLogLogPlus<String, RandomState> =
373 HyperLogLogPlus::new(PRECISION, RandomState::new()).unwrap();
374
375 for arr in arrays {
376 let string_array = arrow_array::cast::as_string_array(arr);
377 for value in string_array.iter().flatten() {
378 hll.insert(value);
379 let estimated_cardinality = hll.count() as u64;
380 if estimated_cardinality >= threshold {
381 return false;
382 }
383 }
384 }
385
386 true
387}
388
389#[cfg(test)]
390fn check_fixed_size_encoding(arrays: &[ArrayRef]) -> Option<u64> {
391 if arrays.is_empty() {
392 return None;
393 }
394
395 if !arrays.iter().all(|arr| {
397 if let Some(arr) = arr.as_string_opt::<i32>() {
398 arr.iter().flatten().all(|s| !s.is_empty())
399 } else if let Some(arr) = arr.as_binary_opt::<i32>() {
400 arr.iter().flatten().all(|s| !s.is_empty())
401 } else if let Some(arr) = arr.as_string_opt::<i64>() {
402 arr.iter().flatten().all(|s| !s.is_empty())
403 } else if let Some(arr) = arr.as_binary_opt::<i64>() {
404 arr.iter().flatten().all(|s| !s.is_empty())
405 } else {
406 panic!("wrong dtype");
407 }
408 }) {
409 return None;
410 }
411
412 let lengths = arrays
413 .iter()
414 .flat_map(|arr| {
415 if let Some(arr) = arr.as_string_opt::<i32>() {
416 let offsets = arr.offsets().inner();
417 offsets
418 .windows(2)
419 .map(|w| (w[1] - w[0]) as u64)
420 .collect::<Vec<_>>()
421 } else if let Some(arr) = arr.as_binary_opt::<i32>() {
422 let offsets = arr.offsets().inner();
423 offsets
424 .windows(2)
425 .map(|w| (w[1] - w[0]) as u64)
426 .collect::<Vec<_>>()
427 } else if let Some(arr) = arr.as_string_opt::<i64>() {
428 let offsets = arr.offsets().inner();
429 offsets
430 .windows(2)
431 .map(|w| (w[1] - w[0]) as u64)
432 .collect::<Vec<_>>()
433 } else if let Some(arr) = arr.as_binary_opt::<i64>() {
434 let offsets = arr.offsets().inner();
435 offsets
436 .windows(2)
437 .map(|w| (w[1] - w[0]) as u64)
438 .collect::<Vec<_>>()
439 } else {
440 panic!("wrong dtype");
441 }
442 })
443 .collect::<Vec<_>>();
444
445 let first_non_zero = lengths.iter().position(|&x| x != 0);
447 if let Some(first_non_zero) = first_non_zero {
448 if !lengths
450 .iter()
451 .all(|&x| x == 0 || x == lengths[first_non_zero])
452 {
453 return None;
454 }
455
456 Some(lengths[first_non_zero])
458 } else {
459 None
460 }
461}
462
463impl ArrayEncodingStrategy for ArrayStrategy {
464 fn create_array_encoder(
465 &self,
466 arrays: &[ArrayRef],
467 field: &Field,
468 ) -> Result<Box<dyn ArrayEncoder>> {
469 let data_size = arrays
470 .iter()
471 .map(|arr| arr.get_buffer_memory_size() as u64)
472 .sum::<u64>();
473 let data_type = arrays[0].data_type();
474
475 let use_dict_encoding = data_type == &DataType::Utf8
476 && check_dict_encoding(arrays, get_dict_encoding_threshold());
477
478 Self::choose_array_encoder(
479 arrays,
480 data_type,
481 data_size,
482 use_dict_encoding,
483 Some(&field.metadata),
484 )
485 }
486}
487
488#[cfg(test)]
489mod tests {
490 use super::{
491 ArrayEncodingStrategy, ArrayStrategy, check_dict_encoding, check_fixed_size_encoding,
492 };
493 use crate::constants::{COMPRESSION_LEVEL_META_KEY, COMPRESSION_META_KEY};
494 use arrow_array::{ArrayRef, StringArray};
495 use arrow_schema::Field;
496 use std::collections::HashMap;
497 use std::sync::Arc;
498
499 fn is_dict_encoding_applicable(arr: Vec<Option<&str>>, threshold: u64) -> bool {
500 let arr = StringArray::from(arr);
501 let arr = Arc::new(arr) as ArrayRef;
502 check_dict_encoding(&[arr], threshold)
503 }
504
505 #[test]
506 fn test_dict_encoding_should_be_applied_if_cardinality_less_than_threshold() {
507 assert!(is_dict_encoding_applicable(
508 vec![Some("a"), Some("b"), Some("a"), Some("b")],
509 3,
510 ));
511 }
512
513 #[test]
514 fn test_dict_encoding_should_not_be_applied_if_cardinality_larger_than_threshold() {
515 assert!(!is_dict_encoding_applicable(
516 vec![Some("a"), Some("b"), Some("c"), Some("d")],
517 3,
518 ));
519 }
520
521 #[test]
522 fn test_dict_encoding_should_not_be_applied_if_cardinality_equal_to_threshold() {
523 assert!(!is_dict_encoding_applicable(
524 vec![Some("a"), Some("b"), Some("c"), Some("a")],
525 3,
526 ));
527 }
528
529 #[test]
530 fn test_dict_encoding_should_not_be_applied_for_empty_arrays() {
531 assert!(!is_dict_encoding_applicable(vec![], 3));
532 }
533
534 #[test]
535 fn test_dict_encoding_should_not_be_applied_for_smaller_than_threshold_arrays() {
536 assert!(!is_dict_encoding_applicable(vec![Some("a"), Some("a")], 3));
537 }
538
539 fn is_fixed_size_encoding_applicable(arrays: Vec<Vec<Option<&str>>>) -> bool {
540 let mut final_arrays = Vec::new();
541 for arr in arrays {
542 let arr = StringArray::from(arr);
543 let arr = Arc::new(arr) as ArrayRef;
544 final_arrays.push(arr);
545 }
546
547 check_fixed_size_encoding(&final_arrays).is_some()
548 }
549
550 #[test]
551 fn test_fixed_size_binary_encoding_applicable() {
552 assert!(!is_fixed_size_encoding_applicable(vec![vec![]]));
553
554 assert!(is_fixed_size_encoding_applicable(vec![vec![
555 Some("a"),
556 Some("b")
557 ]]));
558
559 assert!(!is_fixed_size_encoding_applicable(vec![vec![
560 Some("abc"),
561 Some("de")
562 ]]));
563
564 assert!(is_fixed_size_encoding_applicable(vec![vec![
565 Some("pqr"),
566 None
567 ]]));
568
569 assert!(!is_fixed_size_encoding_applicable(vec![vec![
570 Some("pqr"),
571 Some("")
572 ]]));
573
574 assert!(!is_fixed_size_encoding_applicable(vec![vec![
575 Some(""),
576 Some("")
577 ]]));
578 }
579
580 #[test]
581 fn test_fixed_size_binary_encoding_applicable_multiple_arrays() {
582 assert!(is_fixed_size_encoding_applicable(vec![
583 vec![Some("a"), Some("b")],
584 vec![Some("c"), Some("d")]
585 ]));
586
587 assert!(!is_fixed_size_encoding_applicable(vec![
588 vec![Some("ab"), Some("bc")],
589 vec![Some("c"), Some("d")]
590 ]));
591
592 assert!(!is_fixed_size_encoding_applicable(vec![
593 vec![Some("ab"), None],
594 vec![None, Some("d")]
595 ]));
596
597 assert!(is_fixed_size_encoding_applicable(vec![
598 vec![Some("a"), None],
599 vec![None, Some("d")]
600 ]));
601
602 assert!(!is_fixed_size_encoding_applicable(vec![
603 vec![Some(""), None],
604 vec![None, Some("")]
605 ]));
606
607 assert!(!is_fixed_size_encoding_applicable(vec![
608 vec![None, None],
609 vec![None, None]
610 ]));
611 }
612
613 fn verify_array_encoder(
614 array: ArrayRef,
615 field_meta: Option<HashMap<String, String>>,
616 expected_encoder: &str,
617 ) {
618 let encoding_strategy = ArrayStrategy;
619 let mut field = Field::new("test_field", array.data_type().clone(), true);
620 if let Some(field_meta) = field_meta {
621 field.set_metadata(field_meta);
622 }
623 let lance_field = lance_core::datatypes::Field::try_from(field).unwrap();
624 let encoder_result = encoding_strategy.create_array_encoder(&[array], &lance_field);
625 assert!(encoder_result.is_ok());
626 let encoder = encoder_result.unwrap();
627 assert_eq!(format!("{:?}", encoder).as_str(), expected_encoder);
628 }
629
630 #[test]
631 fn test_choose_encoder_for_zstd_compressed_string_field() {
632 verify_array_encoder(
633 Arc::new(StringArray::from(vec!["a", "bb", "ccc"])),
634 Some(HashMap::from([(
635 COMPRESSION_META_KEY.to_string(),
636 "zstd".to_string(),
637 )])),
638 "BinaryEncoder { indices_encoder: BasicEncoder { values_encoder: ValueEncoder }, compression_config: Some(CompressionConfig { scheme: Zstd, level: None }), buffer_compressor: Some(ZstdBufferCompressor { compression_level: 0 }) }",
639 );
640 }
641
642 #[test]
643 fn test_choose_encoder_for_zstd_compression_level() {
644 verify_array_encoder(
645 Arc::new(StringArray::from(vec!["a", "bb", "ccc"])),
646 Some(HashMap::from([
647 (COMPRESSION_META_KEY.to_string(), "zstd".to_string()),
648 (COMPRESSION_LEVEL_META_KEY.to_string(), "22".to_string()),
649 ])),
650 "BinaryEncoder { indices_encoder: BasicEncoder { values_encoder: ValueEncoder }, compression_config: Some(CompressionConfig { scheme: Zstd, level: Some(22) }), buffer_compressor: Some(ZstdBufferCompressor { compression_level: 22 }) }",
651 );
652 }
653}