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 EncodingUtils,
33 metadata::{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 metadata::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<metadata::CompressionScheme> for CompressionScheme {
98 type Error = Error;
99
100 fn try_from(scheme: metadata::CompressionScheme) -> Result<Self> {
101 match scheme {
102 metadata::CompressionScheme::CompressionAlgorithmLz4 => Ok(Self::Lz4),
103 metadata::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
145 fn decompress_exact(
146 &self,
147 input_buf: &[u8],
148 output_buf: &mut Vec<u8>,
149 expected_bytes: usize,
150 ) -> Result<()> {
151 self.decompress(input_buf, output_buf)?;
152 if output_buf.len() != expected_bytes {
153 return Err(Error::invalid_input(format!(
154 "General compression produced {} bytes, expected {expected_bytes}",
155 output_buf.len()
156 )));
157 }
158 Ok(())
159 }
160
161 fn config(&self) -> CompressionConfig;
162}
163
164#[cfg(feature = "zstd")]
165mod zstd {
166 use std::io::{Cursor, Write};
167 use std::sync::{Mutex, OnceLock};
168
169 use super::*;
170
171 use ::zstd::bulk::{Compressor, Decompressor};
172 use ::zstd::stream::copy_decode;
173 use std::cell::RefCell;
174
175 thread_local! {
176 static DECOMPRESSOR: RefCell<Option<Decompressor<'static>>> = const { RefCell::new(None) };
180 }
181
182 fn append_cursor(buf: &mut Vec<u8>) -> Cursor<&mut Vec<u8>> {
185 let start = buf.len() as u64;
186 let mut cursor = Cursor::new(buf);
187 cursor.set_position(start);
188 cursor
189 }
190
191 fn decompress_frame_into(
195 compressed: &[u8],
196 output_buf: &mut Vec<u8>,
197 ) -> std::io::Result<usize> {
198 DECOMPRESSOR.with(|slot| {
199 let mut slot = slot.borrow_mut();
200 if slot.is_none() {
201 *slot = Some(Decompressor::new()?);
202 }
203 slot.as_mut()
204 .expect("initialized above")
205 .decompress_to_buffer(compressed, &mut append_cursor(output_buf))
206 })
207 }
208
209 pub struct ZstdBufferCompressor {
221 compression_level: i32,
222 compressor: OnceLock<std::result::Result<Mutex<Compressor<'static>>, String>>,
223 }
224
225 impl std::fmt::Debug for ZstdBufferCompressor {
226 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
227 f.debug_struct("ZstdBufferCompressor")
228 .field("compression_level", &self.compression_level)
229 .finish()
230 }
231 }
232
233 impl ZstdBufferCompressor {
234 pub fn new(compression_level: i32) -> Self {
235 Self {
236 compression_level,
237 compressor: OnceLock::new(),
238 }
239 }
240
241 fn get_compressor(&self) -> Result<&Mutex<Compressor<'static>>> {
242 self.compressor
243 .get_or_init(|| {
244 Compressor::new(self.compression_level)
245 .map(Mutex::new)
246 .map_err(|e| e.to_string())
247 })
248 .as_ref()
249 .map_err(|e| Error::internal(format!("Failed to create zstd compressor: {}", e)))
250 }
251
252 fn is_raw_stream_format(&self, input_buf: &[u8]) -> bool {
254 if input_buf.len() < 8 {
255 return true; }
257 let mut magic_buf = [0u8; 4];
259 magic_buf.copy_from_slice(&input_buf[..4]);
260 let magic = u32::from_le_bytes(magic_buf);
261
262 const ZSTD_MAGIC_NUMBER: u32 = 0xFD2FB528;
264 if magic == ZSTD_MAGIC_NUMBER {
265 const FHD_BYTE_INDEX: usize = 4;
269 let fhd_byte = input_buf[FHD_BYTE_INDEX];
270 const FHD_RESERVED_BIT_MASK: u8 = 0b0001_0000;
271 let reserved_bit = fhd_byte & FHD_RESERVED_BIT_MASK;
272
273 if reserved_bit != 0 {
274 false
278 } else {
279 true
282 }
283 } else {
284 false
286 }
287 }
288
289 fn decompress_length_prefixed_zstd(
290 &self,
291 input_buf: &[u8],
292 output_buf: &mut Vec<u8>,
293 ) -> Result<()> {
294 const LENGTH_PREFIX_SIZE: usize = 8;
295 let mut len_buf = [0u8; LENGTH_PREFIX_SIZE];
296 len_buf.copy_from_slice(&input_buf[..LENGTH_PREFIX_SIZE]);
297
298 let uncompressed_len = u64::from_le_bytes(len_buf) as usize;
299
300 let start = output_buf.len();
301 output_buf.reserve(uncompressed_len);
302 let compressed_data = &input_buf[LENGTH_PREFIX_SIZE..];
303 let written = decompress_frame_into(compressed_data, output_buf)?;
304 if written != uncompressed_len {
305 output_buf.truncate(start);
309 return Err(Error::internal(format!(
310 "Zstd decompressed {written} bytes, expected {uncompressed_len}"
311 )));
312 }
313 Ok(())
314 }
315 }
316
317 impl BufferCompressor for ZstdBufferCompressor {
318 fn compress(&self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()> {
319 output_buf.write_all(&(input_buf.len() as u64).to_le_bytes())?;
320
321 output_buf.reserve(::zstd::zstd_safe::compress_bound(input_buf.len()));
322 self.get_compressor()?
323 .lock()
324 .unwrap()
325 .compress_to_buffer(input_buf, &mut append_cursor(output_buf))
326 .map_err(|e| Error::internal(format!("Zstd compression error: {}", e)))?;
327 Ok(())
328 }
329
330 fn decompress(&self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()> {
331 if input_buf.is_empty() {
332 return Ok(());
333 }
334
335 let is_raw_stream_format = self.is_raw_stream_format(input_buf);
336 if is_raw_stream_format {
337 copy_decode(Cursor::new(input_buf), output_buf)?;
338 } else {
339 self.decompress_length_prefixed_zstd(input_buf, output_buf)?;
340 }
341
342 Ok(())
343 }
344
345 fn decompress_exact(
346 &self,
347 input_buf: &[u8],
348 output_buf: &mut Vec<u8>,
349 expected_bytes: usize,
350 ) -> Result<()> {
351 if input_buf.is_empty() {
352 if expected_bytes == 0 {
353 return Ok(());
354 }
355 return Err(Error::invalid_input(format!(
356 "Zstd payload is empty, expected {expected_bytes} output bytes"
357 )));
358 }
359 let compressed = if self.is_raw_stream_format(input_buf) {
360 input_buf
361 } else {
362 let declared = u64::from_le_bytes(input_buf[..8].try_into().map_err(|_| {
363 Error::invalid_input("Zstd payload is shorter than its 8-byte length prefix")
364 })?);
365 let declared = usize::try_from(declared).map_err(|_| {
366 Error::invalid_input("Zstd declared output length does not fit usize")
367 })?;
368 if declared != expected_bytes {
369 return Err(Error::invalid_input(format!(
370 "Zstd declares {declared} output bytes, expected {expected_bytes}"
371 )));
372 }
373 &input_buf[8..]
374 };
375 let start = output_buf.len();
376 output_buf
378 .try_reserve_exact(expected_bytes)
379 .map_err(|error| {
380 Error::invalid_input(format!(
381 "Zstd could not reserve {expected_bytes} output bytes: {error}"
382 ))
383 })?;
384 let decoded = decompress_frame_into(compressed, output_buf);
385 let decoded = match decoded {
386 Ok(decoded) => decoded,
387 Err(error) => {
388 output_buf.truncate(start);
389 return Err(error.into());
390 }
391 };
392 if decoded != expected_bytes {
393 output_buf.truncate(start);
394 return Err(Error::invalid_input(format!(
395 "Zstd produced {decoded} bytes, expected {expected_bytes}"
396 )));
397 }
398 Ok(())
399 }
400
401 fn config(&self) -> CompressionConfig {
402 CompressionConfig {
403 scheme: CompressionScheme::Zstd,
404 level: Some(self.compression_level),
405 }
406 }
407 }
408}
409
410#[cfg(feature = "lz4")]
411mod lz4 {
412 use super::*;
413
414 #[derive(Debug, Default)]
415 pub struct Lz4BufferCompressor {}
416
417 impl BufferCompressor for Lz4BufferCompressor {
418 fn compress(&self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()> {
419 let start_pos = output_buf.len();
421
422 let max_size = ::lz4::block::compress_bound(input_buf.len())?;
424 output_buf.resize(start_pos + max_size + 4, 0);
426
427 let compressed_size = ::lz4::block::compress_to_buffer(
428 input_buf,
429 None,
430 true,
431 &mut output_buf[start_pos..],
432 )
433 .map_err(|err| Error::internal(format!("LZ4 compression error: {}", err)))?;
434
435 output_buf.truncate(start_pos + compressed_size);
437 Ok(())
438 }
439
440 fn decompress(&self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()> {
441 if input_buf.len() < 4 {
444 return Err(Error::invalid_input(
445 "LZ4 buffer is missing its length prefix",
446 ));
447 }
448
449 let uncompressed_size =
451 u32::from_le_bytes([input_buf[0], input_buf[1], input_buf[2], input_buf[3]])
452 as usize;
453
454 let start_pos = output_buf.len();
456
457 output_buf.resize(start_pos + uncompressed_size, 0);
459
460 let decompressed_size =
462 ::lz4::block::decompress_to_buffer(input_buf, None, &mut output_buf[start_pos..])
463 .map_err(|err| Error::internal(format!("LZ4 decompression error: {}", err)))?;
464
465 output_buf.truncate(start_pos + decompressed_size);
467
468 Ok(())
469 }
470
471 fn decompress_exact(
472 &self,
473 input_buf: &[u8],
474 output_buf: &mut Vec<u8>,
475 expected_bytes: usize,
476 ) -> Result<()> {
477 let declared = input_buf.get(..4).ok_or_else(|| {
478 Error::invalid_input("LZ4 payload is shorter than its 4-byte length prefix")
479 })?;
480 let declared =
481 u32::from_le_bytes(declared.try_into().map_err(|_| {
482 Error::invalid_input("LZ4 payload has an invalid length prefix")
483 })?) as usize;
484 if declared != expected_bytes {
485 return Err(Error::invalid_input(format!(
486 "LZ4 declares {declared} output bytes, expected {expected_bytes}"
487 )));
488 }
489 let start = output_buf.len();
490 let end = start
491 .checked_add(expected_bytes)
492 .ok_or_else(|| Error::invalid_input("LZ4 output buffer length overflows usize"))?;
493 output_buf
494 .try_reserve_exact(expected_bytes)
495 .map_err(|error| {
496 Error::invalid_input(format!(
497 "LZ4 could not reserve {expected_bytes} output bytes: {error}"
498 ))
499 })?;
500 output_buf.resize(end, 0);
501 let decoded =
502 ::lz4::block::decompress_to_buffer(input_buf, None, &mut output_buf[start..]);
503 let decoded = match decoded {
504 Ok(decoded) => decoded,
505 Err(error) => {
506 output_buf.truncate(start);
507 return Err(Error::invalid_input(format!(
508 "LZ4 decompression error: {error}"
509 )));
510 }
511 };
512 if decoded != expected_bytes {
513 output_buf.truncate(start);
514 return Err(Error::invalid_input(format!(
515 "LZ4 produced {decoded} bytes, expected {expected_bytes}"
516 )));
517 }
518 Ok(())
519 }
520
521 fn config(&self) -> CompressionConfig {
522 CompressionConfig {
523 scheme: CompressionScheme::Lz4,
524 level: None,
525 }
526 }
527 }
528}
529
530#[derive(Debug, Default)]
531pub struct NoopBufferCompressor {}
532
533impl BufferCompressor for NoopBufferCompressor {
534 fn compress(&self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()> {
535 output_buf.extend_from_slice(input_buf);
536 Ok(())
537 }
538
539 fn decompress(&self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()> {
540 output_buf.extend_from_slice(input_buf);
541 Ok(())
542 }
543
544 fn decompress_exact(
545 &self,
546 input_buf: &[u8],
547 output_buf: &mut Vec<u8>,
548 expected_bytes: usize,
549 ) -> Result<()> {
550 if input_buf.len() != expected_bytes {
551 return Err(Error::invalid_input(format!(
552 "Uncompressed payload has {} bytes, expected {expected_bytes}",
553 input_buf.len()
554 )));
555 }
556 output_buf.extend_from_slice(input_buf);
557 Ok(())
558 }
559
560 fn config(&self) -> CompressionConfig {
561 CompressionConfig {
562 scheme: CompressionScheme::None,
563 level: None,
564 }
565 }
566}
567
568pub struct GeneralBufferCompressor {}
569
570impl GeneralBufferCompressor {
571 pub fn get_compressor(
572 compression_config: CompressionConfig,
573 ) -> Result<Box<dyn BufferCompressor>> {
574 match compression_config.scheme {
575 CompressionScheme::Fsst => Err(Error::invalid_input_source(
577 "fsst is not usable as a general buffer compressor".into(),
578 )),
579 CompressionScheme::Zstd => {
580 #[cfg(feature = "zstd")]
581 {
582 Ok(Box::new(zstd::ZstdBufferCompressor::new(
583 compression_config.level.unwrap_or(0),
584 )))
585 }
586 #[cfg(not(feature = "zstd"))]
587 {
588 Err(Error::invalid_input_source(
589 "package was not built with zstd support".into(),
590 ))
591 }
592 }
593 CompressionScheme::Lz4 => {
594 #[cfg(feature = "lz4")]
595 {
596 Ok(Box::new(lz4::Lz4BufferCompressor::default()))
597 }
598 #[cfg(not(feature = "lz4"))]
599 {
600 Err(Error::invalid_input_source(
601 "package was not built with lz4 support".into(),
602 ))
603 }
604 }
605 CompressionScheme::None => Ok(Box::new(NoopBufferCompressor {})),
606 }
607 }
608}
609
610#[derive(Debug)]
613pub struct GeneralBlockDecompressor {
614 inner: Box<dyn BlockDecompressor>,
615 compressor: Box<dyn BufferCompressor>,
616}
617
618impl GeneralBlockDecompressor {
619 pub fn try_new(
620 inner: Box<dyn BlockDecompressor>,
621 compression: CompressionConfig,
622 ) -> Result<Self> {
623 let compressor = GeneralBufferCompressor::get_compressor(compression)?;
624 Ok(Self { inner, compressor })
625 }
626}
627
628impl BlockDecompressor for GeneralBlockDecompressor {
629 fn decompress(&self, data: Option<LanceBuffer>, num_values: u64) -> Result<DataBlock> {
630 let data = require_block_payload(data, "General block compression")?;
631 let mut decompressed = Vec::new();
632 self.compressor.decompress(&data, &mut decompressed)?;
633 self.inner
634 .decompress(Some(LanceBuffer::from(decompressed)), num_values)
635 }
636}
637
638#[derive(Debug)]
640pub struct CompressedBufferEncoder {
641 pub(crate) compressor: Box<dyn BufferCompressor>,
642 block_compression: CompressionConfig,
645}
646
647impl Default for CompressedBufferEncoder {
648 fn default() -> Self {
649 #[cfg(feature = "zstd")]
651 let (scheme, level) = (CompressionScheme::Zstd, Some(0));
652 #[cfg(all(feature = "lz4", not(feature = "zstd")))]
653 let (scheme, level) = (CompressionScheme::Lz4, None);
654 #[cfg(not(any(feature = "zstd", feature = "lz4")))]
655 let (scheme, level) = (CompressionScheme::None, None);
656
657 let block_compression = CompressionConfig { scheme, level };
658 let compressor = GeneralBufferCompressor::get_compressor(block_compression).unwrap();
659 Self {
660 compressor,
661 block_compression,
662 }
663 }
664}
665
666impl CompressedBufferEncoder {
667 pub fn try_new(compression_config: CompressionConfig) -> Result<Self> {
668 let compressor = GeneralBufferCompressor::get_compressor(compression_config)?;
669 Ok(Self {
670 compressor,
671 block_compression: compression_config,
672 })
673 }
674
675 pub fn from_scheme(scheme: metadata::CompressionScheme) -> Result<Self> {
676 let scheme = CompressionScheme::try_from(scheme)?;
677 let block_compression = CompressionConfig {
678 scheme,
679 level: Some(0),
680 };
681 Ok(Self {
682 compressor: GeneralBufferCompressor::get_compressor(block_compression)?,
683 block_compression,
684 })
685 }
686}
687
688impl CompressedBufferEncoder {
689 pub fn per_value_compress<T: ArrowNativeType>(
690 &self,
691 data: &[u8],
692 offsets: &[T],
693 compressed: &mut Vec<u8>,
694 ) -> Result<LanceBuffer> {
695 let mut new_offsets: Vec<T> = Vec::with_capacity(offsets.len());
696 new_offsets.push(T::from_usize(0).unwrap());
697
698 for off in offsets.windows(2) {
699 let start = off[0].as_usize();
700 let end = off[1].as_usize();
701 self.compressor.compress(&data[start..end], compressed)?;
702 new_offsets.push(T::from_usize(compressed.len()).unwrap());
703 }
704
705 Ok(LanceBuffer::reinterpret_vec(new_offsets))
706 }
707
708 pub fn per_value_decompress<T: ArrowNativeType>(
709 &self,
710 data: &[u8],
711 offsets: &[T],
712 decompressed: &mut Vec<u8>,
713 ) -> Result<LanceBuffer> {
714 let mut new_offsets: Vec<T> = Vec::with_capacity(offsets.len());
715 new_offsets.push(T::from_usize(0).unwrap());
716
717 for off in offsets.windows(2) {
718 let start = off[0].as_usize();
719 let end = off[1].as_usize();
720 self.compressor
721 .decompress(&data[start..end], decompressed)?;
722 new_offsets.push(T::from_usize(decompressed.len()).unwrap());
723 }
724
725 Ok(LanceBuffer::reinterpret_vec(new_offsets))
726 }
727}
728
729impl PerValueCompressor for CompressedBufferEncoder {
730 fn compress(&self, data: DataBlock) -> Result<(PerValueDataBlock, CompressiveEncoding)> {
731 let data_type = data.name();
732 let data = data.as_variable_width().ok_or(Error::internal(format!(
733 "Attempt to use CompressedBufferEncoder on data of type {}",
734 data_type
735 )))?;
736
737 let data_bytes = &data.data;
738 let mut compressed = Vec::with_capacity(data_bytes.len());
739
740 let new_offsets = match data.bits_per_offset {
741 32 => self.per_value_compress::<u32>(
742 data_bytes,
743 &data.offsets.borrow_to_typed_slice::<u32>(),
744 &mut compressed,
745 )?,
746 64 => self.per_value_compress::<u64>(
747 data_bytes,
748 &data.offsets.borrow_to_typed_slice::<u64>(),
749 &mut compressed,
750 )?,
751 _ => unreachable!(),
752 };
753
754 let compressed = PerValueDataBlock::Variable(VariableWidthBlock {
755 bits_per_offset: data.bits_per_offset,
756 data: LanceBuffer::from(compressed),
757 offsets: new_offsets,
758 num_values: data.num_values,
759 block_info: BlockInfo::new(),
760 });
761
762 let encoding = EncodingUtils::wrapped(
765 self.compressor.config(),
766 EncodingUtils::variable(EncodingUtils::flat(data.bits_per_offset as u64, None), None),
767 )?;
768
769 Ok((compressed, encoding))
770 }
771}
772
773impl VariablePerValueDecompressor for CompressedBufferEncoder {
774 fn decompress(&self, data: VariableWidthBlock) -> Result<DataBlock> {
775 let data_bytes = &data.data;
776 let mut decompressed = Vec::with_capacity(data_bytes.len() * 2);
777
778 let new_offsets = match data.bits_per_offset {
779 32 => self.per_value_decompress(
780 data_bytes,
781 &data.offsets.borrow_to_typed_slice::<u32>(),
782 &mut decompressed,
783 )?,
784 64 => self.per_value_decompress(
785 data_bytes,
786 &data.offsets.borrow_to_typed_slice::<u64>(),
787 &mut decompressed,
788 )?,
789 _ => unreachable!(),
790 };
791 Ok(DataBlock::VariableWidth(VariableWidthBlock {
792 bits_per_offset: data.bits_per_offset,
793 data: LanceBuffer::from(decompressed),
794 offsets: new_offsets,
795 num_values: data.num_values,
796 block_info: BlockInfo::new(),
797 }))
798 }
799}
800
801impl BlockCompressor for CompressedBufferEncoder {
802 fn compress(&self, data: DataBlock) -> Result<(Option<LanceBuffer>, CompressiveEncoding)> {
803 let (encoded, inner_encoding) = match data {
804 DataBlock::FixedWidth(fixed_width) => (
805 fixed_width.data,
806 EncodingUtils::flat(fixed_width.bits_per_value, None),
807 ),
808 DataBlock::VariableWidth(variable_width) => {
809 let encoder = VariableEncoder::default();
811 let (payload, encoding) =
812 BlockCompressor::compress(&encoder, DataBlock::VariableWidth(variable_width))?;
813 (
814 payload.ok_or_else(|| {
815 Error::internal(
816 "VariableEncoder returned no payload for general compression"
817 .to_string(),
818 )
819 })?,
820 encoding,
821 )
822 }
823 _ => {
824 return Err(Error::invalid_input_source(
825 "Unsupported data block type".into(),
826 ));
827 }
828 };
829
830 let mut compressed = Vec::new();
831 self.compressor.compress(&encoded, &mut compressed)?;
832 Ok((
833 Some(LanceBuffer::from(compressed)),
834 EncodingUtils::wrapped(self.block_compression, inner_encoding)?,
835 ))
836 }
837}
838
839impl BlockDecompressor for CompressedBufferEncoder {
840 fn decompress(&self, data: Option<LanceBuffer>, num_values: u64) -> Result<DataBlock> {
841 let data = require_block_payload(data, "Compressed variable block")?;
842 let mut decompressed = Vec::new();
843 self.compressor.decompress(&data, &mut decompressed)?;
844
845 let inner_decoder = BinaryBlockDecompressor::default();
847 inner_decoder.decompress(Some(LanceBuffer::from(decompressed)), num_values)
848 }
849}
850
851#[cfg(test)]
852mod tests {
853 use super::*;
854 use std::str::FromStr;
855
856 use crate::encodings::physical::block::zstd::ZstdBufferCompressor;
857
858 #[test]
859 fn test_compression_scheme_from_str() {
860 assert_eq!(
861 CompressionScheme::from_str("none").unwrap(),
862 CompressionScheme::None
863 );
864 assert_eq!(
865 CompressionScheme::from_str("zstd").unwrap(),
866 CompressionScheme::Zstd
867 );
868 }
869
870 #[test]
871 fn test_compression_scheme_from_str_invalid() {
872 assert!(CompressionScheme::from_str("invalid").is_err());
873 }
874
875 #[cfg(feature = "zstd")]
876 mod zstd {
877 use std::io::Write;
878 use std::sync::Arc;
879
880 use super::*;
881
882 #[test]
883 fn test_compress_zstd_with_length_prefixed() {
884 let compressor = ZstdBufferCompressor::new(0);
885 let input_data = b"Hello, world!";
886 let mut compressed_data = Vec::new();
887
888 compressor
889 .compress(input_data, &mut compressed_data)
890 .unwrap();
891 let mut decompressed_data = Vec::new();
892 compressor
893 .decompress(&compressed_data, &mut decompressed_data)
894 .unwrap();
895 assert_eq!(input_data, decompressed_data.as_slice());
896 }
897
898 fn pseudo_random_bytes(seed: u64, len: usize) -> Vec<u8> {
901 let mut state = seed | 1;
902 (0..len)
903 .map(|_| {
904 state ^= state << 13;
905 state ^= state >> 7;
906 state ^= state << 17;
907 state as u8
908 })
909 .collect()
910 }
911
912 fn zstd_test_inputs() -> Vec<Vec<u8>> {
915 let mut inputs = Vec::new();
916 for (i, &size) in [0usize, 1, 7, 64, 4095, 65_536, 1 << 20].iter().enumerate() {
917 inputs.push(pseudo_random_bytes(i as u64 + 1, size));
918 inputs.push((0..size).map(|j| b"lance zstd "[j % 11]).collect());
919 inputs.push(vec![0u8; size]);
920 }
921 inputs
922 }
923
924 fn reference_frame(input: &[u8], level: i32) -> Vec<u8> {
927 let mut frame = (input.len() as u64).to_le_bytes().to_vec();
928 frame.extend(::zstd::bulk::compress(input, level).unwrap());
929 frame
930 }
931
932 #[test]
938 fn test_zstd_append_into_spare_capacity_round_trips() {
939 for level in [0, 1, 3, 9] {
940 let compressor = ZstdBufferCompressor::new(level);
941 for input in zstd_test_inputs() {
942 let prefix = b"existing-prefix".to_vec();
943 let mut compressed = Vec::with_capacity(prefix.len() + 3);
944 compressed.extend_from_slice(&prefix);
945 compressor.compress(&input, &mut compressed).unwrap();
946 assert_eq!(&compressed[..prefix.len()], &prefix[..]);
947
948 let mut decompressed = Vec::with_capacity(4);
949 decompressed.extend_from_slice(b"xy");
950 compressor
951 .decompress(&compressed[prefix.len()..], &mut decompressed)
952 .unwrap();
953 assert_eq!(&decompressed[..2], b"xy");
954 assert_eq!(&decompressed[2..], &input[..]);
955 }
956 }
957 }
958
959 #[test]
963 fn test_zstd_compress_output_matches_reference_frame() {
964 for level in [0, 1, 3, 9, 19] {
965 let compressor = ZstdBufferCompressor::new(level);
966 for input in zstd_test_inputs() {
967 let mut compressed = Vec::new();
968 compressor.compress(&input, &mut compressed).unwrap();
969 assert_eq!(
970 compressed,
971 reference_frame(&input, level),
972 "level {level}, {} input bytes",
973 input.len()
974 );
975 }
976 }
977 }
978
979 #[test]
982 fn test_zstd_interoperates_with_plain_zstd() {
983 let compressor = ZstdBufferCompressor::new(3);
984 for input in zstd_test_inputs() {
985 let mut decompressed = Vec::new();
986 compressor
987 .decompress(&reference_frame(&input, 3), &mut decompressed)
988 .unwrap();
989 assert_eq!(decompressed, input);
990
991 let mut compressed = Vec::new();
992 compressor.compress(&input, &mut compressed).unwrap();
993 let plain = ::zstd::bulk::decompress(&compressed[8..], input.len()).unwrap();
994 assert_eq!(plain, input);
995 }
996 }
997
998 #[test]
1002 fn test_zstd_decompress_length_mismatch_errors_and_preserves_buffer() {
1003 let compressor = ZstdBufferCompressor::new(0);
1004 let input = pseudo_random_bytes(7, 10_000);
1005 let frame = reference_frame(&input, 3);
1006 for declared in [0u64, 1, 9_999, 10_001, 20_000] {
1007 let mut bad = frame.clone();
1008 bad[..8].copy_from_slice(&declared.to_le_bytes());
1009 for spare in [0usize, 64 * 1024] {
1010 let mut output = Vec::with_capacity(4 + spare);
1011 output.extend_from_slice(b"keep");
1012 assert!(
1013 compressor.decompress(&bad, &mut output).is_err(),
1014 "declared {declared} bytes for a {}-byte frame must fail",
1015 input.len()
1016 );
1017 assert_eq!(output, b"keep", "declared {declared}, spare {spare}");
1018 }
1019 }
1020 }
1021
1022 #[test]
1024 fn test_zstd_decompress_corrupt_frame_errors_and_preserves_buffer() {
1025 let compressor = ZstdBufferCompressor::new(0);
1026 let input = pseudo_random_bytes(11, 50_000);
1027 let frame = reference_frame(&input, 3);
1028 let mut corrupted = frame.clone();
1029 corrupted[8..12].copy_from_slice(&[0, 0, 0, 0]);
1030 let truncated = frame[..frame.len() / 2].to_vec();
1031 for bad in [corrupted, truncated] {
1032 let mut output = b"keep".to_vec();
1033 assert!(compressor.decompress(&bad, &mut output).is_err());
1034 assert_eq!(output, b"keep");
1035 }
1036 }
1037
1038 #[test]
1042 fn test_zstd_decompressor_context_reuse_on_one_thread() {
1043 let compressor = ZstdBufferCompressor::new(0);
1044 let big = pseudo_random_bytes(3, 1 << 20);
1045 let tiny = b"x".to_vec();
1046 let empty = Vec::new();
1047 let mut corrupt = reference_frame(&big, 3);
1048 corrupt[8..12].copy_from_slice(&[0, 0, 0, 0]);
1049 for input in [&big, &tiny, &empty, &big, &tiny] {
1050 let mut scratch = Vec::new();
1051 assert!(compressor.decompress(&corrupt, &mut scratch).is_err());
1052 let mut output = Vec::new();
1053 compressor
1054 .decompress(&reference_frame(input, 3), &mut output)
1055 .unwrap();
1056 assert_eq!(&output, input);
1057 }
1058 }
1059
1060 #[test]
1063 fn test_zstd_decompress_concurrently_from_many_threads() {
1064 let compressor = Arc::new(ZstdBufferCompressor::new(0));
1065 let handles: Vec<_> = (0..8u64)
1066 .map(|thread| {
1067 let compressor = Arc::clone(&compressor);
1068 std::thread::spawn(move || {
1069 for i in 0..64u64 {
1070 let len = ((thread * 64 + i) * 7919 % 200_000) as usize;
1071 let input = pseudo_random_bytes(thread * 1000 + i, len);
1072 let mut compressed = Vec::new();
1073 compressor.compress(&input, &mut compressed).unwrap();
1074 let mut output = Vec::new();
1075 compressor.decompress(&compressed, &mut output).unwrap();
1076 assert_eq!(output, input);
1077 }
1078 })
1079 })
1080 .collect();
1081 for handle in handles {
1082 handle.join().unwrap();
1083 }
1084 }
1085
1086 #[test]
1090 fn test_zstd_output_buffer_capacity_edges() {
1091 let compressor = ZstdBufferCompressor::new(0);
1092 for input in zstd_test_inputs() {
1093 let expected = reference_frame(&input, 0);
1094
1095 let mut tight = b"prefix".to_vec();
1096 tight.shrink_to_fit();
1097 compressor.compress(&input, &mut tight).unwrap();
1098 assert_eq!(&tight[..6], b"prefix");
1099 assert_eq!(&tight[6..], &expected[..]);
1100
1101 let mut roomy = Vec::with_capacity(8 << 20);
1102 roomy.extend_from_slice(b"prefix");
1103 compressor.compress(&input, &mut roomy).unwrap();
1104 assert_eq!(roomy.len(), 6 + expected.len());
1105 assert_eq!(&roomy[6..], &expected[..]);
1106
1107 let mut decoded = Vec::with_capacity(8 << 20);
1108 compressor.decompress(&expected, &mut decoded).unwrap();
1109 assert_eq!(decoded, input);
1110 }
1111 }
1112
1113 #[test]
1115 fn test_zstd_decompress_empty_input_is_noop() {
1116 let compressor = ZstdBufferCompressor::new(0);
1117 let mut output = b"keep".to_vec();
1118 compressor.decompress(&[], &mut output).unwrap();
1119 assert_eq!(output, b"keep");
1120 }
1121
1122 #[test]
1123 fn test_zstd_exact_output_rejects_declared_length() {
1124 let compressor = ZstdBufferCompressor::new(0);
1125 let mut compressed = Vec::new();
1126 compressor.compress(b"bounded", &mut compressed).unwrap();
1127 compressed[..8].copy_from_slice(&u64::MAX.to_le_bytes());
1128
1129 let mut output = Vec::new();
1130 let error = compressor
1131 .decompress_exact(&compressed, &mut output, 7)
1132 .unwrap_err();
1133 assert!(error.to_string().contains("expected 7"));
1134 assert!(output.is_empty());
1135 }
1136
1137 #[test]
1140 fn test_zstd_exact_output_appends_into_spare_capacity() {
1141 let compressor = ZstdBufferCompressor::new(0);
1142 for input in zstd_test_inputs() {
1143 let prefixed = reference_frame(&input, 3);
1144 let raw = ::zstd::stream::encode_all(input.as_slice(), 3).unwrap();
1145 for frame in [&prefixed, &raw] {
1146 for spare in [0usize, 4 << 20] {
1147 let mut output = Vec::with_capacity(4 + spare);
1148 output.extend_from_slice(b"keep");
1149 compressor
1150 .decompress_exact(frame, &mut output, input.len())
1151 .unwrap();
1152 assert_eq!(&output[..4], b"keep");
1153 assert_eq!(&output[4..], &input[..]);
1154 }
1155 }
1156 }
1157 }
1158
1159 #[test]
1163 fn test_zstd_exact_output_frame_size_mismatch_preserves_buffer() {
1164 let compressor = ZstdBufferCompressor::new(0);
1165 let input = pseudo_random_bytes(5, 10_000);
1166 let raw = ::zstd::stream::encode_all(input.as_slice(), 3).unwrap();
1167 for expected in [0usize, 1, 9_999, 10_001] {
1168 let mut prefixed = reference_frame(&input, 3);
1169 prefixed[..8].copy_from_slice(&(expected as u64).to_le_bytes());
1170 for frame in [&prefixed, &raw] {
1171 for spare in [0usize, 64 * 1024] {
1172 let mut output = Vec::with_capacity(4 + spare);
1173 output.extend_from_slice(b"keep");
1174 assert!(
1175 compressor
1176 .decompress_exact(frame, &mut output, expected)
1177 .is_err(),
1178 "expected {expected} bytes from a {}-byte frame must fail",
1179 input.len()
1180 );
1181 assert_eq!(output, b"keep", "expected {expected}, spare {spare}");
1182 }
1183 }
1184 }
1185 }
1186
1187 #[test]
1188 fn test_zstd_compress_decompress_multiple_times() {
1189 let compressor = ZstdBufferCompressor::new(0);
1190 let (input_data_1, input_data_2) = (b"Hello ", b"World");
1191 let mut compressed_data = Vec::new();
1192
1193 compressor
1194 .compress(input_data_1, &mut compressed_data)
1195 .unwrap();
1196 let compressed_length_1 = compressed_data.len();
1197
1198 compressor
1199 .compress(input_data_2, &mut compressed_data)
1200 .unwrap();
1201
1202 let mut decompressed_data = Vec::new();
1203 compressor
1204 .decompress(
1205 &compressed_data[..compressed_length_1],
1206 &mut decompressed_data,
1207 )
1208 .unwrap();
1209
1210 compressor
1211 .decompress(
1212 &compressed_data[compressed_length_1..],
1213 &mut decompressed_data,
1214 )
1215 .unwrap();
1216
1217 assert_eq!(
1219 decompressed_data.len(),
1220 input_data_1.len() + input_data_2.len()
1221 );
1222 assert_eq!(
1223 &decompressed_data[..input_data_1.len()],
1224 input_data_1,
1225 "First part of decompressed data should match input_1"
1226 );
1227 assert_eq!(
1228 &decompressed_data[input_data_1.len()..],
1229 input_data_2,
1230 "Second part of decompressed data should match input_2"
1231 );
1232 }
1233
1234 #[test]
1235 fn test_compress_zstd_raw_stream_format_and_decompress_with_length_prefixed() {
1236 let compressor = ZstdBufferCompressor::new(0);
1237 let input_data = b"Hello, world!";
1238 let mut compressed_data = Vec::new();
1239
1240 let mut encoder = ::zstd::Encoder::new(&mut compressed_data, 0).unwrap();
1242 encoder.write_all(input_data).unwrap();
1243 encoder.finish().expect("failed to encode data with zstd");
1244
1245 let mut decompressed_data = Vec::new();
1247 compressor
1248 .decompress(&compressed_data, &mut decompressed_data)
1249 .unwrap();
1250 assert_eq!(input_data, decompressed_data.as_slice());
1251 }
1252 }
1253
1254 #[cfg(feature = "lz4")]
1255 mod lz4 {
1256 use std::{collections::HashMap, sync::Arc};
1257
1258 use arrow_schema::{DataType, Field};
1259 use lance_datagen::array::{binary_prefix_plus_counter, utf8_prefix_plus_counter};
1260
1261 use super::*;
1262
1263 use crate::constants::DICT_SIZE_RATIO_META_KEY;
1264 use crate::{
1265 constants::{
1266 COMPRESSION_META_KEY, DICT_DIVISOR_META_KEY, STRUCTURAL_ENCODING_FULLZIP,
1267 STRUCTURAL_ENCODING_META_KEY,
1268 },
1269 encodings::physical::block::lz4::Lz4BufferCompressor,
1270 testing::{
1271 FnArrayGeneratorProvider, TestCases, TestEncoding,
1272 check_round_trip_encoding_generated,
1273 },
1274 };
1275
1276 #[test]
1277 fn test_lz4_compress_decompress() {
1278 let compressor = Lz4BufferCompressor::default();
1279 let input_data = b"Hello, world!";
1280 let mut compressed_data = Vec::new();
1281
1282 compressor
1283 .compress(input_data, &mut compressed_data)
1284 .unwrap();
1285 let mut decompressed_data = Vec::new();
1286 compressor
1287 .decompress(&compressed_data, &mut decompressed_data)
1288 .unwrap();
1289 assert_eq!(input_data, decompressed_data.as_slice());
1290 }
1291
1292 #[test]
1293 fn test_lz4_exact_output_rejects_declared_length() {
1294 let compressor = Lz4BufferCompressor::default();
1295 let mut compressed = Vec::new();
1296 compressor.compress(b"bounded", &mut compressed).unwrap();
1297 compressed[..4].copy_from_slice(&u32::MAX.to_le_bytes());
1298
1299 let mut output = Vec::new();
1300 let error = compressor
1301 .decompress_exact(&compressed, &mut output, 7)
1302 .unwrap_err();
1303 assert!(error.to_string().contains("declares"));
1304 assert!(output.is_empty());
1305 }
1306
1307 #[rstest::rstest]
1308 #[test_log::test(tokio::test)]
1309 async fn test_lz4_compress_round_trip(
1310 #[values(
1311 DataType::Utf8,
1312 DataType::LargeUtf8,
1313 DataType::Binary,
1314 DataType::LargeBinary
1315 )]
1316 data_type: DataType,
1317 #[values(
1318 TestEncoding::StructuralU16,
1319 TestEncoding::StructuralU32,
1320 TestEncoding::StructuralSparse
1321 )]
1322 encoding: TestEncoding,
1323 #[values(false, true)] use_slicing: bool,
1324 ) {
1325 let field = Field::new("", data_type.clone(), false);
1326 let mut field_meta = HashMap::new();
1327 field_meta.insert(COMPRESSION_META_KEY.to_string(), "lz4".to_string());
1328 field_meta.insert(DICT_DIVISOR_META_KEY.to_string(), "100000".to_string());
1331 field_meta.insert(DICT_SIZE_RATIO_META_KEY.to_string(), "0.0001".to_string());
1332 field_meta.insert(
1334 STRUCTURAL_ENCODING_META_KEY.to_string(),
1335 STRUCTURAL_ENCODING_FULLZIP.to_string(),
1336 );
1337 let field = field.with_metadata(field_meta);
1338 let test_cases = TestCases::basic()
1339 .with_page_sizes(vec![1024 * 1024])
1341 .with_expected_encoding("zstd")
1342 .with_encoding(encoding)
1343 .with_slicing_modes([use_slicing]);
1344
1345 let datagen = Box::new(FnArrayGeneratorProvider::new(move || match data_type {
1348 DataType::Utf8 => utf8_prefix_plus_counter("compressme", false),
1349 DataType::Binary => {
1350 binary_prefix_plus_counter(Arc::from(b"compressme".to_owned()), false)
1351 }
1352 DataType::LargeUtf8 => utf8_prefix_plus_counter("compressme", true),
1353 DataType::LargeBinary => {
1354 binary_prefix_plus_counter(Arc::from(b"compressme".to_owned()), true)
1355 }
1356 _ => panic!("Unsupported data type: {:?}", data_type),
1357 }));
1358
1359 check_round_trip_encoding_generated(field, datagen, test_cases).await;
1360 }
1361 }
1362}