1use arrow_buffer::ArrowNativeType;
25use lance_core::{Error, Result};
26
27use std::str::FromStr;
28
29use crate::compression::{BlockCompressor, BlockDecompressor, require_block_payload};
30use crate::encodings::physical::binary::{BinaryBlockDecompressor, VariableEncoder};
31use crate::format::{
32 ProtobufUtils21,
33 pb21::{self, CompressiveEncoding},
34};
35use crate::{
36 buffer::LanceBuffer,
37 compression::VariablePerValueDecompressor,
38 data::{BlockInfo, DataBlock, VariableWidthBlock},
39 encodings::logical::primitive::fullzip::{PerValueCompressor, PerValueDataBlock},
40};
41
42#[derive(Debug, Clone, Copy, PartialEq)]
43pub struct CompressionConfig {
44 pub(crate) scheme: CompressionScheme,
45 pub(crate) level: Option<i32>,
46}
47
48impl CompressionConfig {
49 pub fn new(scheme: CompressionScheme, level: Option<i32>) -> Self {
51 Self { scheme, level }
52 }
53
54 pub fn scheme(&self) -> CompressionScheme {
56 self.scheme
57 }
58
59 pub fn level(&self) -> Option<i32> {
61 self.level
62 }
63}
64
65impl Default for CompressionConfig {
66 fn default() -> Self {
67 Self {
68 scheme: CompressionScheme::Lz4,
69 level: Some(0),
70 }
71 }
72}
73
74#[derive(Debug, Clone, Copy, PartialEq)]
75pub enum CompressionScheme {
76 None,
77 Fsst,
78 Zstd,
79 Lz4,
80}
81
82impl TryFrom<CompressionScheme> for pb21::CompressionScheme {
83 type Error = Error;
84
85 fn try_from(scheme: CompressionScheme) -> Result<Self> {
86 match scheme {
87 CompressionScheme::Lz4 => Ok(Self::CompressionAlgorithmLz4),
88 CompressionScheme::Zstd => Ok(Self::CompressionAlgorithmZstd),
89 _ => Err(Error::invalid_input(format!(
90 "Unsupported compression scheme: {:?}",
91 scheme
92 ))),
93 }
94 }
95}
96
97impl TryFrom<pb21::CompressionScheme> for CompressionScheme {
98 type Error = Error;
99
100 fn try_from(scheme: pb21::CompressionScheme) -> Result<Self> {
101 match scheme {
102 pb21::CompressionScheme::CompressionAlgorithmLz4 => Ok(Self::Lz4),
103 pb21::CompressionScheme::CompressionAlgorithmZstd => Ok(Self::Zstd),
104 _ => Err(Error::invalid_input(format!(
105 "Unsupported compression scheme: {:?}",
106 scheme
107 ))),
108 }
109 }
110}
111
112impl std::fmt::Display for CompressionScheme {
113 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
114 let scheme_str = match self {
115 Self::Fsst => "fsst",
116 Self::Zstd => "zstd",
117 Self::None => "none",
118 Self::Lz4 => "lz4",
119 };
120 write!(f, "{}", scheme_str)
121 }
122}
123
124impl FromStr for CompressionScheme {
125 type Err = Error;
126
127 fn from_str(s: &str) -> Result<Self> {
128 match s {
129 "none" => Ok(Self::None),
130 "fsst" => Ok(Self::Fsst),
131 "zstd" => Ok(Self::Zstd),
132 "lz4" => Ok(Self::Lz4),
133 _ => Err(Error::invalid_input(format!(
134 "Unknown compression scheme: {}",
135 s
136 ))),
137 }
138 }
139}
140
141pub trait BufferCompressor: std::fmt::Debug + Send + Sync {
142 fn compress(&self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()>;
143 fn decompress(&self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()>;
144 fn config(&self) -> CompressionConfig;
145}
146
147#[cfg(feature = "zstd")]
148mod zstd {
149 use std::io::{Cursor, Write};
150 use std::sync::{Mutex, OnceLock};
151
152 use super::*;
153
154 use ::zstd::bulk::{Compressor, decompress_to_buffer};
155 use ::zstd::stream::copy_decode;
156
157 pub struct ZstdBufferCompressor {
171 compression_level: i32,
172 compressor: OnceLock<std::result::Result<Mutex<Compressor<'static>>, String>>,
173 }
174
175 impl std::fmt::Debug for ZstdBufferCompressor {
176 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
177 f.debug_struct("ZstdBufferCompressor")
178 .field("compression_level", &self.compression_level)
179 .finish()
180 }
181 }
182
183 impl ZstdBufferCompressor {
184 pub fn new(compression_level: i32) -> Self {
185 Self {
186 compression_level,
187 compressor: OnceLock::new(),
188 }
189 }
190
191 fn get_compressor(&self) -> Result<&Mutex<Compressor<'static>>> {
192 self.compressor
193 .get_or_init(|| {
194 Compressor::new(self.compression_level)
195 .map(Mutex::new)
196 .map_err(|e| e.to_string())
197 })
198 .as_ref()
199 .map_err(|e| Error::internal(format!("Failed to create zstd compressor: {}", e)))
200 }
201
202 fn is_raw_stream_format(&self, input_buf: &[u8]) -> bool {
204 if input_buf.len() < 8 {
205 return true; }
207 let mut magic_buf = [0u8; 4];
209 magic_buf.copy_from_slice(&input_buf[..4]);
210 let magic = u32::from_le_bytes(magic_buf);
211
212 const ZSTD_MAGIC_NUMBER: u32 = 0xFD2FB528;
214 if magic == ZSTD_MAGIC_NUMBER {
215 const FHD_BYTE_INDEX: usize = 4;
219 let fhd_byte = input_buf[FHD_BYTE_INDEX];
220 const FHD_RESERVED_BIT_MASK: u8 = 0b0001_0000;
221 let reserved_bit = fhd_byte & FHD_RESERVED_BIT_MASK;
222
223 if reserved_bit != 0 {
224 false
228 } else {
229 true
232 }
233 } else {
234 false
236 }
237 }
238
239 fn decompress_length_prefixed_zstd(
240 &self,
241 input_buf: &[u8],
242 output_buf: &mut Vec<u8>,
243 ) -> Result<()> {
244 const LENGTH_PREFIX_SIZE: usize = 8;
245 let mut len_buf = [0u8; LENGTH_PREFIX_SIZE];
246 len_buf.copy_from_slice(&input_buf[..LENGTH_PREFIX_SIZE]);
247
248 let uncompressed_len = u64::from_le_bytes(len_buf) as usize;
249
250 let start = output_buf.len();
251 output_buf.resize(start + uncompressed_len, 0);
252
253 let compressed_data = &input_buf[LENGTH_PREFIX_SIZE..];
254 decompress_to_buffer(compressed_data, &mut output_buf[start..])?;
255 Ok(())
256 }
257 }
258
259 impl BufferCompressor for ZstdBufferCompressor {
260 fn compress(&self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()> {
261 output_buf.write_all(&(input_buf.len() as u64).to_le_bytes())?;
262
263 let max_compressed_size = ::zstd::zstd_safe::compress_bound(input_buf.len());
264 let start_pos = output_buf.len();
265 output_buf.resize(start_pos + max_compressed_size, 0);
266
267 let compressed_size = self
268 .get_compressor()?
269 .lock()
270 .unwrap()
271 .compress_to_buffer(input_buf, &mut output_buf[start_pos..])
272 .map_err(|e| Error::internal(format!("Zstd compression error: {}", e)))?;
273
274 output_buf.truncate(start_pos + compressed_size);
275 Ok(())
276 }
277
278 fn decompress(&self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()> {
279 if input_buf.is_empty() {
280 return Ok(());
281 }
282
283 let is_raw_stream_format = self.is_raw_stream_format(input_buf);
284 if is_raw_stream_format {
285 copy_decode(Cursor::new(input_buf), output_buf)?;
286 } else {
287 self.decompress_length_prefixed_zstd(input_buf, output_buf)?;
288 }
289
290 Ok(())
291 }
292
293 fn config(&self) -> CompressionConfig {
294 CompressionConfig {
295 scheme: CompressionScheme::Zstd,
296 level: Some(self.compression_level),
297 }
298 }
299 }
300}
301
302#[cfg(feature = "lz4")]
303mod lz4 {
304 use super::*;
305
306 #[derive(Debug, Default)]
307 pub struct Lz4BufferCompressor {}
308
309 impl BufferCompressor for Lz4BufferCompressor {
310 fn compress(&self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()> {
311 let start_pos = output_buf.len();
313
314 let max_size = ::lz4::block::compress_bound(input_buf.len())?;
316 output_buf.resize(start_pos + max_size + 4, 0);
318
319 let compressed_size = ::lz4::block::compress_to_buffer(
320 input_buf,
321 None,
322 true,
323 &mut output_buf[start_pos..],
324 )
325 .map_err(|err| Error::internal(format!("LZ4 compression error: {}", err)))?;
326
327 output_buf.truncate(start_pos + compressed_size);
329 Ok(())
330 }
331
332 fn decompress(&self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()> {
333 if input_buf.len() < 4 {
336 return Err(Error::internal("LZ4 compressed data too short".to_string()));
337 }
338
339 let uncompressed_size =
341 u32::from_le_bytes([input_buf[0], input_buf[1], input_buf[2], input_buf[3]])
342 as usize;
343
344 let start_pos = output_buf.len();
346
347 output_buf.resize(start_pos + uncompressed_size, 0);
349
350 let decompressed_size =
352 ::lz4::block::decompress_to_buffer(input_buf, None, &mut output_buf[start_pos..])
353 .map_err(|err| Error::internal(format!("LZ4 decompression error: {}", err)))?;
354
355 output_buf.truncate(start_pos + decompressed_size);
357
358 Ok(())
359 }
360
361 fn config(&self) -> CompressionConfig {
362 CompressionConfig {
363 scheme: CompressionScheme::Lz4,
364 level: None,
365 }
366 }
367 }
368}
369
370#[derive(Debug, Default)]
371pub struct NoopBufferCompressor {}
372
373impl BufferCompressor for NoopBufferCompressor {
374 fn compress(&self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()> {
375 output_buf.extend_from_slice(input_buf);
376 Ok(())
377 }
378
379 fn decompress(&self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()> {
380 output_buf.extend_from_slice(input_buf);
381 Ok(())
382 }
383
384 fn config(&self) -> CompressionConfig {
385 CompressionConfig {
386 scheme: CompressionScheme::None,
387 level: None,
388 }
389 }
390}
391
392pub struct GeneralBufferCompressor {}
393
394impl GeneralBufferCompressor {
395 pub fn get_compressor(
396 compression_config: CompressionConfig,
397 ) -> Result<Box<dyn BufferCompressor>> {
398 match compression_config.scheme {
399 CompressionScheme::Fsst => Err(Error::invalid_input_source(
401 "fsst is not usable as a general buffer compressor".into(),
402 )),
403 CompressionScheme::Zstd => {
404 #[cfg(feature = "zstd")]
405 {
406 Ok(Box::new(zstd::ZstdBufferCompressor::new(
407 compression_config.level.unwrap_or(0),
408 )))
409 }
410 #[cfg(not(feature = "zstd"))]
411 {
412 Err(Error::invalid_input_source(
413 "package was not built with zstd support".into(),
414 ))
415 }
416 }
417 CompressionScheme::Lz4 => {
418 #[cfg(feature = "lz4")]
419 {
420 Ok(Box::new(lz4::Lz4BufferCompressor::default()))
421 }
422 #[cfg(not(feature = "lz4"))]
423 {
424 Err(Error::invalid_input_source(
425 "package was not built with lz4 support".into(),
426 ))
427 }
428 }
429 CompressionScheme::None => Ok(Box::new(NoopBufferCompressor {})),
430 }
431 }
432}
433
434#[derive(Debug)]
437pub struct GeneralBlockDecompressor {
438 inner: Box<dyn BlockDecompressor>,
439 compressor: Box<dyn BufferCompressor>,
440}
441
442impl GeneralBlockDecompressor {
443 pub fn try_new(
444 inner: Box<dyn BlockDecompressor>,
445 compression: CompressionConfig,
446 ) -> Result<Self> {
447 let compressor = GeneralBufferCompressor::get_compressor(compression)?;
448 Ok(Self { inner, compressor })
449 }
450}
451
452impl BlockDecompressor for GeneralBlockDecompressor {
453 fn decompress(&self, data: Option<LanceBuffer>, num_values: u64) -> Result<DataBlock> {
454 let data = require_block_payload(data, "General block compression")?;
455 let mut decompressed = Vec::new();
456 self.compressor.decompress(&data, &mut decompressed)?;
457 self.inner
458 .decompress(Some(LanceBuffer::from(decompressed)), num_values)
459 }
460}
461
462#[derive(Debug)]
464pub struct CompressedBufferEncoder {
465 pub(crate) compressor: Box<dyn BufferCompressor>,
466 block_compression: CompressionConfig,
469}
470
471impl Default for CompressedBufferEncoder {
472 fn default() -> Self {
473 #[cfg(feature = "zstd")]
475 let (scheme, level) = (CompressionScheme::Zstd, Some(0));
476 #[cfg(all(feature = "lz4", not(feature = "zstd")))]
477 let (scheme, level) = (CompressionScheme::Lz4, None);
478 #[cfg(not(any(feature = "zstd", feature = "lz4")))]
479 let (scheme, level) = (CompressionScheme::None, None);
480
481 let block_compression = CompressionConfig { scheme, level };
482 let compressor = GeneralBufferCompressor::get_compressor(block_compression).unwrap();
483 Self {
484 compressor,
485 block_compression,
486 }
487 }
488}
489
490impl CompressedBufferEncoder {
491 pub fn try_new(compression_config: CompressionConfig) -> Result<Self> {
492 let compressor = GeneralBufferCompressor::get_compressor(compression_config)?;
493 Ok(Self {
494 compressor,
495 block_compression: compression_config,
496 })
497 }
498
499 pub fn from_scheme(scheme: pb21::CompressionScheme) -> Result<Self> {
500 let scheme = CompressionScheme::try_from(scheme)?;
501 let block_compression = CompressionConfig {
502 scheme,
503 level: Some(0),
504 };
505 Ok(Self {
506 compressor: GeneralBufferCompressor::get_compressor(block_compression)?,
507 block_compression,
508 })
509 }
510}
511
512impl CompressedBufferEncoder {
513 pub fn per_value_compress<T: ArrowNativeType>(
514 &self,
515 data: &[u8],
516 offsets: &[T],
517 compressed: &mut Vec<u8>,
518 ) -> Result<LanceBuffer> {
519 let mut new_offsets: Vec<T> = Vec::with_capacity(offsets.len());
520 new_offsets.push(T::from_usize(0).unwrap());
521
522 for off in offsets.windows(2) {
523 let start = off[0].as_usize();
524 let end = off[1].as_usize();
525 self.compressor.compress(&data[start..end], compressed)?;
526 new_offsets.push(T::from_usize(compressed.len()).unwrap());
527 }
528
529 Ok(LanceBuffer::reinterpret_vec(new_offsets))
530 }
531
532 pub fn per_value_decompress<T: ArrowNativeType>(
533 &self,
534 data: &[u8],
535 offsets: &[T],
536 decompressed: &mut Vec<u8>,
537 ) -> Result<LanceBuffer> {
538 let mut new_offsets: Vec<T> = Vec::with_capacity(offsets.len());
539 new_offsets.push(T::from_usize(0).unwrap());
540
541 for off in offsets.windows(2) {
542 let start = off[0].as_usize();
543 let end = off[1].as_usize();
544 self.compressor
545 .decompress(&data[start..end], decompressed)?;
546 new_offsets.push(T::from_usize(decompressed.len()).unwrap());
547 }
548
549 Ok(LanceBuffer::reinterpret_vec(new_offsets))
550 }
551}
552
553impl PerValueCompressor for CompressedBufferEncoder {
554 fn compress(&self, data: DataBlock) -> Result<(PerValueDataBlock, CompressiveEncoding)> {
555 let data_type = data.name();
556 let data = data.as_variable_width().ok_or(Error::internal(format!(
557 "Attempt to use CompressedBufferEncoder on data of type {}",
558 data_type
559 )))?;
560
561 let data_bytes = &data.data;
562 let mut compressed = Vec::with_capacity(data_bytes.len());
563
564 let new_offsets = match data.bits_per_offset {
565 32 => self.per_value_compress::<u32>(
566 data_bytes,
567 &data.offsets.borrow_to_typed_slice::<u32>(),
568 &mut compressed,
569 )?,
570 64 => self.per_value_compress::<u64>(
571 data_bytes,
572 &data.offsets.borrow_to_typed_slice::<u64>(),
573 &mut compressed,
574 )?,
575 _ => unreachable!(),
576 };
577
578 let compressed = PerValueDataBlock::Variable(VariableWidthBlock {
579 bits_per_offset: data.bits_per_offset,
580 data: LanceBuffer::from(compressed),
581 offsets: new_offsets,
582 num_values: data.num_values,
583 block_info: BlockInfo::new(),
584 });
585
586 let encoding = ProtobufUtils21::wrapped(
589 self.compressor.config(),
590 ProtobufUtils21::variable(
591 ProtobufUtils21::flat(data.bits_per_offset as u64, None),
592 None,
593 ),
594 )?;
595
596 Ok((compressed, encoding))
597 }
598}
599
600impl VariablePerValueDecompressor for CompressedBufferEncoder {
601 fn decompress(&self, data: VariableWidthBlock) -> Result<DataBlock> {
602 let data_bytes = &data.data;
603 let mut decompressed = Vec::with_capacity(data_bytes.len() * 2);
604
605 let new_offsets = match data.bits_per_offset {
606 32 => self.per_value_decompress(
607 data_bytes,
608 &data.offsets.borrow_to_typed_slice::<u32>(),
609 &mut decompressed,
610 )?,
611 64 => self.per_value_decompress(
612 data_bytes,
613 &data.offsets.borrow_to_typed_slice::<u64>(),
614 &mut decompressed,
615 )?,
616 _ => unreachable!(),
617 };
618 Ok(DataBlock::VariableWidth(VariableWidthBlock {
619 bits_per_offset: data.bits_per_offset,
620 data: LanceBuffer::from(decompressed),
621 offsets: new_offsets,
622 num_values: data.num_values,
623 block_info: BlockInfo::new(),
624 }))
625 }
626}
627
628impl BlockCompressor for CompressedBufferEncoder {
629 fn compress(&self, data: DataBlock) -> Result<(Option<LanceBuffer>, CompressiveEncoding)> {
630 let (encoded, inner_encoding) = match data {
631 DataBlock::FixedWidth(fixed_width) => (
632 fixed_width.data,
633 ProtobufUtils21::flat(fixed_width.bits_per_value, None),
634 ),
635 DataBlock::VariableWidth(variable_width) => {
636 let encoder = VariableEncoder::default();
638 let (payload, encoding) =
639 BlockCompressor::compress(&encoder, DataBlock::VariableWidth(variable_width))?;
640 (
641 payload.ok_or_else(|| {
642 Error::internal(
643 "VariableEncoder returned no payload for general compression"
644 .to_string(),
645 )
646 })?,
647 encoding,
648 )
649 }
650 _ => {
651 return Err(Error::invalid_input_source(
652 "Unsupported data block type".into(),
653 ));
654 }
655 };
656
657 let mut compressed = Vec::new();
658 self.compressor.compress(&encoded, &mut compressed)?;
659 Ok((
660 Some(LanceBuffer::from(compressed)),
661 ProtobufUtils21::wrapped(self.block_compression, inner_encoding)?,
662 ))
663 }
664}
665
666impl BlockDecompressor for CompressedBufferEncoder {
667 fn decompress(&self, data: Option<LanceBuffer>, num_values: u64) -> Result<DataBlock> {
668 let data = require_block_payload(data, "Compressed variable block")?;
669 let mut decompressed = Vec::new();
670 self.compressor.decompress(&data, &mut decompressed)?;
671
672 let inner_decoder = BinaryBlockDecompressor::default();
674 inner_decoder.decompress(Some(LanceBuffer::from(decompressed)), num_values)
675 }
676}
677
678#[cfg(test)]
679mod tests {
680 use super::*;
681 use std::str::FromStr;
682
683 use crate::encodings::physical::block::zstd::ZstdBufferCompressor;
684
685 #[test]
686 fn test_compression_scheme_from_str() {
687 assert_eq!(
688 CompressionScheme::from_str("none").unwrap(),
689 CompressionScheme::None
690 );
691 assert_eq!(
692 CompressionScheme::from_str("zstd").unwrap(),
693 CompressionScheme::Zstd
694 );
695 }
696
697 #[test]
698 fn test_compression_scheme_from_str_invalid() {
699 assert!(CompressionScheme::from_str("invalid").is_err());
700 }
701
702 #[cfg(feature = "zstd")]
703 mod zstd {
704 use std::io::Write;
705
706 use super::*;
707
708 #[test]
709 fn test_compress_zstd_with_length_prefixed() {
710 let compressor = ZstdBufferCompressor::new(0);
711 let input_data = b"Hello, world!";
712 let mut compressed_data = Vec::new();
713
714 compressor
715 .compress(input_data, &mut compressed_data)
716 .unwrap();
717 let mut decompressed_data = Vec::new();
718 compressor
719 .decompress(&compressed_data, &mut decompressed_data)
720 .unwrap();
721 assert_eq!(input_data, decompressed_data.as_slice());
722 }
723
724 #[test]
725 fn test_zstd_compress_decompress_multiple_times() {
726 let compressor = ZstdBufferCompressor::new(0);
727 let (input_data_1, input_data_2) = (b"Hello ", b"World");
728 let mut compressed_data = Vec::new();
729
730 compressor
731 .compress(input_data_1, &mut compressed_data)
732 .unwrap();
733 let compressed_length_1 = compressed_data.len();
734
735 compressor
736 .compress(input_data_2, &mut compressed_data)
737 .unwrap();
738
739 let mut decompressed_data = Vec::new();
740 compressor
741 .decompress(
742 &compressed_data[..compressed_length_1],
743 &mut decompressed_data,
744 )
745 .unwrap();
746
747 compressor
748 .decompress(
749 &compressed_data[compressed_length_1..],
750 &mut decompressed_data,
751 )
752 .unwrap();
753
754 assert_eq!(
756 decompressed_data.len(),
757 input_data_1.len() + input_data_2.len()
758 );
759 assert_eq!(
760 &decompressed_data[..input_data_1.len()],
761 input_data_1,
762 "First part of decompressed data should match input_1"
763 );
764 assert_eq!(
765 &decompressed_data[input_data_1.len()..],
766 input_data_2,
767 "Second part of decompressed data should match input_2"
768 );
769 }
770
771 #[test]
772 fn test_compress_zstd_raw_stream_format_and_decompress_with_length_prefixed() {
773 let compressor = ZstdBufferCompressor::new(0);
774 let input_data = b"Hello, world!";
775 let mut compressed_data = Vec::new();
776
777 let mut encoder = ::zstd::Encoder::new(&mut compressed_data, 0).unwrap();
779 encoder.write_all(input_data).unwrap();
780 encoder.finish().expect("failed to encode data with zstd");
781
782 let mut decompressed_data = Vec::new();
784 compressor
785 .decompress(&compressed_data, &mut decompressed_data)
786 .unwrap();
787 assert_eq!(input_data, decompressed_data.as_slice());
788 }
789 }
790
791 #[cfg(feature = "lz4")]
792 mod lz4 {
793 use std::{collections::HashMap, sync::Arc};
794
795 use arrow_schema::{DataType, Field};
796 use lance_datagen::array::{binary_prefix_plus_counter, utf8_prefix_plus_counter};
797
798 use super::*;
799
800 use crate::constants::DICT_SIZE_RATIO_META_KEY;
801 use crate::{
802 constants::{
803 COMPRESSION_META_KEY, DICT_DIVISOR_META_KEY, STRUCTURAL_ENCODING_FULLZIP,
804 STRUCTURAL_ENCODING_META_KEY,
805 },
806 encodings::physical::block::lz4::Lz4BufferCompressor,
807 testing::{
808 FnArrayGeneratorProvider, TestCases, TestEncoding,
809 check_round_trip_encoding_generated,
810 },
811 };
812
813 #[test]
814 fn test_lz4_compress_decompress() {
815 let compressor = Lz4BufferCompressor::default();
816 let input_data = b"Hello, world!";
817 let mut compressed_data = Vec::new();
818
819 compressor
820 .compress(input_data, &mut compressed_data)
821 .unwrap();
822 let mut decompressed_data = Vec::new();
823 compressor
824 .decompress(&compressed_data, &mut decompressed_data)
825 .unwrap();
826 assert_eq!(input_data, decompressed_data.as_slice());
827 }
828
829 #[rstest::rstest]
830 #[test_log::test(tokio::test)]
831 async fn test_lz4_compress_round_trip(
832 #[values(
833 DataType::Utf8,
834 DataType::LargeUtf8,
835 DataType::Binary,
836 DataType::LargeBinary
837 )]
838 data_type: DataType,
839 #[values(
840 TestEncoding::StructuralU16,
841 TestEncoding::StructuralU32,
842 TestEncoding::StructuralSparse
843 )]
844 encoding: TestEncoding,
845 #[values(false, true)] use_slicing: bool,
846 ) {
847 let field = Field::new("", data_type.clone(), false);
848 let mut field_meta = HashMap::new();
849 field_meta.insert(COMPRESSION_META_KEY.to_string(), "lz4".to_string());
850 field_meta.insert(DICT_DIVISOR_META_KEY.to_string(), "100000".to_string());
853 field_meta.insert(DICT_SIZE_RATIO_META_KEY.to_string(), "0.0001".to_string());
854 field_meta.insert(
856 STRUCTURAL_ENCODING_META_KEY.to_string(),
857 STRUCTURAL_ENCODING_FULLZIP.to_string(),
858 );
859 let field = field.with_metadata(field_meta);
860 let test_cases = TestCases::basic()
861 .with_page_sizes(vec![1024 * 1024])
863 .with_expected_encoding("zstd")
864 .with_encoding(encoding)
865 .with_slicing_modes([use_slicing]);
866
867 let datagen = Box::new(FnArrayGeneratorProvider::new(move || match data_type {
870 DataType::Utf8 => utf8_prefix_plus_counter("compressme", false),
871 DataType::Binary => {
872 binary_prefix_plus_counter(Arc::from(b"compressme".to_owned()), false)
873 }
874 DataType::LargeUtf8 => utf8_prefix_plus_counter("compressme", true),
875 DataType::LargeBinary => {
876 binary_prefix_plus_counter(Arc::from(b"compressme".to_owned()), true)
877 }
878 _ => panic!("Unsupported data type: {:?}", data_type),
879 }));
880
881 check_round_trip_encoding_generated(field, datagen, test_cases).await;
882 }
883 }
884}