1#[cfg(feature = "bitpacking")]
20use crate::encodings::physical::bitpacking::{InlineBitpacking, OutOfLineBitpacking};
21use crate::{
22 buffer::LanceBuffer,
23 compression_config::{BssMode, CompressionFieldParams},
24 constants::{
25 BSS_META_KEY, COMPRESSION_LEVEL_META_KEY, COMPRESSION_META_KEY, RLE_THRESHOLD_META_KEY,
26 },
27 data::{DataBlock, FixedWidthDataBlock, VariableWidthBlock},
28 encodings::{
29 logical::primitive::{
30 fullzip::PerValueCompressor,
31 miniblock::{MAX_MINIBLOCK_VALUES, MiniBlockCompressor},
32 },
33 physical::{
34 binary::{
35 BinaryBlockDecompressor, BinaryMiniBlockDecompressor, BinaryMiniBlockEncoder,
36 VariableDecoder, VariableEncoder,
37 },
38 block::{
39 CompressedBufferEncoder, CompressionConfig, CompressionScheme,
40 GeneralBlockDecompressor,
41 },
42 byte_stream_split::{
43 ByteStreamSplitDecompressor, ByteStreamSplitEncoder, should_use_bss,
44 },
45 constant::ConstantDecompressor,
46 fsst::{
47 FsstMiniBlockDecompressor, FsstMiniBlockEncoder, FsstPerValueDecompressor,
48 FsstPerValueEncoder,
49 },
50 general::{GeneralMiniBlockCompressor, GeneralMiniBlockDecompressor},
51 packed::{
52 PackedStructFixedPerValueDecompressor, PackedStructFixedPerValueEncoder,
53 PackedStructFixedWidthMiniBlockDecompressor,
54 PackedStructFixedWidthMiniBlockEncoder, PackedStructVariablePerValueDecompressor,
55 PackedStructVariablePerValueEncoder, VariablePackedStructFieldDecoder,
56 VariablePackedStructFieldKind,
57 },
58 rle::{
59 RleChildDecompressor, RleDecompressor, RleEncoder, RunLengthWidth,
60 rle_encoded_size, select_run_length_width,
61 },
62 value::{ValueDecompressor, ValueEncoder},
63 },
64 },
65 format::{
66 ProtobufUtils21,
67 pb21::{CompressiveEncoding, compressive_encoding::Compression},
68 },
69 statistics::{GetStat, Stat},
70};
71
72use arrow_array::{cast::AsArray, types::UInt64Type};
73use arrow_schema::DataType;
74use fsst::fsst::{FSST_LEAST_INPUT_MAX_LENGTH, FSST_LEAST_INPUT_SIZE};
75use lance_core::{Error, Result, datatypes::Field, error::LanceOptionExt};
76use std::{str::FromStr, sync::Arc};
77
78const DEFAULT_RLE_COMPRESSION_THRESHOLD: f64 = 0.5;
84
85const MIN_BLOCK_SIZE_FOR_GENERAL_COMPRESSION: u64 = 32 * 1024;
87const RLE_BLOCK_HEADER_BYTES: u128 = std::mem::size_of::<u64>() as u128;
88
89pub trait BlockCompressor: std::fmt::Debug + Send + Sync {
102 fn compress(&self, data: DataBlock) -> Result<LanceBuffer>;
107}
108
109pub trait CompressionStrategy: Send + Sync + std::fmt::Debug {
122 fn create_block_compressor(
124 &self,
125 field: &Field,
126 data: &DataBlock,
127 ) -> Result<(Box<dyn BlockCompressor>, CompressiveEncoding)>;
128
129 fn create_per_value(
131 &self,
132 field: &Field,
133 data: &DataBlock,
134 ) -> Result<Box<dyn PerValueCompressor>>;
135
136 fn create_miniblock_compressor(
138 &self,
139 field: &Field,
140 data: &DataBlock,
141 ) -> Result<Box<dyn MiniBlockCompressor>>;
142}
143
144fn try_bss_for_mini_block(
145 data: &FixedWidthDataBlock,
146 params: &CompressionFieldParams,
147) -> Option<Box<dyn MiniBlockCompressor>> {
148 if params.compression.is_none() || params.compression.as_deref() == Some("none") {
151 return None;
152 }
153
154 let mode = params.bss.unwrap_or(BssMode::Auto);
155 if should_use_bss(data, mode) {
157 return Some(Box::new(ByteStreamSplitEncoder::new(
158 data.bits_per_value as usize,
159 )));
160 }
161 None
162}
163
164fn rle_is_applicable(data: &FixedWidthDataBlock, params: &CompressionFieldParams) -> Option<u128> {
165 let bits = data.bits_per_value;
166 if !matches!(bits, 8 | 16 | 32 | 64) {
167 return None;
168 }
169
170 let type_size = bits / 8;
171 let run_count = data.expect_single_stat::<UInt64Type>(Stat::RunCount);
172 let threshold = params
173 .rle_threshold
174 .unwrap_or(DEFAULT_RLE_COMPRESSION_THRESHOLD);
175
176 let passes_threshold = match params.rle_threshold {
179 Some(_) => (run_count as f64) < (data.num_values as f64) * threshold,
180 None => true,
181 };
182
183 if !passes_threshold {
184 return None;
185 }
186
187 Some((data.num_values as u128) * (type_size as u128))
188}
189
190fn rle_beats_raw_and_bitpacking(
191 data: &FixedWidthDataBlock,
192 encoded_bytes: u128,
193 raw_bytes: u128,
194) -> bool {
195 if encoded_bytes >= raw_bytes {
196 return false;
197 }
198
199 #[cfg(feature = "bitpacking")]
200 {
201 if let Some(bitpack_bytes) = estimate_inline_bitpacking_bytes(data).map(u128::from)
202 && bitpack_bytes < encoded_bytes
203 {
204 return false;
205 }
206 }
207 true
208}
209
210fn try_fixed_u8_rle_for_mini_block(
211 data: &FixedWidthDataBlock,
212 params: &CompressionFieldParams,
213) -> Option<Box<dyn MiniBlockCompressor>> {
214 let raw_bytes = rle_is_applicable(data, params)?;
215 let rle_bytes = estimate_rle_size_for_width_from_data(
216 data,
217 Some(*MAX_MINIBLOCK_VALUES),
218 RunLengthWidth::U8,
219 )
220 .ok()?;
221 rle_beats_raw_and_bitpacking(data, rle_bytes, raw_bytes)
222 .then(|| Box::new(RleEncoder::with_run_length_width(RunLengthWidth::U8)) as _)
223}
224
225fn try_child_rle_for_mini_block(
226 data: &FixedWidthDataBlock,
227 params: &CompressionFieldParams,
228) -> Option<Box<dyn MiniBlockCompressor>> {
229 let raw_bytes = rle_is_applicable(data, params)?;
230 let (run_length_width, estimated_bytes) =
231 estimate_rle_width_and_size_from_data(data, Some(*MAX_MINIBLOCK_VALUES)).ok()?;
232 let child_compression = rle_child_compression_config(params);
233 let encoder = || {
234 RleEncoder::with_child_encoding(
235 run_length_width,
236 child_compression,
237 child_compression,
238 true,
239 )
240 };
241
242 #[cfg(feature = "bitpacking")]
243 let bitpack_bytes = estimate_inline_bitpacking_bytes(data).map(u128::from);
244 #[cfg(not(feature = "bitpacking"))]
245 let bitpack_bytes = None::<u128>;
246
247 let should_measure_children = (child_compression.is_some() || cfg!(feature = "bitpacking"))
248 && (estimated_bytes >= raw_bytes
249 || bitpack_bytes.is_some_and(|bytes| bytes < estimated_bytes));
250 let selected_bytes = if should_measure_children {
251 encoder().selected_payload_size(data).ok()?
252 } else {
253 estimated_bytes
254 };
255
256 rle_beats_raw_and_bitpacking(data, selected_bytes, raw_bytes).then(|| Box::new(encoder()) as _)
257}
258
259fn rle_child_compression_config(params: &CompressionFieldParams) -> Option<CompressionConfig> {
260 let raw = params.compression.as_deref()?;
261 if matches!(raw, "none" | "fsst") {
262 return None;
263 }
264 let scheme = CompressionScheme::from_str(raw).ok()?;
265 Some(CompressionConfig::new(scheme, params.compression_level))
266}
267
268fn try_rle_for_block_with_width(
269 data: &FixedWidthDataBlock,
270 params: &CompressionFieldParams,
271 run_length_width: RunLengthWidth,
272 rle_payload_bytes: u128,
273) -> Result<Option<(Box<dyn BlockCompressor>, CompressiveEncoding)>> {
274 let bits = data.bits_per_value;
275 if !matches!(bits, 8 | 16 | 32 | 64) {
276 return Ok(None);
277 }
278
279 let run_count = data.expect_single_stat::<UInt64Type>(Stat::RunCount);
280 let threshold = params
281 .rle_threshold
282 .unwrap_or(DEFAULT_RLE_COMPRESSION_THRESHOLD);
283
284 let passes_threshold = match params.rle_threshold {
285 Some(_) => (run_count as f64) < (data.num_values as f64) * threshold,
286 None => true,
287 };
288
289 if !passes_threshold {
290 return Ok(None);
291 }
292
293 let raw_bytes = (data.num_values as u128) * ((bits / 8) as u128);
294 let rle_bytes = rle_payload_bytes.saturating_add(RLE_BLOCK_HEADER_BYTES);
295
296 if rle_bytes >= raw_bytes {
297 return Ok(None);
298 }
299
300 #[cfg(feature = "bitpacking")]
301 {
302 if let Some(bitpack_bytes) = estimate_block_bitpacking_bytes(data)
303 && bitpack_bytes < rle_bytes
304 {
305 return Ok(None);
306 }
307 }
308
309 let compressor = Box::new(RleEncoder::with_run_length_width(run_length_width));
310 let encoding = ProtobufUtils21::rle(
311 ProtobufUtils21::flat(bits, None),
312 ProtobufUtils21::flat(run_length_width.bits_per_value(), None),
313 );
314 Ok(Some((compressor, encoding)))
315}
316
317fn try_fixed_u8_rle_for_block(
318 data: &FixedWidthDataBlock,
319 params: &CompressionFieldParams,
320) -> Result<Option<(Box<dyn BlockCompressor>, CompressiveEncoding)>> {
321 if !matches!(data.bits_per_value, 8 | 16 | 32 | 64) {
322 return Ok(None);
323 }
324 let encoded_bytes = estimate_rle_size_for_width_from_data(data, None, RunLengthWidth::U8)?;
325 try_rle_for_block_with_width(data, params, RunLengthWidth::U8, encoded_bytes)
326}
327
328fn try_variable_rle_for_block(
329 data: &FixedWidthDataBlock,
330 params: &CompressionFieldParams,
331) -> Result<Option<(Box<dyn BlockCompressor>, CompressiveEncoding)>> {
332 if !matches!(data.bits_per_value, 8 | 16 | 32 | 64) {
333 return Ok(None);
334 }
335 let (width, encoded_bytes) = estimate_rle_width_and_size_from_data(data, None)?;
336 try_rle_for_block_with_width(data, params, width, encoded_bytes)
337}
338
339fn estimate_rle_width_and_size_from_data(
340 data: &FixedWidthDataBlock,
341 max_segment_values: Option<u64>,
342) -> Result<(RunLengthWidth, u128)> {
343 select_run_length_width(
344 &data.data,
345 data.num_values,
346 data.bits_per_value,
347 max_segment_values,
348 )
349}
350
351fn estimate_rle_size_for_width_from_data(
352 data: &FixedWidthDataBlock,
353 max_segment_values: Option<u64>,
354 run_length_width: RunLengthWidth,
355) -> Result<u128> {
356 rle_encoded_size(
357 &data.data,
358 data.num_values,
359 data.bits_per_value,
360 max_segment_values,
361 run_length_width,
362 )
363}
364
365fn try_bitpack_for_mini_block(_data: &FixedWidthDataBlock) -> Option<Box<dyn MiniBlockCompressor>> {
366 #[cfg(feature = "bitpacking")]
367 {
368 let bits = _data.bits_per_value;
369 if estimate_inline_bitpacking_bytes(_data).is_some() {
370 return Some(Box::new(InlineBitpacking::new(bits)));
371 }
372 None
373 }
374 #[cfg(not(feature = "bitpacking"))]
375 {
376 None
377 }
378}
379
380#[cfg(feature = "bitpacking")]
381fn estimate_inline_bitpacking_bytes(data: &FixedWidthDataBlock) -> Option<u64> {
382 use arrow_array::cast::AsArray;
383
384 let bits = data.bits_per_value;
385 if !matches!(bits, 8 | 16 | 32 | 64) {
386 return None;
387 }
388 if data.num_values == 0 {
389 return None;
390 }
391
392 let bit_widths = data.expect_stat(Stat::BitWidth);
393 let widths = bit_widths.as_primitive::<UInt64Type>();
394
395 let words_per_chunk: u128 = 1;
396 let word_bytes: u128 = (bits / 8) as u128;
397 let mut total_words: u128 = 0;
398 for i in 0..widths.len() {
399 let bit_width = widths.value(i) as u128;
400 let packed_words = (1024u128 * bit_width) / (bits as u128);
401 total_words = total_words.saturating_add(words_per_chunk.saturating_add(packed_words));
402 }
403
404 let estimated_bytes = total_words.saturating_mul(word_bytes);
405 let raw_bytes = data.data_size() as u128;
406
407 if estimated_bytes >= raw_bytes {
408 return None;
409 }
410
411 u64::try_from(estimated_bytes).ok()
412}
413
414fn try_bitpack_for_block(
415 data: &FixedWidthDataBlock,
416) -> Option<(Box<dyn BlockCompressor>, CompressiveEncoding)> {
417 let bits = data.bits_per_value;
418 if !matches!(bits, 8 | 16 | 32 | 64) {
419 return None;
420 }
421
422 let bit_widths = data.expect_stat(Stat::BitWidth);
423 let widths = bit_widths.as_primitive::<UInt64Type>();
424 let max_bit_width = *widths.values().iter().max().unwrap();
425
426 let too_small =
427 widths.len() == 1 && InlineBitpacking::min_size_bytes(widths.value(0)) >= data.data_size();
428
429 if too_small {
430 return None;
431 }
432
433 if data.num_values <= 1024 {
434 let compressor = Box::new(InlineBitpacking::new(bits));
435 let encoding = ProtobufUtils21::inline_bitpacking(bits, None);
436 Some((compressor, encoding))
437 } else {
438 let compressor = Box::new(OutOfLineBitpacking::new(max_bit_width, bits));
439 let encoding = ProtobufUtils21::out_of_line_bitpacking(
440 bits,
441 ProtobufUtils21::flat(max_bit_width, None),
442 );
443 Some((compressor, encoding))
444 }
445}
446
447#[cfg(feature = "bitpacking")]
448fn estimate_block_bitpacking_bytes(data: &FixedWidthDataBlock) -> Option<u128> {
449 let bits = data.bits_per_value;
450 if !matches!(bits, 8 | 16 | 32 | 64) || data.num_values == 0 {
451 return None;
452 }
453
454 let bit_widths = data.expect_stat(Stat::BitWidth);
455 let widths = bit_widths.as_primitive::<UInt64Type>();
456 let max_bit_width = *widths.values().iter().max()?;
457 let word_bytes = (bits / 8) as u128;
458
459 let bitpacked_words = if data.num_values <= 1024 {
460 1 + (1024u128 * (max_bit_width as u128)) / (bits as u128)
461 } else {
462 estimate_out_of_line_bitpacking_words(data.num_values, max_bit_width, bits)?
463 };
464 let bitpacked_bytes = bitpacked_words.saturating_mul(word_bytes);
465 if bitpacked_bytes >= data.data_size() as u128 {
466 return None;
467 }
468
469 Some(bitpacked_bytes)
470}
471
472#[cfg(feature = "bitpacking")]
473fn estimate_out_of_line_bitpacking_words(
474 num_values: u64,
475 compressed_bits_per_value: u64,
476 bits_per_value: u64,
477) -> Option<u128> {
478 let num_values = usize::try_from(num_values).ok()?;
479 let compressed_bits_per_value = usize::try_from(compressed_bits_per_value).ok()?;
480 let bits_per_value = usize::try_from(bits_per_value).ok()?;
481 if compressed_bits_per_value >= bits_per_value {
482 return None;
483 }
484
485 let elems_per_chunk = 1024usize;
486 let num_chunks = num_values.div_ceil(elems_per_chunk);
487 let words_per_chunk = (elems_per_chunk * compressed_bits_per_value).div_ceil(bits_per_value);
488 let last_chunk_is_runt = !num_values.is_multiple_of(elems_per_chunk);
489
490 if !last_chunk_is_runt {
491 return Some((num_chunks * words_per_chunk) as u128);
492 }
493
494 let num_whole_chunks = num_chunks - 1;
495 let remaining_items = num_values - num_whole_chunks * elems_per_chunk;
496 let tail_bit_savings = bits_per_value - compressed_bits_per_value;
497 let padding_cost = compressed_bits_per_value * (elems_per_chunk - remaining_items);
498 let tail_pack_savings = tail_bit_savings * remaining_items;
499 let tail_words = if padding_cost < tail_pack_savings {
500 words_per_chunk
501 } else {
502 remaining_items
503 };
504
505 Some((num_whole_chunks * words_per_chunk + tail_words) as u128)
506}
507
508fn maybe_wrap_general_for_mini_block(
509 inner: Box<dyn MiniBlockCompressor>,
510 params: &CompressionFieldParams,
511) -> Result<Box<dyn MiniBlockCompressor>> {
512 match params.compression.as_deref() {
513 None | Some("none") | Some("fsst") => Ok(inner),
514 Some(raw) => {
515 let scheme = CompressionScheme::from_str(raw)
516 .map_err(|_| Error::invalid_input(format!("Unknown compression scheme: {raw}")))?;
517 let cfg = CompressionConfig::new(scheme, params.compression_level);
518 Ok(Box::new(GeneralMiniBlockCompressor::new(inner, cfg)))
519 }
520 }
521}
522
523fn try_general_compression(
524 field_params: &CompressionFieldParams,
525 data: &DataBlock,
526) -> Result<Option<(Box<dyn BlockCompressor>, CompressionConfig)>> {
527 if field_params.compression.as_deref() == Some("none") {
529 return Ok(None);
530 }
531
532 if let Some(compression_scheme) = &field_params.compression {
535 let scheme: CompressionScheme = compression_scheme.parse()?;
536 let config = CompressionConfig::new(scheme, field_params.compression_level);
537 let compressor = Box::new(CompressedBufferEncoder::try_new(config)?);
538 return Ok(Some((compressor, config)));
539 }
540
541 if data.data_size() > MIN_BLOCK_SIZE_FOR_GENERAL_COMPRESSION {
543 let compressor = Box::new(CompressedBufferEncoder::default());
544 let config = compressor.compressor.config();
545 return Ok(Some((compressor, config)));
546 }
547
548 Ok(None)
549}
550
551pub fn field_metadata_params(field: &Field) -> CompressionFieldParams {
553 let mut params = CompressionFieldParams::default();
554
555 if let Some(compression) = field.metadata.get(COMPRESSION_META_KEY) {
556 params.compression = Some(compression.clone());
557 }
558 if let Some(level) = field.metadata.get(COMPRESSION_LEVEL_META_KEY) {
559 params.compression_level = level.parse().ok();
560 }
561 if let Some(threshold) = field.metadata.get(RLE_THRESHOLD_META_KEY) {
562 params.rle_threshold = threshold.parse().ok();
563 }
564 if let Some(bss_str) = field.metadata.get(BSS_META_KEY) {
565 match BssMode::parse(bss_str) {
566 Some(mode) => params.bss = Some(mode),
567 None => log::warn!("Invalid BSS mode '{}', using default", bss_str),
568 }
569 }
570 if let Some(minichunk_size_str) = field
571 .metadata
572 .get(super::constants::MINICHUNK_SIZE_META_KEY)
573 {
574 if let Ok(minichunk_size) = minichunk_size_str.parse::<i64>() {
575 params.minichunk_size = Some(minichunk_size);
576 } else {
577 log::warn!("Invalid minichunk_size '{}', skipping", minichunk_size_str);
578 }
579 }
580
581 params
582}
583
584pub fn finalize_miniblock_compressor(
586 data: &DataBlock,
587 compressor: Box<dyn MiniBlockCompressor>,
588 params: &CompressionFieldParams,
589) -> Result<Box<dyn MiniBlockCompressor>> {
590 if matches!(data, DataBlock::FixedWidth(_)) {
591 maybe_wrap_general_for_mini_block(compressor, params)
592 } else {
593 Ok(compressor)
594 }
595}
596
597pub fn try_uncompressed_fixed_width_miniblock(
599 data: &DataBlock,
600 params: &CompressionFieldParams,
601) -> Option<Box<dyn MiniBlockCompressor>> {
602 (matches!(data, DataBlock::FixedWidth(_)) && params.compression.as_deref() == Some("none"))
603 .then(|| Box::new(ValueEncoder::default()) as _)
604}
605
606pub fn try_byte_stream_split_miniblock(
608 data: &DataBlock,
609 params: &CompressionFieldParams,
610) -> Option<Box<dyn MiniBlockCompressor>> {
611 let DataBlock::FixedWidth(data) = data else {
612 return None;
613 };
614 try_bss_for_mini_block(data, params)
615}
616
617pub fn try_fixed_u8_rle_miniblock(
619 data: &DataBlock,
620 params: &CompressionFieldParams,
621) -> Option<Box<dyn MiniBlockCompressor>> {
622 let DataBlock::FixedWidth(data) = data else {
623 return None;
624 };
625 try_fixed_u8_rle_for_mini_block(data, params)
626}
627
628pub fn try_child_rle_miniblock(
630 data: &DataBlock,
631 params: &CompressionFieldParams,
632) -> Option<Box<dyn MiniBlockCompressor>> {
633 let DataBlock::FixedWidth(data) = data else {
634 return None;
635 };
636 try_child_rle_for_mini_block(data, params)
637}
638
639pub fn try_bitpacking_miniblock(data: &DataBlock) -> Option<Box<dyn MiniBlockCompressor>> {
641 let DataBlock::FixedWidth(data) = data else {
642 return None;
643 };
644 try_bitpack_for_mini_block(data)
645}
646
647pub fn try_raw_fixed_width_miniblock(data: &DataBlock) -> Option<Box<dyn MiniBlockCompressor>> {
649 matches!(data, DataBlock::FixedWidth(_)).then(|| Box::new(ValueEncoder::default()) as _)
650}
651
652pub fn try_variable_width_miniblock(
654 field: &Field,
655 data: &DataBlock,
656 params: &CompressionFieldParams,
657) -> Result<Option<Box<dyn MiniBlockCompressor>>> {
658 let DataBlock::VariableWidth(data) = data else {
659 return Ok(None);
660 };
661 if data.bits_per_offset != 32 && data.bits_per_offset != 64 {
662 return Err(Error::invalid_input(format!(
663 "Variable width compression not supported for {} bit offsets",
664 data.bits_per_offset
665 )));
666 }
667
668 let compression = params.compression.as_deref();
669 let data_size = data.expect_single_stat::<UInt64Type>(Stat::DataSize);
670 let max_len = data.expect_single_stat::<UInt64Type>(Stat::MaxLength);
671 if compression == Some("none") {
672 return Ok(Some(Box::new(BinaryMiniBlockEncoder::new(
673 params.minichunk_size,
674 ))));
675 }
676
677 let use_fsst = compression == Some("fsst")
678 || (compression.is_none()
679 && !matches!(field.data_type(), DataType::Binary | DataType::LargeBinary)
680 && max_len >= FSST_LEAST_INPUT_MAX_LENGTH
681 && data_size >= FSST_LEAST_INPUT_SIZE as u64);
682 let mut encoder: Box<dyn MiniBlockCompressor> = if use_fsst {
683 Box::new(FsstMiniBlockEncoder::new(params.minichunk_size))
684 } else {
685 Box::new(BinaryMiniBlockEncoder::new(params.minichunk_size))
686 };
687 if let Some(compression_scheme) = compression.filter(|scheme| *scheme != "fsst") {
688 let scheme: CompressionScheme = compression_scheme.parse()?;
689 let config = CompressionConfig::new(scheme, params.compression_level);
690 encoder = Box::new(GeneralMiniBlockCompressor::new(encoder, config));
691 }
692 Ok(Some(encoder))
693}
694
695pub fn try_fixed_packed_struct_miniblock(
697 data: &DataBlock,
698) -> Result<Option<Box<dyn MiniBlockCompressor>>> {
699 let DataBlock::Struct(data) = data else {
700 return Ok(None);
701 };
702 if data.has_variable_width_child() {
703 return Err(Error::invalid_input(
704 "Packed struct mini-block encoding supports only fixed-width children",
705 ));
706 }
707 Ok(Some(Box::new(
708 PackedStructFixedWidthMiniBlockEncoder::default(),
709 )))
710}
711
712pub fn try_raw_fixed_size_list_miniblock(data: &DataBlock) -> Option<Box<dyn MiniBlockCompressor>> {
714 matches!(data, DataBlock::FixedSizeList(_)).then(|| Box::new(ValueEncoder::default()) as _)
715}
716
717pub fn try_raw_per_value(data: &DataBlock) -> Option<Box<dyn PerValueCompressor>> {
719 matches!(data, DataBlock::FixedWidth(_) | DataBlock::FixedSizeList(_))
720 .then(|| Box::new(ValueEncoder::default()) as _)
721}
722
723fn validate_packed_struct(field: &Field, data: &DataBlock) -> Result<Option<bool>> {
724 let DataBlock::Struct(data) = data else {
725 return Ok(None);
726 };
727 if field.children.len() != data.children.len() {
728 return Err(Error::invalid_input(
729 "Struct field metadata does not match data block children",
730 ));
731 }
732 Ok(Some(data.has_variable_width_child()))
733}
734
735pub fn reject_packed_struct_per_value(
737 field: &Field,
738 data: &DataBlock,
739) -> Result<Option<Box<dyn PerValueCompressor>>> {
740 let Some(has_variable_child) = validate_packed_struct(field, data)? else {
741 return Ok(None);
742 };
743 if has_variable_child {
744 return Err(Error::not_supported_source(
745 "Variable packed struct encoding is not enabled by the selected file format".into(),
746 ));
747 }
748 Err(Error::invalid_input(
749 "Packed struct per-value compression should not be used for fixed-width-only structs",
750 ))
751}
752
753pub fn try_variable_packed_struct_per_value(
755 strategy: Arc<dyn CompressionStrategy>,
756 field: &Field,
757 data: &DataBlock,
758) -> Result<Option<Box<dyn PerValueCompressor>>> {
759 let Some(has_variable_child) = validate_packed_struct(field, data)? else {
760 return Ok(None);
761 };
762 if !has_variable_child {
763 return Err(Error::invalid_input(
764 "Packed struct per-value compression should not be used for fixed-width-only structs",
765 ));
766 }
767 Ok(Some(Box::new(PackedStructVariablePerValueEncoder::new(
768 strategy,
769 field.children.clone(),
770 ))))
771}
772
773pub fn try_packed_struct_per_value(
775 strategy: Arc<dyn CompressionStrategy>,
776 field: &Field,
777 data: &DataBlock,
778) -> Result<Option<Box<dyn PerValueCompressor>>> {
779 let Some(has_variable_child) = validate_packed_struct(field, data)? else {
780 return Ok(None);
781 };
782 if has_variable_child {
783 return Ok(Some(Box::new(PackedStructVariablePerValueEncoder::new(
784 strategy,
785 field.children.clone(),
786 ))));
787 }
788
789 Ok(Some(Box::new(PackedStructFixedPerValueEncoder::new(
790 field.children.clone(),
791 ))))
792}
793
794pub fn try_variable_width_per_value(
797 field: &Field,
798 data: &DataBlock,
799 params: &CompressionFieldParams,
800) -> Result<Option<Box<dyn PerValueCompressor>>> {
801 let DataBlock::VariableWidth(data) = data else {
802 return Ok(None);
803 };
804 let compression = params.compression.as_deref();
805 if compression == Some("none") {
806 return Ok(Some(Box::new(VariableEncoder::default())));
807 }
808
809 let max_len = data.expect_single_stat::<UInt64Type>(Stat::MaxLength);
810 let data_size = data.expect_single_stat::<UInt64Type>(Stat::DataSize);
811 let per_value_requested = compression.is_some_and(|compression| compression != "fsst");
812 if (max_len > 32 * 1024 || per_value_requested) && data_size >= FSST_LEAST_INPUT_SIZE as u64 {
813 if compression == Some("zstd") {
814 let config = CompressionConfig::new(CompressionScheme::Zstd, params.compression_level);
815 return Ok(Some(Box::new(CompressedBufferEncoder::try_new(config)?)));
816 }
817 return Ok(Some(Box::new(CompressedBufferEncoder::default())));
818 }
819
820 if data.bits_per_offset != 32 && data.bits_per_offset != 64 {
821 return Err(Error::invalid_input(format!(
822 "Per-value compression does not support variable-width data with {}-bit offsets",
823 data.bits_per_offset
824 )));
825 }
826 let encoder = Box::new(VariableEncoder::default());
827 let use_fsst = compression == Some("fsst")
828 || (compression.is_none()
829 && !matches!(field.data_type(), DataType::Binary | DataType::LargeBinary)
830 && max_len >= FSST_LEAST_INPUT_MAX_LENGTH
831 && data_size >= FSST_LEAST_INPUT_SIZE as u64);
832 Ok(Some(if use_fsst {
833 Box::new(FsstPerValueEncoder::new(encoder))
834 } else {
835 encoder
836 }))
837}
838
839pub fn try_fixed_u8_rle_block(
841 data: &DataBlock,
842 params: &CompressionFieldParams,
843) -> Result<Option<(Box<dyn BlockCompressor>, CompressiveEncoding)>> {
844 let DataBlock::FixedWidth(data) = data else {
845 return Ok(None);
846 };
847 try_fixed_u8_rle_for_block(data, params)
848}
849
850pub fn try_variable_rle_block(
852 data: &DataBlock,
853 params: &CompressionFieldParams,
854) -> Result<Option<(Box<dyn BlockCompressor>, CompressiveEncoding)>> {
855 let DataBlock::FixedWidth(data) = data else {
856 return Ok(None);
857 };
858 try_variable_rle_for_block(data, params)
859}
860
861pub fn try_bitpacking_block(
863 data: &DataBlock,
864) -> Option<(Box<dyn BlockCompressor>, CompressiveEncoding)> {
865 let DataBlock::FixedWidth(data) = data else {
866 return None;
867 };
868 try_bitpack_for_block(data)
869}
870
871pub fn try_general_block(
873 data: &DataBlock,
874 params: &CompressionFieldParams,
875) -> Result<Option<(Box<dyn BlockCompressor>, CompressiveEncoding)>> {
876 let Some((compressor, config)) = try_general_compression(params, data)? else {
877 return Ok(None);
878 };
879 let inner = match data {
880 DataBlock::FixedWidth(data) => ProtobufUtils21::flat(data.bits_per_value, None),
881 DataBlock::VariableWidth(data) => ProtobufUtils21::variable(
882 ProtobufUtils21::flat(data.bits_per_offset as u64, None),
883 None,
884 ),
885 _ => return Ok(None),
886 };
887 Ok(Some((compressor, ProtobufUtils21::wrapped(config, inner)?)))
888}
889
890pub fn try_raw_block(data: &DataBlock) -> Option<(Box<dyn BlockCompressor>, CompressiveEncoding)> {
892 match data {
893 DataBlock::FixedWidth(data) => Some((
894 Box::new(ValueEncoder::default()) as Box<dyn BlockCompressor>,
895 ProtobufUtils21::flat(data.bits_per_value, None),
896 )),
897 DataBlock::VariableWidth(data) => Some((
898 Box::new(VariableEncoder::default()) as Box<dyn BlockCompressor>,
899 ProtobufUtils21::variable(
900 ProtobufUtils21::flat(data.bits_per_offset as u64, None),
901 None,
902 ),
903 )),
904 _ => None,
905 }
906}
907
908pub trait MiniBlockDecompressor: std::fmt::Debug + Send + Sync {
909 fn decompress(&self, data: Vec<LanceBuffer>, num_values: u64) -> Result<DataBlock>;
910
911 fn decoded_size_bytes(&self, _num_values: u64) -> Option<u64> {
918 None
919 }
920}
921
922pub trait FixedPerValueDecompressor: std::fmt::Debug + Send + Sync {
923 fn decompress(&self, data: FixedWidthDataBlock, num_values: u64) -> Result<DataBlock>;
925 fn bits_per_value(&self) -> u64;
929
930 fn decoded_size_bytes(&self, _num_values: u64) -> Option<u64> {
937 None
938 }
939}
940
941pub trait VariablePerValueDecompressor: std::fmt::Debug + Send + Sync {
942 fn decompress(&self, data: VariableWidthBlock) -> Result<DataBlock>;
944}
945
946pub trait BlockDecompressor: std::fmt::Debug + Send + Sync {
947 fn decompress(&self, data: LanceBuffer, num_values: u64) -> Result<DataBlock>;
948}
949
950pub trait DecompressionStrategy: std::fmt::Debug + Send + Sync {
951 fn create_miniblock_decompressor(
952 &self,
953 description: &CompressiveEncoding,
954 decompression_strategy: &dyn DecompressionStrategy,
955 ) -> Result<Box<dyn MiniBlockDecompressor>>;
956
957 fn create_fixed_per_value_decompressor(
958 &self,
959 description: &CompressiveEncoding,
960 ) -> Result<Box<dyn FixedPerValueDecompressor>>;
961
962 fn create_variable_per_value_decompressor(
963 &self,
964 description: &CompressiveEncoding,
965 ) -> Result<Box<dyn VariablePerValueDecompressor>>;
966
967 fn create_block_decompressor(
968 &self,
969 description: &CompressiveEncoding,
970 ) -> Result<Box<dyn BlockDecompressor>>;
971}
972
973#[derive(Debug, Default)]
974pub struct DefaultDecompressionStrategy {}
975
976impl DecompressionStrategy for DefaultDecompressionStrategy {
977 fn create_miniblock_decompressor(
978 &self,
979 description: &CompressiveEncoding,
980 decompression_strategy: &dyn DecompressionStrategy,
981 ) -> Result<Box<dyn MiniBlockDecompressor>> {
982 match description.compression.as_ref().unwrap() {
983 Compression::Flat(flat) => Ok(Box::new(ValueDecompressor::from_flat(flat))),
984 #[cfg(feature = "bitpacking")]
985 Compression::InlineBitpacking(description) => {
986 Ok(Box::new(InlineBitpacking::from_description(description)))
987 }
988 #[cfg(not(feature = "bitpacking"))]
989 Compression::InlineBitpacking(_) => Err(Error::not_supported_source(
990 "this runtime was not built with bitpacking support".into(),
991 )),
992 Compression::Variable(variable) => {
993 let Compression::Flat(offsets) = variable
994 .offsets
995 .as_ref()
996 .unwrap()
997 .compression
998 .as_ref()
999 .unwrap()
1000 else {
1001 panic!("Variable compression only supports flat offsets")
1002 };
1003 Ok(Box::new(BinaryMiniBlockDecompressor::new(
1004 offsets.bits_per_value as u8,
1005 )))
1006 }
1007 Compression::Fsst(description) => {
1008 let inner_decompressor = decompression_strategy.create_miniblock_decompressor(
1009 description.values.as_ref().unwrap(),
1010 decompression_strategy,
1011 )?;
1012 Ok(Box::new(FsstMiniBlockDecompressor::new(
1013 description,
1014 inner_decompressor,
1015 )))
1016 }
1017 Compression::PackedStruct(description) => Ok(Box::new(
1018 PackedStructFixedWidthMiniBlockDecompressor::new(description),
1019 )),
1020 Compression::VariablePackedStruct(_) => Err(Error::not_supported_source(
1021 "variable packed struct decoding is not yet implemented".into(),
1022 )),
1023 Compression::FixedSizeList(fsl) => {
1024 Ok(Box::new(ValueDecompressor::from_fsl(fsl)))
1027 }
1028 Compression::Rle(rle) => Ok(Box::new(create_rle_decompressor(
1029 rle,
1030 decompression_strategy,
1031 )?)),
1032 Compression::ByteStreamSplit(bss) => {
1033 let Compression::Flat(values) =
1034 bss.values.as_ref().unwrap().compression.as_ref().unwrap()
1035 else {
1036 panic!("ByteStreamSplit compression only supports flat values")
1037 };
1038 Ok(Box::new(ByteStreamSplitDecompressor::new(
1039 values.bits_per_value as usize,
1040 )))
1041 }
1042 Compression::General(general) => {
1043 let inner_decompressor = self.create_miniblock_decompressor(
1045 general.values.as_ref().ok_or_else(|| {
1046 Error::invalid_input("GeneralMiniBlock missing inner encoding")
1047 })?,
1048 decompression_strategy,
1049 )?;
1050
1051 let compression = general.compression.as_ref().ok_or_else(|| {
1053 Error::invalid_input("GeneralMiniBlock missing compression config")
1054 })?;
1055
1056 let scheme = compression.scheme().try_into()?;
1057
1058 let compression_config = CompressionConfig::new(scheme, compression.level);
1059
1060 Ok(Box::new(GeneralMiniBlockDecompressor::new(
1061 inner_decompressor,
1062 compression_config,
1063 )))
1064 }
1065 _ => todo!(),
1066 }
1067 }
1068
1069 fn create_fixed_per_value_decompressor(
1070 &self,
1071 description: &CompressiveEncoding,
1072 ) -> Result<Box<dyn FixedPerValueDecompressor>> {
1073 match description.compression.as_ref().unwrap() {
1074 Compression::Constant(constant) => Ok(Box::new(ConstantDecompressor::new(
1075 constant
1076 .value
1077 .as_ref()
1078 .map(|v| LanceBuffer::from_bytes(v.clone(), 1)),
1079 ))),
1080 Compression::Flat(flat) => Ok(Box::new(ValueDecompressor::from_flat(flat))),
1081 Compression::FixedSizeList(fsl) => Ok(Box::new(ValueDecompressor::from_fsl(fsl))),
1082 Compression::PackedStruct(description) => Ok(Box::new(
1083 PackedStructFixedPerValueDecompressor::new(description)?,
1084 )),
1085 _ => todo!("fixed-per-value decompressor for {:?}", description),
1086 }
1087 }
1088
1089 fn create_variable_per_value_decompressor(
1090 &self,
1091 description: &CompressiveEncoding,
1092 ) -> Result<Box<dyn VariablePerValueDecompressor>> {
1093 match description.compression.as_ref().unwrap() {
1094 Compression::Variable(variable) => {
1095 let Compression::Flat(offsets) = variable
1096 .offsets
1097 .as_ref()
1098 .unwrap()
1099 .compression
1100 .as_ref()
1101 .unwrap()
1102 else {
1103 panic!("Variable compression only supports flat offsets")
1104 };
1105 assert!(offsets.bits_per_value < u8::MAX as u64);
1106 Ok(Box::new(VariableDecoder::default()))
1107 }
1108 Compression::Fsst(fsst) => Ok(Box::new(FsstPerValueDecompressor::new(
1109 LanceBuffer::from_bytes(fsst.symbol_table.clone(), 1),
1110 Box::new(VariableDecoder::default()),
1111 ))),
1112 Compression::General(general) => Ok(Box::new(CompressedBufferEncoder::from_scheme(
1113 general.compression.as_ref().expect_ok()?.scheme(),
1114 )?)),
1115 Compression::VariablePackedStruct(description) => {
1116 let mut fields = Vec::with_capacity(description.fields.len());
1117 for field in &description.fields {
1118 let value_encoding = field.value.as_ref().ok_or_else(|| {
1119 Error::invalid_input("VariablePackedStruct field is missing value encoding")
1120 })?;
1121 let decoder = match field.layout.as_ref().ok_or_else(|| {
1122 Error::invalid_input("VariablePackedStruct field is missing layout details")
1123 })? {
1124 crate::format::pb21::variable_packed_struct::field_encoding::Layout::BitsPerValue(
1125 bits_per_value,
1126 ) => {
1127 let decompressor =
1128 self.create_fixed_per_value_decompressor(value_encoding)?;
1129 VariablePackedStructFieldDecoder {
1130 kind: VariablePackedStructFieldKind::Fixed {
1131 bits_per_value: *bits_per_value,
1132 decompressor: Arc::from(decompressor),
1133 },
1134 }
1135 }
1136 crate::format::pb21::variable_packed_struct::field_encoding::Layout::BitsPerLength(
1137 bits_per_length,
1138 ) => {
1139 let decompressor =
1140 self.create_variable_per_value_decompressor(value_encoding)?;
1141 VariablePackedStructFieldDecoder {
1142 kind: VariablePackedStructFieldKind::Variable {
1143 bits_per_length: *bits_per_length,
1144 decompressor: Arc::from(decompressor),
1145 },
1146 }
1147 }
1148 };
1149 fields.push(decoder);
1150 }
1151 Ok(Box::new(PackedStructVariablePerValueDecompressor::new(
1152 fields,
1153 )))
1154 }
1155 _ => todo!("variable-per-value decompressor for {:?}", description),
1156 }
1157 }
1158
1159 fn create_block_decompressor(
1160 &self,
1161 description: &CompressiveEncoding,
1162 ) -> Result<Box<dyn BlockDecompressor>> {
1163 match description.compression.as_ref().unwrap() {
1164 Compression::InlineBitpacking(inline_bitpacking) => Ok(Box::new(
1165 InlineBitpacking::from_description(inline_bitpacking),
1166 )),
1167 Compression::Flat(flat) => Ok(Box::new(ValueDecompressor::from_flat(flat))),
1168 Compression::Constant(constant) => {
1169 let scalar = constant
1170 .value
1171 .as_ref()
1172 .map(|v| LanceBuffer::from_bytes(v.clone(), 1));
1173 Ok(Box::new(ConstantDecompressor::new(scalar)))
1174 }
1175 Compression::Variable(_) => Ok(Box::new(BinaryBlockDecompressor::default())),
1176 Compression::FixedSizeList(fsl) => {
1177 Ok(Box::new(ValueDecompressor::from_fsl(fsl.as_ref())))
1178 }
1179 Compression::OutOfLineBitpacking(out_of_line) => {
1180 let compressed_bit_width = match out_of_line
1182 .values
1183 .as_ref()
1184 .unwrap()
1185 .compression
1186 .as_ref()
1187 .unwrap()
1188 {
1189 Compression::Flat(flat) => flat.bits_per_value,
1190 _ => {
1191 return Err(Error::invalid_input_source(
1192 "OutOfLineBitpacking values must use Flat encoding".into(),
1193 ));
1194 }
1195 };
1196 Ok(Box::new(OutOfLineBitpacking::new(
1197 compressed_bit_width,
1198 out_of_line.uncompressed_bits_per_value,
1199 )))
1200 }
1201 Compression::General(general) => {
1202 let inner_desc = general
1203 .values
1204 .as_ref()
1205 .ok_or_else(|| {
1206 Error::invalid_input("General compression missing inner encoding")
1207 })?
1208 .as_ref();
1209 let inner_decompressor = self.create_block_decompressor(inner_desc)?;
1210
1211 let compression = general.compression.as_ref().ok_or_else(|| {
1212 Error::invalid_input("General compression missing compression config")
1213 })?;
1214 let scheme = compression.scheme().try_into()?;
1215 let config = CompressionConfig::new(scheme, compression.level);
1216 let general_decompressor =
1217 GeneralBlockDecompressor::try_new(inner_decompressor, config)?;
1218
1219 Ok(Box::new(general_decompressor))
1220 }
1221 Compression::Rle(rle) => Ok(Box::new(create_rle_decompressor(rle, self)?)),
1222 _ => todo!(),
1223 }
1224 }
1225}
1226pub(crate) fn create_rle_decompressor(
1227 rle: &crate::format::pb21::Rle,
1228 decompression_strategy: &dyn DecompressionStrategy,
1229) -> Result<RleDecompressor> {
1230 let values = rle
1231 .values
1232 .as_ref()
1233 .ok_or_else(|| Error::invalid_input("RLE compression missing values encoding"))?;
1234 let run_lengths = rle
1235 .run_lengths
1236 .as_ref()
1237 .ok_or_else(|| Error::invalid_input("RLE compression missing run lengths encoding"))?;
1238
1239 let values = create_rle_child_decompressor(values, "values", decompression_strategy)?;
1240 let run_lengths =
1241 create_rle_child_decompressor(run_lengths, "run lengths", decompression_strategy)?;
1242
1243 if !matches!(values.bits_per_value(), 8 | 16 | 32 | 64) {
1244 return Err(Error::invalid_input(format!(
1245 "RLE compression only supports 8, 16, 32, or 64-bit values, got {}",
1246 values.bits_per_value()
1247 )));
1248 }
1249
1250 let run_length_width =
1251 RunLengthWidth::from_bits(run_lengths.bits_per_value()).ok_or_else(|| {
1252 Error::invalid_input(format!(
1253 "RLE compression only supports 8, 16, or 32-bit run lengths, got {}",
1254 run_lengths.bits_per_value()
1255 ))
1256 })?;
1257
1258 if values.requires_num_values() && run_lengths.requires_num_values() {
1259 return Err(Error::invalid_input(
1260 "RLE values and run lengths child encodings cannot both require the run count",
1261 ));
1262 }
1263
1264 if values.is_identity() && run_lengths.is_identity() {
1265 return Ok(RleDecompressor::with_run_length_width(
1266 values.bits_per_value(),
1267 run_length_width,
1268 ));
1269 }
1270
1271 Ok(RleDecompressor::with_child_decompressors(
1272 values.bits_per_value(),
1273 run_length_width,
1274 values,
1275 run_lengths,
1276 ))
1277}
1278
1279fn create_rle_child_decompressor(
1280 encoding: &CompressiveEncoding,
1281 role: &str,
1282 decompression_strategy: &dyn DecompressionStrategy,
1283) -> Result<RleChildDecompressor> {
1284 let compression = encoding
1285 .compression
1286 .as_ref()
1287 .ok_or_else(|| Error::invalid_input(format!("RLE {role} missing child compression")))?;
1288 let (bits_per_value, requires_num_values, needs_decompressor) =
1289 validate_rle_child_compression(compression, role)?;
1290
1291 if needs_decompressor {
1292 Ok(RleChildDecompressor::block(
1293 bits_per_value,
1294 decompression_strategy.create_block_decompressor(encoding)?,
1295 requires_num_values,
1296 ))
1297 } else {
1298 Ok(RleChildDecompressor::flat(bits_per_value))
1299 }
1300}
1301
1302fn validate_rle_child_compression(
1303 compression: &Compression,
1304 role: &str,
1305) -> Result<(u64, bool, bool)> {
1306 match compression {
1307 Compression::Flat(flat) => Ok((flat.bits_per_value, false, false)),
1308 Compression::General(general) => {
1309 general.compression.as_ref().ok_or_else(|| {
1310 Error::invalid_input(format!(
1311 "RLE {role} general child missing compression config"
1312 ))
1313 })?;
1314 let values = general.values.as_ref().ok_or_else(|| {
1315 Error::invalid_input(format!("RLE {role} general child missing inner encoding"))
1316 })?;
1317 let inner = values.compression.as_ref().ok_or_else(|| {
1318 Error::invalid_input(format!(
1319 "RLE {role} general child missing inner compression"
1320 ))
1321 })?;
1322 let (bits_per_value, requires_num_values) =
1323 validate_rle_block_child_inner(inner, role)?;
1324 Ok((bits_per_value, requires_num_values, true))
1325 }
1326 Compression::OutOfLineBitpacking(out_of_line) => {
1327 let values = out_of_line.values.as_ref().ok_or_else(|| {
1328 Error::invalid_input(format!(
1329 "RLE {role} bitpacking child missing values encoding"
1330 ))
1331 })?;
1332 let Compression::Flat(_) = values.compression.as_ref().ok_or_else(|| {
1333 Error::invalid_input(format!(
1334 "RLE {role} bitpacking child missing values compression"
1335 ))
1336 })?
1337 else {
1338 return Err(Error::invalid_input(format!(
1339 "RLE {role} bitpacking child only supports flat values"
1340 )));
1341 };
1342 Ok((out_of_line.uncompressed_bits_per_value, true, true))
1343 }
1344 other => Err(Error::invalid_input(format!(
1345 "RLE {role} only supports flat, general, or out-of-line bitpacking child encodings, got {}",
1346 compression_name(other)
1347 ))),
1348 }
1349}
1350
1351fn validate_rle_block_child_inner(compression: &Compression, role: &str) -> Result<(u64, bool)> {
1352 match compression {
1353 Compression::Flat(flat) => Ok((flat.bits_per_value, false)),
1354 Compression::OutOfLineBitpacking(out_of_line) => {
1355 let values = out_of_line.values.as_ref().ok_or_else(|| {
1356 Error::invalid_input(format!(
1357 "RLE {role} bitpacking child missing values encoding"
1358 ))
1359 })?;
1360 let Compression::Flat(_) = values.compression.as_ref().ok_or_else(|| {
1361 Error::invalid_input(format!(
1362 "RLE {role} bitpacking child missing values compression"
1363 ))
1364 })?
1365 else {
1366 return Err(Error::invalid_input(format!(
1367 "RLE {role} bitpacking child only supports flat values"
1368 )));
1369 };
1370 Ok((out_of_line.uncompressed_bits_per_value, true))
1371 }
1372 other => Err(Error::invalid_input(format!(
1373 "RLE {role} general child only supports flat or out-of-line bitpacking inner encodings, got {}",
1374 compression_name(other)
1375 ))),
1376 }
1377}
1378
1379fn compression_name(compression: &Compression) -> &'static str {
1380 match compression {
1381 Compression::Flat(_) => "flat",
1382 Compression::Variable(_) => "variable",
1383 Compression::Fsst(_) => "fsst",
1384 Compression::OutOfLineBitpacking(_) => "out-of-line bitpacking",
1385 Compression::InlineBitpacking(_) => "inline bitpacking",
1386 Compression::General(_) => "general",
1387 Compression::Constant(_) => "constant",
1388 Compression::Dictionary(_) => "dictionary",
1389 Compression::ByteStreamSplit(_) => "byte stream split",
1390 Compression::PackedStruct(_) => "packed struct",
1391 Compression::FixedSizeList(_) => "fixed-size list",
1392 Compression::VariablePackedStruct(_) => "variable packed struct",
1393 Compression::Rle(_) => "rle",
1394 }
1395}
1396
1397#[cfg(test)]
1398mod tests {
1399 use super::*;
1400 use crate::buffer::LanceBuffer;
1401 use crate::compression_config::CompressionParams;
1402 use crate::data::{BlockInfo, DataBlock, FixedWidthDataBlock};
1403 use crate::encodings::logical::primitive::miniblock::MiniBlockCompressionContext;
1404 use crate::statistics::ComputeStat;
1405 use crate::testing::{TestEncoding, extract_array_encoding_chain, test_compression_strategy};
1406 use arrow_schema::{DataType, Field as ArrowField};
1407 use std::collections::HashMap;
1408
1409 fn strategy(encoding: TestEncoding, params: CompressionParams) -> Arc<dyn CompressionStrategy> {
1410 test_compression_strategy(encoding, params)
1411 }
1412
1413 fn baseline_strategy(params: CompressionParams) -> Arc<dyn CompressionStrategy> {
1414 strategy(TestEncoding::StructuralU16, params)
1415 }
1416
1417 fn miniblock_context() -> MiniBlockCompressionContext {
1418 MiniBlockCompressionContext::new(0, true, true)
1419 }
1420
1421 fn create_test_field(name: &str, data_type: DataType) -> Field {
1422 let arrow_field = ArrowField::new(name, data_type, true);
1423 let mut field = Field::try_from(&arrow_field).unwrap();
1424 field.id = -1;
1425 field
1426 }
1427
1428 fn create_fixed_width_block_with_stats(
1429 bits_per_value: u64,
1430 num_values: u64,
1431 run_count: u64,
1432 ) -> DataBlock {
1433 let bytes_per_value = (bits_per_value / 8) as usize;
1435 let total_bytes = bytes_per_value * num_values as usize;
1436 let mut data = vec![0u8; total_bytes];
1437
1438 let values_per_run = (num_values / run_count).max(1);
1440 let mut run_value = 0u8;
1441
1442 for i in 0..num_values as usize {
1443 if i % values_per_run as usize == 0 {
1444 run_value = run_value.wrapping_add(17); }
1446 for j in 0..bytes_per_value {
1448 let byte_offset = i * bytes_per_value + j;
1449 if byte_offset < data.len() {
1450 data[byte_offset] = run_value.wrapping_add(j as u8);
1451 }
1452 }
1453 }
1454
1455 let mut block = FixedWidthDataBlock {
1456 bits_per_value,
1457 data: LanceBuffer::reinterpret_vec(data),
1458 num_values,
1459 block_info: BlockInfo::default(),
1460 };
1461
1462 use crate::statistics::ComputeStat;
1464 block.compute_stat();
1465
1466 DataBlock::FixedWidth(block)
1467 }
1468
1469 fn create_fixed_width_block(bits_per_value: u64, num_values: u64) -> DataBlock {
1470 let bytes_per_value = (bits_per_value / 8) as usize;
1472 let total_bytes = bytes_per_value * num_values as usize;
1473 let mut data = vec![0u8; total_bytes];
1474
1475 for i in 0..num_values as usize {
1477 let byte_offset = i * bytes_per_value;
1478 if byte_offset < data.len() {
1479 data[byte_offset] = (i % 256) as u8;
1480 }
1481 }
1482
1483 let mut block = FixedWidthDataBlock {
1484 bits_per_value,
1485 data: LanceBuffer::reinterpret_vec(data),
1486 num_values,
1487 block_info: BlockInfo::default(),
1488 };
1489
1490 use crate::statistics::ComputeStat;
1492 block.compute_stat();
1493
1494 DataBlock::FixedWidth(block)
1495 }
1496
1497 fn rle_run_length_bits(encoding: &CompressiveEncoding) -> u64 {
1498 let Compression::Rle(rle) = encoding.compression.as_ref().unwrap() else {
1499 panic!("expected RLE encoding");
1500 };
1501 let Compression::Flat(run_lengths) = rle
1502 .run_lengths
1503 .as_ref()
1504 .unwrap()
1505 .compression
1506 .as_ref()
1507 .unwrap()
1508 else {
1509 panic!("expected flat run lengths");
1510 };
1511 run_lengths.bits_per_value
1512 }
1513
1514 fn expect_rle_encoding(encoding: &CompressiveEncoding) -> &crate::format::pb21::Rle {
1515 match encoding.compression.as_ref().unwrap() {
1516 Compression::Rle(rle) => rle,
1517 Compression::General(general) => {
1518 let inner = general.values.as_ref().unwrap();
1519 let Compression::Rle(rle) = inner.compression.as_ref().unwrap() else {
1520 panic!("expected wrapped RLE encoding");
1521 };
1522 rle
1523 }
1524 other => panic!("expected RLE encoding, got {}", compression_name(other)),
1525 }
1526 }
1527
1528 fn create_variable_width_block(
1529 bits_per_offset: u8,
1530 num_values: u64,
1531 avg_value_size: usize,
1532 ) -> DataBlock {
1533 use crate::statistics::ComputeStat;
1534
1535 let mut offsets = Vec::with_capacity((num_values + 1) as usize);
1537 let mut current_offset = 0i64;
1538 offsets.push(current_offset);
1539
1540 for i in 0..num_values {
1542 let value_size = if avg_value_size == 0 {
1543 1
1544 } else {
1545 ((avg_value_size as i64 + (i as i64 % 8) - 4).max(1) as usize)
1546 .min(avg_value_size * 2)
1547 };
1548 current_offset += value_size as i64;
1549 offsets.push(current_offset);
1550 }
1551
1552 let total_data_size = current_offset as usize;
1554 let mut data = vec![0u8; total_data_size];
1555
1556 for i in 0..num_values {
1558 let start_offset = offsets[i as usize] as usize;
1559 let end_offset = offsets[(i + 1) as usize] as usize;
1560
1561 let content = (i % 256) as u8;
1562 for j in 0..end_offset - start_offset {
1563 data[start_offset + j] = content.wrapping_add(j as u8);
1564 }
1565 }
1566
1567 let offsets_buffer = match bits_per_offset {
1569 32 => {
1570 let offsets_32: Vec<i32> = offsets.iter().map(|&o| o as i32).collect();
1571 LanceBuffer::reinterpret_vec(offsets_32)
1572 }
1573 64 => LanceBuffer::reinterpret_vec(offsets),
1574 _ => panic!("Unsupported bits_per_offset: {}", bits_per_offset),
1575 };
1576
1577 let mut block = VariableWidthBlock {
1578 data: LanceBuffer::from(data),
1579 offsets: offsets_buffer,
1580 bits_per_offset,
1581 num_values,
1582 block_info: BlockInfo::default(),
1583 };
1584
1585 block.compute_stat();
1586 DataBlock::VariableWidth(block)
1587 }
1588
1589 fn create_fsst_candidate_variable_width_block() -> DataBlock {
1590 create_variable_width_block(32, 4096, FSST_LEAST_INPUT_MAX_LENGTH as usize + 16)
1591 }
1592
1593 #[test]
1594 fn test_parameter_based_compression() {
1595 let mut params = CompressionParams::new();
1596
1597 params.columns.insert(
1599 "*_id".to_string(),
1600 CompressionFieldParams {
1601 rle_threshold: Some(0.3),
1602 compression: Some("lz4".to_string()),
1603 compression_level: None,
1604 bss: Some(BssMode::Off), minichunk_size: None,
1606 },
1607 );
1608
1609 let strategy = baseline_strategy(params);
1610 let field = create_test_field("user_id", DataType::Int32);
1611
1612 let data = create_fixed_width_block_with_stats(32, 1000, 100); let compressor = strategy.create_miniblock_compressor(&field, &data).unwrap();
1617 let debug_str = format!("{:?}", compressor);
1619
1620 assert!(debug_str.contains("GeneralMiniBlockCompressor"));
1622 assert!(debug_str.contains("RleEncoder"));
1623 }
1624
1625 #[test]
1626 fn test_type_level_parameters() {
1627 let mut params = CompressionParams::new();
1628
1629 params.types.insert(
1631 "Int32".to_string(),
1632 CompressionFieldParams {
1633 rle_threshold: Some(0.1), compression: Some("zstd".to_string()),
1635 compression_level: Some(3),
1636 bss: Some(BssMode::Off), minichunk_size: None,
1638 },
1639 );
1640
1641 let strategy = baseline_strategy(params);
1642 let field = create_test_field("some_column", DataType::Int32);
1643 let data = create_fixed_width_block_with_stats(32, 1000, 50);
1645
1646 let compressor = strategy.create_miniblock_compressor(&field, &data).unwrap();
1647 assert!(format!("{:?}", compressor).contains("RleEncoder"));
1649 }
1650
1651 #[test]
1654 #[cfg(feature = "bitpacking")]
1655 fn test_block_bitpacks_with_zero_segment() {
1656 let strategy = baseline_strategy(CompressionParams::default());
1657 let field = create_test_field("levels", DataType::UInt16);
1658
1659 let mut values: Vec<u16> = vec![0; 1024];
1661 values.extend(std::iter::repeat_n(1u16, 1024));
1662 let mut block = FixedWidthDataBlock {
1663 bits_per_value: 16,
1664 data: LanceBuffer::reinterpret_vec(values),
1665 num_values: 2048,
1666 block_info: BlockInfo::default(),
1667 };
1668 block.compute_stat();
1669 let data = DataBlock::FixedWidth(block);
1670
1671 let (compressor, _encoding) = strategy.create_block_compressor(&field, &data).unwrap();
1672 let debug_str = format!("{:?}", compressor);
1673 assert!(
1674 debug_str.contains("OutOfLineBitpacking"),
1675 "expected OutOfLineBitpacking, got: {debug_str}"
1676 );
1677 }
1678
1679 #[test]
1680 fn test_rle_block_accounts_for_header_before_selecting() {
1681 let strategy = strategy(TestEncoding::StructuralSparse, CompressionParams::default());
1682 let field = create_test_field("small_constant", DataType::Int32);
1683 let values = vec![42i32; 2];
1684 let mut block = FixedWidthDataBlock {
1685 bits_per_value: 32,
1686 data: LanceBuffer::reinterpret_vec(values),
1687 num_values: 2,
1688 block_info: BlockInfo::default(),
1689 };
1690 block.compute_stat();
1691 let data = DataBlock::FixedWidth(block);
1692
1693 let (compressor, encoding) = strategy.create_block_compressor(&field, &data).unwrap();
1694
1695 assert!(format!("{compressor:?}").contains("ValueEncoder"));
1696 assert!(matches!(
1697 encoding.compression.as_ref(),
1698 Some(Compression::Flat(_))
1699 ));
1700 }
1701
1702 #[test]
1703 #[cfg(feature = "bitpacking")]
1704 fn test_rle_block_prefers_bitpacking_when_smaller() {
1705 let strategy = strategy(TestEncoding::StructuralSparse, CompressionParams::default());
1706 let field = create_test_field("levels", DataType::UInt16);
1707
1708 let mut values = Vec::with_capacity(2048);
1709 for run_idx in 0..1024 {
1710 values.extend(std::iter::repeat_n((run_idx % 2) as u16, 2));
1711 }
1712 let mut block = FixedWidthDataBlock {
1713 bits_per_value: 16,
1714 data: LanceBuffer::reinterpret_vec(values),
1715 num_values: 2048,
1716 block_info: BlockInfo::default(),
1717 };
1718 block.compute_stat();
1719 let data = DataBlock::FixedWidth(block);
1720
1721 let (compressor, encoding) = strategy.create_block_compressor(&field, &data).unwrap();
1722 let debug_str = format!("{compressor:?}");
1723 assert!(
1724 debug_str.contains("OutOfLineBitpacking"),
1725 "expected OutOfLineBitpacking, got: {debug_str}"
1726 );
1727 assert!(matches!(
1728 encoding.compression.as_ref(),
1729 Some(Compression::OutOfLineBitpacking(_))
1730 ));
1731 }
1732
1733 #[test]
1734 #[cfg(feature = "bitpacking")]
1735 fn test_low_cardinality_prefers_bitpacking_over_rle() {
1736 let strategy = baseline_strategy(CompressionParams::default());
1737 let field = create_test_field("int_score", DataType::Int64);
1738
1739 let mut values: Vec<u64> = Vec::with_capacity(256);
1742 for run_idx in 0..64 {
1743 let value = match run_idx % 3 {
1744 0 => 3u64,
1745 1 => 4u64,
1746 _ => 5u64,
1747 };
1748 values.extend(std::iter::repeat_n(value, 4));
1749 }
1750
1751 let mut block = FixedWidthDataBlock {
1752 bits_per_value: 64,
1753 data: LanceBuffer::reinterpret_vec(values),
1754 num_values: 256,
1755 block_info: BlockInfo::default(),
1756 };
1757
1758 use crate::statistics::ComputeStat;
1759 block.compute_stat();
1760
1761 let data = DataBlock::FixedWidth(block);
1762 let compressor = strategy.create_miniblock_compressor(&field, &data).unwrap();
1763 let debug_str = format!("{:?}", compressor);
1764 assert!(
1765 debug_str.contains("InlineBitpacking"),
1766 "expected InlineBitpacking, got: {debug_str}"
1767 );
1768 assert!(
1769 !debug_str.contains("RleEncoder"),
1770 "expected RLE to be skipped when bitpacking is smaller, got: {debug_str}"
1771 );
1772 }
1773
1774 fn check_uncompressed_encoding(encoding: &CompressiveEncoding, variable: bool) {
1775 let chain = extract_array_encoding_chain(encoding);
1776 if variable {
1777 assert_eq!(chain.len(), 2);
1778 assert_eq!(chain.first().unwrap().as_str(), "variable");
1779 assert_eq!(chain.get(1).unwrap().as_str(), "flat");
1780 } else {
1781 assert_eq!(chain.len(), 1);
1782 assert_eq!(chain.first().unwrap().as_str(), "flat");
1783 }
1784 }
1785
1786 #[test]
1787 fn test_none_compression() {
1788 let mut params = CompressionParams::new();
1789
1790 params.columns.insert(
1792 "embeddings".to_string(),
1793 CompressionFieldParams {
1794 compression: Some("none".to_string()),
1795 ..Default::default()
1796 },
1797 );
1798
1799 let strategy = baseline_strategy(params);
1800 let field = create_test_field("embeddings", DataType::Float32);
1801 let fixed_data = create_fixed_width_block(32, 1000);
1802 let variable_data = create_variable_width_block(32, 10, 32 * 1024);
1803
1804 let compressor = strategy
1806 .create_miniblock_compressor(&field, &fixed_data)
1807 .unwrap();
1808 let (_block, encoding) = compressor
1809 .compress(miniblock_context(), fixed_data.clone())
1810 .unwrap();
1811 check_uncompressed_encoding(&encoding, false);
1812 let compressor = strategy
1813 .create_miniblock_compressor(&field, &variable_data)
1814 .unwrap();
1815 let (_block, encoding) = compressor
1816 .compress(miniblock_context(), variable_data.clone())
1817 .unwrap();
1818 check_uncompressed_encoding(&encoding, true);
1819
1820 let compressor = strategy.create_per_value(&field, &fixed_data).unwrap();
1822 let (_block, encoding) = compressor.compress(fixed_data).unwrap();
1823 check_uncompressed_encoding(&encoding, false);
1824 let compressor = strategy.create_per_value(&field, &variable_data).unwrap();
1825 let (_block, encoding) = compressor.compress(variable_data).unwrap();
1826 check_uncompressed_encoding(&encoding, true);
1827 }
1828
1829 #[test]
1830 fn test_field_metadata_none_compression() {
1831 let mut arrow_field = ArrowField::new("simple_col", DataType::Binary, true);
1833 let mut metadata = HashMap::new();
1834 metadata.insert(COMPRESSION_META_KEY.to_string(), "none".to_string());
1835 arrow_field = arrow_field.with_metadata(metadata);
1836 let field = Field::try_from(&arrow_field).unwrap();
1837
1838 let strategy = baseline_strategy(CompressionParams::new());
1839
1840 let fixed_data = create_fixed_width_block(32, 1000);
1842 let variable_data = create_variable_width_block(32, 10, 32 * 1024);
1843
1844 let compressor = strategy
1845 .create_miniblock_compressor(&field, &fixed_data)
1846 .unwrap();
1847 let (_block, encoding) = compressor
1848 .compress(miniblock_context(), fixed_data.clone())
1849 .unwrap();
1850 check_uncompressed_encoding(&encoding, false);
1851
1852 let compressor = strategy
1853 .create_miniblock_compressor(&field, &variable_data)
1854 .unwrap();
1855 let (_block, encoding) = compressor
1856 .compress(miniblock_context(), variable_data.clone())
1857 .unwrap();
1858 check_uncompressed_encoding(&encoding, true);
1859
1860 let compressor = strategy.create_per_value(&field, &fixed_data).unwrap();
1862 let (_block, encoding) = compressor.compress(fixed_data).unwrap();
1863 check_uncompressed_encoding(&encoding, false);
1864
1865 let compressor = strategy.create_per_value(&field, &variable_data).unwrap();
1866 let (_block, encoding) = compressor.compress(variable_data).unwrap();
1867 check_uncompressed_encoding(&encoding, true);
1868 }
1869
1870 #[test]
1871 fn test_auto_fsst_disabled_for_binary_fields() {
1872 let strategy = baseline_strategy(CompressionParams::default());
1873 let field = create_test_field("bytes", DataType::Binary);
1874 let variable_data = create_fsst_candidate_variable_width_block();
1875
1876 let miniblock = strategy
1877 .create_miniblock_compressor(&field, &variable_data)
1878 .unwrap();
1879 let miniblock_debug = format!("{:?}", miniblock);
1880 assert!(
1881 miniblock_debug.contains("BinaryMiniBlockEncoder"),
1882 "expected BinaryMiniBlockEncoder, got: {miniblock_debug}"
1883 );
1884 assert!(
1885 !miniblock_debug.contains("FsstMiniBlockEncoder"),
1886 "did not expect FsstMiniBlockEncoder, got: {miniblock_debug}"
1887 );
1888
1889 let per_value = strategy.create_per_value(&field, &variable_data).unwrap();
1890 let per_value_debug = format!("{:?}", per_value);
1891 assert!(
1892 per_value_debug.contains("VariableEncoder"),
1893 "expected VariableEncoder, got: {per_value_debug}"
1894 );
1895 assert!(
1896 !per_value_debug.contains("FsstPerValueEncoder"),
1897 "did not expect FsstPerValueEncoder, got: {per_value_debug}"
1898 );
1899 }
1900
1901 #[test]
1902 fn test_auto_fsst_still_enabled_for_utf8_fields() {
1903 let strategy = baseline_strategy(CompressionParams::default());
1904 let field = create_test_field("text", DataType::Utf8);
1905 let variable_data = create_fsst_candidate_variable_width_block();
1906
1907 let miniblock = strategy
1908 .create_miniblock_compressor(&field, &variable_data)
1909 .unwrap();
1910 let miniblock_debug = format!("{:?}", miniblock);
1911 assert!(
1912 miniblock_debug.contains("FsstMiniBlockEncoder"),
1913 "expected FsstMiniBlockEncoder, got: {miniblock_debug}"
1914 );
1915
1916 let per_value = strategy.create_per_value(&field, &variable_data).unwrap();
1917 let per_value_debug = format!("{:?}", per_value);
1918 assert!(
1919 per_value_debug.contains("FsstPerValueEncoder"),
1920 "expected FsstPerValueEncoder, got: {per_value_debug}"
1921 );
1922 }
1923
1924 #[test]
1925 fn test_explicit_fsst_still_supported_for_binary_fields() {
1926 let mut params = CompressionParams::new();
1927 params.columns.insert(
1928 "bytes".to_string(),
1929 CompressionFieldParams {
1930 compression: Some("fsst".to_string()),
1931 ..Default::default()
1932 },
1933 );
1934
1935 let strategy = baseline_strategy(params);
1936 let field = create_test_field("bytes", DataType::Binary);
1937 let variable_data = create_fsst_candidate_variable_width_block();
1938
1939 let miniblock = strategy
1940 .create_miniblock_compressor(&field, &variable_data)
1941 .unwrap();
1942 let miniblock_debug = format!("{:?}", miniblock);
1943 assert!(
1944 miniblock_debug.contains("FsstMiniBlockEncoder"),
1945 "expected FsstMiniBlockEncoder, got: {miniblock_debug}"
1946 );
1947
1948 let per_value = strategy.create_per_value(&field, &variable_data).unwrap();
1949 let per_value_debug = format!("{:?}", per_value);
1950 assert!(
1951 per_value_debug.contains("FsstPerValueEncoder"),
1952 "expected FsstPerValueEncoder, got: {per_value_debug}"
1953 );
1954 }
1955
1956 #[test]
1957 #[cfg(feature = "zstd")]
1958 fn test_compression_level_honored_for_large_per_value() {
1959 let mut params = CompressionParams::new();
1960 params.columns.insert(
1961 "html".to_string(),
1962 CompressionFieldParams {
1963 compression: Some("zstd".to_string()),
1964 compression_level: Some(19),
1965 ..Default::default()
1966 },
1967 );
1968 let strategy = baseline_strategy(params);
1969 let field = create_test_field("html", DataType::Utf8);
1970 let large = create_variable_width_block(32, 64, 40 * 1024);
1971
1972 let per_value = strategy.create_per_value(&field, &large).unwrap();
1973 let debug = format!("{per_value:?}");
1974 assert!(
1975 debug.contains("ZstdBufferCompressor") && debug.contains("compression_level: 19"),
1976 "expected zstd level 19 to reach the per-value compressor, got: {debug}"
1977 );
1978 }
1979
1980 #[test]
1981 fn test_parameter_merge_priority() {
1982 let mut params = CompressionParams::new();
1983
1984 params.types.insert(
1986 "Int32".to_string(),
1987 CompressionFieldParams {
1988 rle_threshold: Some(0.5),
1989 compression: Some("lz4".to_string()),
1990 ..Default::default()
1991 },
1992 );
1993
1994 params.columns.insert(
1996 "user_id".to_string(),
1997 CompressionFieldParams {
1998 rle_threshold: Some(0.2),
1999 compression: Some("zstd".to_string()),
2000 compression_level: Some(6),
2001 bss: None,
2002 minichunk_size: None,
2003 },
2004 );
2005
2006 let merged = params.get_field_params("user_id", &DataType::Int32);
2008
2009 assert_eq!(merged.rle_threshold, Some(0.2));
2011 assert_eq!(merged.compression, Some("zstd".to_string()));
2012 assert_eq!(merged.compression_level, Some(6));
2013
2014 let merged = params.get_field_params("other_field", &DataType::Int32);
2016 assert_eq!(merged.rle_threshold, Some(0.5));
2017 assert_eq!(merged.compression, Some("lz4".to_string()));
2018 assert_eq!(merged.compression_level, None);
2019 }
2020
2021 #[test]
2022 fn test_pattern_matching() {
2023 let mut params = CompressionParams::new();
2024
2025 params.columns.insert(
2027 "log_*".to_string(),
2028 CompressionFieldParams {
2029 compression: Some("zstd".to_string()),
2030 compression_level: Some(6),
2031 ..Default::default()
2032 },
2033 );
2034
2035 let merged = params.get_field_params("log_messages", &DataType::Utf8);
2037 assert_eq!(merged.compression, Some("zstd".to_string()));
2038 assert_eq!(merged.compression_level, Some(6));
2039
2040 let merged = params.get_field_params("messages_log", &DataType::Utf8);
2042 assert_eq!(merged.compression, None);
2043 }
2044
2045 #[test]
2046 fn test_legacy_metadata_support() {
2047 let params = CompressionParams::new();
2048 let strategy = baseline_strategy(params);
2049
2050 let mut metadata = HashMap::new();
2052 metadata.insert(COMPRESSION_META_KEY.to_string(), "none".to_string());
2053 let mut field = create_test_field("some_column", DataType::Int32);
2054 field.metadata = metadata;
2055
2056 let data = create_fixed_width_block(32, 1000);
2057 let compressor = strategy.create_miniblock_compressor(&field, &data).unwrap();
2058
2059 assert!(format!("{:?}", compressor).contains("ValueEncoder"));
2061 }
2062
2063 #[test]
2064 fn test_default_behavior() {
2065 let params = CompressionParams::new();
2067 let strategy = baseline_strategy(params);
2068
2069 let field = create_test_field("random_column", DataType::Int32);
2070 let data = create_fixed_width_block_with_stats(32, 1000, 600);
2072
2073 let compressor = strategy.create_miniblock_compressor(&field, &data).unwrap();
2074 let debug_str = format!("{:?}", compressor);
2076 assert!(debug_str.contains("ValueEncoder") || debug_str.contains("InlineBitpacking"));
2077 }
2078
2079 #[test]
2080 fn test_field_metadata_compression() {
2081 let params = CompressionParams::new();
2082 let strategy = baseline_strategy(params);
2083
2084 let mut metadata = HashMap::new();
2086 metadata.insert(COMPRESSION_META_KEY.to_string(), "zstd".to_string());
2087 metadata.insert(COMPRESSION_LEVEL_META_KEY.to_string(), "6".to_string());
2088 let mut field = create_test_field("test_column", DataType::Int32);
2089 field.metadata = metadata;
2090
2091 let data = create_fixed_width_block(32, 1000);
2092 let compressor = strategy.create_miniblock_compressor(&field, &data).unwrap();
2093
2094 let debug_str = format!("{:?}", compressor);
2096 assert!(debug_str.contains("GeneralMiniBlockCompressor"));
2097 }
2098
2099 #[test]
2100 fn test_field_metadata_rle_threshold() {
2101 let params = CompressionParams::new();
2102 let strategy = baseline_strategy(params);
2103
2104 let mut metadata = HashMap::new();
2106 metadata.insert(RLE_THRESHOLD_META_KEY.to_string(), "0.8".to_string());
2107 metadata.insert(BSS_META_KEY.to_string(), "off".to_string()); let mut field = create_test_field("test_column", DataType::Int32);
2109 field.metadata = metadata;
2110
2111 let data = create_fixed_width_block_with_stats(32, 1000, 100);
2114
2115 let compressor = strategy.create_miniblock_compressor(&field, &data).unwrap();
2116
2117 let debug_str = format!("{:?}", compressor);
2119 assert!(debug_str.contains("RleEncoder"));
2120 }
2121
2122 #[test]
2123 fn test_rle_v2_miniblock_selects_u16_run_lengths() {
2124 let mut metadata = HashMap::new();
2125 metadata.insert(RLE_THRESHOLD_META_KEY.to_string(), "1.0".to_string());
2126 metadata.insert(BSS_META_KEY.to_string(), "off".to_string());
2127 let mut field = create_test_field("test_column", DataType::Int32);
2128 field.metadata = metadata;
2129
2130 let values = vec![7i32; 1000];
2131 let mut data = FixedWidthDataBlock {
2132 bits_per_value: 32,
2133 data: LanceBuffer::reinterpret_vec(values),
2134 num_values: 1000,
2135 block_info: BlockInfo::default(),
2136 };
2137 data.compute_stat();
2138 let data = DataBlock::FixedWidth(data);
2139
2140 let strategy = strategy(TestEncoding::StructuralSparse, CompressionParams::default());
2141 let compressor = strategy.create_miniblock_compressor(&field, &data).unwrap();
2142 let (_compressed, encoding) = compressor.compress(miniblock_context(), data).unwrap();
2143 assert_eq!(rle_run_length_bits(&encoding), 16);
2144 }
2145
2146 #[test]
2147 fn test_rle_v2_miniblock_keeps_u8_run_lengths_before_v2_3() {
2148 for version in [TestEncoding::StructuralU16, TestEncoding::StructuralU32] {
2149 let mut metadata = HashMap::new();
2150 metadata.insert(RLE_THRESHOLD_META_KEY.to_string(), "1.0".to_string());
2151 metadata.insert(BSS_META_KEY.to_string(), "off".to_string());
2152 let mut field = create_test_field("test_column", DataType::Int32);
2153 field.metadata = metadata;
2154
2155 let values = vec![7i32; 1000];
2156 let mut data = FixedWidthDataBlock {
2157 bits_per_value: 32,
2158 data: LanceBuffer::reinterpret_vec(values),
2159 num_values: 1000,
2160 block_info: BlockInfo::default(),
2161 };
2162 data.compute_stat();
2163 let data = DataBlock::FixedWidth(data);
2164
2165 let strategy = strategy(version, CompressionParams::default());
2166 let compressor = strategy.create_miniblock_compressor(&field, &data).unwrap();
2167 let (_compressed, encoding) = compressor.compress(miniblock_context(), data).unwrap();
2168 assert_eq!(rle_run_length_bits(&encoding), 8, "version={version}");
2169 }
2170 }
2171
2172 #[test]
2173 fn test_rle_v2_uses_selected_width_cost_before_bitpacking() {
2174 let mut metadata = HashMap::new();
2175 metadata.insert(RLE_THRESHOLD_META_KEY.to_string(), "1.0".to_string());
2176 metadata.insert(BSS_META_KEY.to_string(), "off".to_string());
2177 let mut field = create_test_field("test_column", DataType::Int32);
2178 field.metadata = metadata;
2179
2180 let values = vec![0i32; 4096];
2181 let mut data = FixedWidthDataBlock {
2182 bits_per_value: 32,
2183 data: LanceBuffer::reinterpret_vec(values),
2184 num_values: 4096,
2185 block_info: BlockInfo::default(),
2186 };
2187 data.compute_stat();
2188 let data = DataBlock::FixedWidth(data);
2189
2190 let strategy = strategy(TestEncoding::StructuralSparse, CompressionParams::default());
2191 let compressor = strategy.create_miniblock_compressor(&field, &data).unwrap();
2192 let debug_str = format!("{compressor:?}");
2193 assert!(debug_str.contains("RleEncoder"));
2194
2195 let (_compressed, encoding) = compressor.compress(miniblock_context(), data).unwrap();
2196 assert_eq!(rle_run_length_bits(&encoding), 16);
2197 }
2198
2199 #[test]
2200 fn test_rle_v2_sorted_dictionary_indices_select_u16_run_lengths() {
2201 let field = create_test_field("dict_indices", DataType::Int32);
2202
2203 let mut values = Vec::with_capacity(1_200);
2204 for value in 0..4 {
2205 values.extend(std::iter::repeat_n(value, 300));
2206 }
2207 let mut data = FixedWidthDataBlock {
2208 bits_per_value: 32,
2209 data: LanceBuffer::reinterpret_vec(values),
2210 num_values: 1_200,
2211 block_info: BlockInfo::default(),
2212 };
2213 data.compute_stat();
2214 let data = DataBlock::FixedWidth(data);
2215
2216 let strategy = strategy(TestEncoding::StructuralSparse, CompressionParams::default());
2217 let compressor = strategy.create_miniblock_compressor(&field, &data).unwrap();
2218 let (_compressed, encoding) = compressor.compress(miniblock_context(), data).unwrap();
2219 assert_eq!(rle_run_length_bits(&encoding), 16);
2220 }
2221
2222 #[test]
2223 fn test_rle_v2_short_runs_keep_u8_run_lengths() {
2224 let field = create_test_field("dict_indices", DataType::Int32);
2225
2226 let mut values = Vec::with_capacity(1_280);
2227 for value in 0..10 {
2228 values.extend(std::iter::repeat_n(value, 128));
2229 }
2230 let mut data = FixedWidthDataBlock {
2231 bits_per_value: 32,
2232 data: LanceBuffer::reinterpret_vec(values),
2233 num_values: 1_280,
2234 block_info: BlockInfo::default(),
2235 };
2236 data.compute_stat();
2237 let data = DataBlock::FixedWidth(data);
2238
2239 let strategy = strategy(TestEncoding::StructuralSparse, CompressionParams::default());
2240 let compressor = strategy.create_miniblock_compressor(&field, &data).unwrap();
2241 let (_compressed, encoding) = compressor.compress(miniblock_context(), data).unwrap();
2242 assert_eq!(rle_run_length_bits(&encoding), 8);
2243 }
2244
2245 #[test]
2246 #[cfg(any(feature = "lz4", feature = "zstd"))]
2247 fn test_rle_miniblock_released_versions_keep_flat_children_when_compression_requested() {
2248 for version in [TestEncoding::StructuralU16, TestEncoding::StructuralU32] {
2249 let mut params = CompressionParams::new();
2250 params.columns.insert(
2251 "dict_indices".to_string(),
2252 CompressionFieldParams {
2253 compression: Some(
2254 if cfg!(feature = "lz4") { "lz4" } else { "zstd" }.to_string(),
2255 ),
2256 rle_threshold: Some(1.0),
2257 bss: Some(BssMode::Off),
2258 ..Default::default()
2259 },
2260 );
2261 let strategy = strategy(version, params);
2262 let field = create_test_field("dict_indices", DataType::UInt32);
2263
2264 let mut values = Vec::with_capacity(8192 * 4);
2265 for value in 0..8192u32 {
2266 values.extend(std::iter::repeat_n(value, 4));
2267 }
2268 let mut data = FixedWidthDataBlock {
2269 bits_per_value: 32,
2270 data: LanceBuffer::reinterpret_vec(values),
2271 num_values: 8192 * 4,
2272 block_info: BlockInfo::default(),
2273 };
2274 data.compute_stat();
2275 let data = DataBlock::FixedWidth(data);
2276
2277 let compressor = strategy.create_miniblock_compressor(&field, &data).unwrap();
2278 let (_compressed, encoding) = compressor.compress(miniblock_context(), data).unwrap();
2279 let rle = expect_rle_encoding(&encoding);
2280
2281 assert!(
2282 matches!(
2283 rle.values.as_ref().unwrap().compression.as_ref().unwrap(),
2284 Compression::Flat(_)
2285 ),
2286 "version={version}"
2287 );
2288 assert!(
2289 matches!(
2290 rle.run_lengths
2291 .as_ref()
2292 .unwrap()
2293 .compression
2294 .as_ref()
2295 .unwrap(),
2296 Compression::Flat(_)
2297 ),
2298 "version={version}"
2299 );
2300 }
2301 }
2302
2303 #[test]
2304 #[cfg(feature = "bitpacking")]
2305 fn test_rle_miniblock_strategy_bitpacks_child_values_when_smaller() {
2306 let field = create_test_field("dict_indices", DataType::Int32);
2307
2308 let mut values = Vec::with_capacity(8192 * 4);
2309 for value in 0..8192 {
2310 values.extend(std::iter::repeat_n(value, 4));
2311 }
2312 let mut data = FixedWidthDataBlock {
2313 bits_per_value: 32,
2314 data: LanceBuffer::reinterpret_vec(values),
2315 num_values: 8192 * 4,
2316 block_info: BlockInfo::default(),
2317 };
2318 data.compute_stat();
2319 let data = DataBlock::FixedWidth(data);
2320
2321 let strategy = strategy(TestEncoding::StructuralSparse, CompressionParams::default());
2322 let compressor = strategy.create_miniblock_compressor(&field, &data).unwrap();
2323 let debug_str = format!("{compressor:?}");
2324 assert!(debug_str.contains("RleEncoder"));
2325
2326 let (_compressed, encoding) = compressor.compress(miniblock_context(), data).unwrap();
2327 let Compression::Rle(rle) = encoding.compression.as_ref().unwrap() else {
2328 panic!("expected RLE encoding");
2329 };
2330 assert!(matches!(
2331 rle.values.as_ref().unwrap().compression.as_ref().unwrap(),
2332 Compression::OutOfLineBitpacking(_)
2333 ));
2334 assert!(matches!(
2335 rle.run_lengths
2336 .as_ref()
2337 .unwrap()
2338 .compression
2339 .as_ref()
2340 .unwrap(),
2341 Compression::Flat(_)
2342 ));
2343 }
2344
2345 #[test]
2346 #[cfg(feature = "bitpacking")]
2347 fn test_rle_miniblock_keeps_child_bitpacked_rle_when_smaller_than_inline_bitpacking() {
2348 let field = create_test_field("int_score", DataType::UInt64);
2349
2350 let mut values = Vec::with_capacity(8192 * 8);
2351 for run_idx in 0..8192 {
2352 let value = match run_idx % 3 {
2353 0 => 3u64,
2354 1 => 4u64,
2355 _ => 5u64,
2356 };
2357 values.extend(std::iter::repeat_n(value, 8));
2358 }
2359 let mut data = FixedWidthDataBlock {
2360 bits_per_value: 64,
2361 data: LanceBuffer::reinterpret_vec(values),
2362 num_values: 8192 * 8,
2363 block_info: BlockInfo::default(),
2364 };
2365 data.compute_stat();
2366 let data = DataBlock::FixedWidth(data);
2367
2368 let strategy = strategy(TestEncoding::StructuralSparse, CompressionParams::default());
2369 let compressor = strategy.create_miniblock_compressor(&field, &data).unwrap();
2370 let debug_str = format!("{compressor:?}");
2371 assert!(
2372 debug_str.contains("RleEncoder"),
2373 "expected RLE to beat inline bitpacking after child selection, got: {debug_str}"
2374 );
2375
2376 let (_compressed, encoding) = compressor.compress(miniblock_context(), data).unwrap();
2377 let rle = expect_rle_encoding(&encoding);
2378 assert!(matches!(
2379 rle.values.as_ref().unwrap().compression.as_ref().unwrap(),
2380 Compression::OutOfLineBitpacking(_)
2381 ));
2382 assert!(matches!(
2383 rle.run_lengths
2384 .as_ref()
2385 .unwrap()
2386 .compression
2387 .as_ref()
2388 .unwrap(),
2389 Compression::Flat(_)
2390 ));
2391 }
2392
2393 #[test]
2394 fn test_field_metadata_override_params() {
2395 let mut params = CompressionParams::new();
2397 params.columns.insert(
2398 "test_column".to_string(),
2399 CompressionFieldParams {
2400 rle_threshold: Some(0.3),
2401 compression: Some("lz4".to_string()),
2402 compression_level: None,
2403 bss: None,
2404 minichunk_size: None,
2405 },
2406 );
2407
2408 let strategy = baseline_strategy(params);
2409
2410 let mut metadata = HashMap::new();
2412 metadata.insert(COMPRESSION_META_KEY.to_string(), "none".to_string());
2413 let mut field = create_test_field("test_column", DataType::Int32);
2414 field.metadata = metadata;
2415
2416 let data = create_fixed_width_block(32, 1000);
2417 let compressor = strategy.create_miniblock_compressor(&field, &data).unwrap();
2418
2419 assert!(format!("{:?}", compressor).contains("ValueEncoder"));
2421 }
2422
2423 #[test]
2424 fn test_field_metadata_mixed_configuration() {
2425 let mut params = CompressionParams::new();
2427 params.types.insert(
2428 "Int32".to_string(),
2429 CompressionFieldParams {
2430 rle_threshold: Some(0.5),
2431 compression: Some("lz4".to_string()),
2432 ..Default::default()
2433 },
2434 );
2435
2436 let strategy = baseline_strategy(params);
2437
2438 let mut metadata = HashMap::new();
2440 metadata.insert(COMPRESSION_LEVEL_META_KEY.to_string(), "3".to_string());
2441 let mut field = create_test_field("test_column", DataType::Int32);
2442 field.metadata = metadata;
2443
2444 let data = create_fixed_width_block(32, 1000);
2445 let compressor = strategy.create_miniblock_compressor(&field, &data).unwrap();
2446
2447 let debug_str = format!("{:?}", compressor);
2449 assert!(debug_str.contains("GeneralMiniBlockCompressor"));
2450 }
2451
2452 #[test]
2453 fn test_bss_field_metadata() {
2454 let params = CompressionParams::new();
2455 let strategy = baseline_strategy(params);
2456
2457 let mut metadata = HashMap::new();
2459 metadata.insert(BSS_META_KEY.to_string(), "on".to_string());
2460 metadata.insert(COMPRESSION_META_KEY.to_string(), "lz4".to_string());
2461 let arrow_field =
2462 ArrowField::new("temperature", DataType::Float32, false).with_metadata(metadata);
2463 let field = Field::try_from(&arrow_field).unwrap();
2464
2465 let data = create_fixed_width_block(32, 100);
2467
2468 let compressor = strategy.create_miniblock_compressor(&field, &data).unwrap();
2469 let debug_str = format!("{:?}", compressor);
2470 assert!(debug_str.contains("ByteStreamSplitEncoder"));
2471 }
2472
2473 #[test]
2474 fn test_bss_with_compression() {
2475 let params = CompressionParams::new();
2476 let strategy = baseline_strategy(params);
2477
2478 let mut metadata = HashMap::new();
2480 metadata.insert(BSS_META_KEY.to_string(), "on".to_string());
2481 metadata.insert(COMPRESSION_META_KEY.to_string(), "lz4".to_string());
2482 let arrow_field =
2483 ArrowField::new("sensor_data", DataType::Float64, false).with_metadata(metadata);
2484 let field = Field::try_from(&arrow_field).unwrap();
2485
2486 let data = create_fixed_width_block(64, 100);
2488
2489 let compressor = strategy.create_miniblock_compressor(&field, &data).unwrap();
2490 let debug_str = format!("{:?}", compressor);
2491 assert!(debug_str.contains("GeneralMiniBlockCompressor"));
2493 assert!(debug_str.contains("ByteStreamSplitEncoder"));
2494 }
2495
2496 #[test]
2497 #[cfg(any(feature = "lz4", feature = "zstd"))]
2498 fn test_general_block_decompression_fixed_width_v2_2() {
2499 let mut params = CompressionParams::new();
2501 params.columns.insert(
2502 "dict_values".to_string(),
2503 CompressionFieldParams {
2504 compression: Some(if cfg!(feature = "lz4") { "lz4" } else { "zstd" }.to_string()),
2505 ..Default::default()
2506 },
2507 );
2508
2509 let strategy = strategy(TestEncoding::StructuralU32, params);
2510
2511 let field = create_test_field("dict_values", DataType::FixedSizeBinary(3));
2512 let data = create_fixed_width_block(24, 1024);
2513 let DataBlock::FixedWidth(expected_block) = &data else {
2514 panic!("expected fixed width block");
2515 };
2516 let expected_bits = expected_block.bits_per_value;
2517 let expected_num_values = expected_block.num_values;
2518 let num_values = expected_num_values;
2519
2520 let (compressor, encoding) = strategy
2521 .create_block_compressor(&field, &data)
2522 .expect("general compression should be selected");
2523 match encoding.compression.as_ref() {
2524 Some(Compression::General(_)) => {}
2525 other => panic!("expected general compression, got {:?}", other),
2526 }
2527
2528 let compressed_buffer = compressor
2529 .compress(data.clone())
2530 .expect("write path general compression should succeed");
2531
2532 let decompressor = DefaultDecompressionStrategy::default()
2533 .create_block_decompressor(&encoding)
2534 .expect("general block decompressor should be created");
2535
2536 let decoded = decompressor
2537 .decompress(compressed_buffer, num_values)
2538 .expect("decompression should succeed");
2539
2540 match decoded {
2541 DataBlock::FixedWidth(block) => {
2542 assert_eq!(block.bits_per_value, expected_bits);
2543 assert_eq!(block.num_values, expected_num_values);
2544 assert_eq!(block.data.as_ref(), expected_block.data.as_ref());
2545 }
2546 _ => panic!("expected fixed width block"),
2547 }
2548 }
2549
2550 #[test]
2551 #[cfg(any(feature = "lz4", feature = "zstd"))]
2552 fn test_general_compression_not_selected_for_v2_1_even_if_requested() {
2553 let mut params = CompressionParams::new();
2554 params.columns.insert(
2555 "dict_values".to_string(),
2556 CompressionFieldParams {
2557 compression: Some(if cfg!(feature = "lz4") { "lz4" } else { "zstd" }.to_string()),
2558 ..Default::default()
2559 },
2560 );
2561
2562 let strategy = strategy(TestEncoding::StructuralU16, params);
2563 let field = create_test_field("dict_values", DataType::FixedSizeBinary(3));
2564 let data = create_fixed_width_block(24, 1024);
2565
2566 let (_compressor, encoding) = strategy
2567 .create_block_compressor(&field, &data)
2568 .expect("block compressor selection should succeed");
2569
2570 assert!(
2571 !matches!(encoding.compression.as_ref(), Some(Compression::General(_))),
2572 "general compression should not be selected for V2.1"
2573 );
2574 }
2575
2576 #[test]
2577 fn test_none_compression_disables_auto_general_block_compression() {
2578 let mut params = CompressionParams::new();
2579 params.columns.insert(
2580 "dict_values".to_string(),
2581 CompressionFieldParams {
2582 compression: Some("none".to_string()),
2583 ..Default::default()
2584 },
2585 );
2586
2587 let strategy = strategy(TestEncoding::StructuralU32, params);
2588 let field = create_test_field("dict_values", DataType::FixedSizeBinary(3));
2589 let data = create_fixed_width_block(24, 20_000);
2590
2591 assert!(
2592 data.data_size() > MIN_BLOCK_SIZE_FOR_GENERAL_COMPRESSION,
2593 "test requires block size above automatic general compression threshold"
2594 );
2595
2596 let (_compressor, encoding) = strategy
2597 .create_block_compressor(&field, &data)
2598 .expect("block compressor selection should succeed");
2599
2600 assert!(
2601 !matches!(encoding.compression.as_ref(), Some(Compression::General(_))),
2602 "compression=none should disable automatic block general compression"
2603 );
2604 }
2605
2606 #[test]
2607 fn test_rle_v2_block_selects_u32_run_lengths() {
2608 let field = create_test_field("dict_indices", DataType::Int32);
2609 let expected_values = vec![42i32; 70_000];
2610 let mut block = FixedWidthDataBlock {
2611 bits_per_value: 32,
2612 data: LanceBuffer::reinterpret_vec(expected_values.clone()),
2613 num_values: expected_values.len() as u64,
2614 block_info: BlockInfo::default(),
2615 };
2616 block.compute_stat();
2617 let data = DataBlock::FixedWidth(block);
2618
2619 let strategy = strategy(TestEncoding::StructuralSparse, CompressionParams::new());
2620 let (compressor, encoding) = strategy.create_block_compressor(&field, &data).unwrap();
2621 assert_eq!(rle_run_length_bits(&encoding), 32);
2622
2623 let compressed = compressor.compress(data).unwrap();
2624 let decompressor = DefaultDecompressionStrategy::default()
2625 .create_block_decompressor(&encoding)
2626 .unwrap();
2627 let decoded = decompressor
2628 .decompress(compressed, expected_values.len() as u64)
2629 .unwrap();
2630
2631 match decoded {
2632 DataBlock::FixedWidth(block) => {
2633 let values = block.data.borrow_to_typed_slice::<i32>();
2634 assert_eq!(values.as_ref(), expected_values);
2635 }
2636 _ => panic!("expected fixed-width block"),
2637 }
2638 }
2639
2640 #[test]
2641 fn test_rle_v2_block_keeps_u8_run_lengths_for_v2_2() {
2642 let field = create_test_field("dict_indices", DataType::Int32);
2643 let values = vec![42i32; 70_000];
2644 let mut block = FixedWidthDataBlock {
2645 bits_per_value: 32,
2646 data: LanceBuffer::reinterpret_vec(values),
2647 num_values: 70_000,
2648 block_info: BlockInfo::default(),
2649 };
2650 block.compute_stat();
2651 let data = DataBlock::FixedWidth(block);
2652
2653 let strategy = strategy(TestEncoding::StructuralU32, CompressionParams::new());
2654 let (_compressor, encoding) = strategy.create_block_compressor(&field, &data).unwrap();
2655 assert_eq!(rle_run_length_bits(&encoding), 8);
2656 }
2657
2658 #[test]
2659 fn test_rle_block_used_for_version_v2_2() {
2660 let field = create_test_field("test_repdef", DataType::UInt16);
2661
2662 let num_values = 1000u64;
2664 let mut data = Vec::with_capacity(num_values as usize);
2665 for i in 0..10 {
2666 for _ in 0..100 {
2667 data.push(i as u16);
2668 }
2669 }
2670
2671 let mut block = FixedWidthDataBlock {
2672 bits_per_value: 16,
2673 data: LanceBuffer::reinterpret_vec(data),
2674 num_values,
2675 block_info: BlockInfo::default(),
2676 };
2677
2678 block.compute_stat();
2679
2680 let data_block = DataBlock::FixedWidth(block);
2681
2682 let strategy = strategy(TestEncoding::StructuralU32, CompressionParams::new());
2683
2684 let (compressor, _) = strategy
2685 .create_block_compressor(&field, &data_block)
2686 .unwrap();
2687
2688 let debug_str = format!("{:?}", compressor);
2689 assert!(debug_str.contains("RleEncoder"));
2690 }
2691
2692 #[test]
2693 fn test_rle_block_not_used_for_version_v2_1() {
2694 let field = create_test_field("test_repdef", DataType::UInt16);
2695
2696 let num_values = 1000u64;
2698 let mut data = Vec::with_capacity(num_values as usize);
2699 for i in 0..10 {
2700 for _ in 0..100 {
2701 data.push(i as u16);
2702 }
2703 }
2704
2705 let mut block = FixedWidthDataBlock {
2706 bits_per_value: 16,
2707 data: LanceBuffer::reinterpret_vec(data),
2708 num_values,
2709 block_info: BlockInfo::default(),
2710 };
2711
2712 block.compute_stat();
2713
2714 let data_block = DataBlock::FixedWidth(block);
2715
2716 let strategy = strategy(TestEncoding::StructuralU16, CompressionParams::new());
2717
2718 let (compressor, _) = strategy
2719 .create_block_compressor(&field, &data_block)
2720 .unwrap();
2721
2722 let debug_str = format!("{:?}", compressor);
2723 assert!(
2724 !debug_str.contains("RleEncoder"),
2725 "RLE should not be used for V2.1"
2726 );
2727 }
2728}