Skip to main content

lance_encoding/encodings/physical/
block.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright The Lance Authors
3
4//! Encodings based on traditional block compression schemes
5//!
6//! Traditional compressors take in a buffer and return a smaller buffer.  All encoding
7//! description is shoved into the compressed buffer and the entire buffer is needed to
8//! decompress any of the data.
9//!
10//! These encodings are not transparent, which limits our ability to use them.  In addition
11//! they are often quite expensive in CPU terms.
12//!
13//! However, they are effective and useful for some cases.  For example, when working with large
14//! variable length values (e.g. source code files) they can be very effective.
15//!
16//! The module introduces the `[BufferCompressor]` trait which describes the interface for a
17//! traditional block compressor.  It is implemented for the most common compression schemes
18//! (zstd, lz4, etc).
19//!
20//! There is not yet a mini-block variant of this compressor (but could easily be one) and the
21//! full zip variant works by applying compression on a per-value basis (which allows it to be
22//! transparent).
23
24use 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    /// Create a compression configuration for an encoding mechanism.
50    pub fn new(scheme: CompressionScheme, level: Option<i32>) -> Self {
51        Self { scheme, level }
52    }
53
54    /// Return the selected compression scheme.
55    pub fn scheme(&self) -> CompressionScheme {
56        self.scheme
57    }
58
59    /// Return the optional compression level.
60    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        /// One reusable decompression context per thread: decode tasks run on a
177        /// shared pool, so a per-thread context avoids both per-value context
178        /// setup and cross-thread lock contention.
179        static DECOMPRESSOR: RefCell<Option<Decompressor<'static>>> = const { RefCell::new(None) };
180    }
181
182    /// Appends into `buf`'s spare capacity (reserved by the caller) without
183    /// zero-filling it first; zstd writes the bytes and the cursor sets the length.
184    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    /// Decompresses one zstd frame into `output_buf`'s spare capacity, which the
192    /// caller reserves, and returns the number of bytes appended. A frame larger
193    /// than the spare capacity is an error.
194    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    /// A zstd buffer compressor that lazily creates and reuses compression contexts.
210    ///
211    /// The compression context is cached to enable reuse across chunks within a
212    /// page. It is lazily initialized to prevent it from getting initialized on
213    /// decode-only codepaths.
214    ///
215    /// Decompression does not share this compressor's mutex-guarded context:
216    /// decode tasks run concurrently on a shared pool, so a shared context would
217    /// serialize them. Each thread instead keeps its own decompression context
218    /// (see `DECOMPRESSOR`), which avoids both per-value context setup and lock
219    /// contention.
220    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        // https://datatracker.ietf.org/doc/html/rfc8878
253        fn is_raw_stream_format(&self, input_buf: &[u8]) -> bool {
254            if input_buf.len() < 8 {
255                return true; // can't be length prefixed format if less than 8 bytes
256            }
257            // read the first 4 bytes as the magic number
258            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            // see RFC 8878, section 3.1.1. Zstandard Frames, which defines the magic number
263            const ZSTD_MAGIC_NUMBER: u32 = 0xFD2FB528;
264            if magic == ZSTD_MAGIC_NUMBER {
265                // the compressed buffer starts like a Zstd frame.
266                // Per RFC 8878, the reserved bit (with Bit Number 3, the 4th bit) in the FHD (frame header descriptor) MUST be 0
267                // see section 3.1.1.1.1. 'Frame_Header_Descriptor' and section 3.1.1.1.1.4. 'Reserved Bit' for details
268                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                    // this bit is 1. This is NOT a valid zstd frame.
275                    // therefore, it must be length prefixed format where the length coincidentally
276                    // started with the magic number
277                    false
278                } else {
279                    // the reserved bit is 0. This is consistent with a valid Zstd frame.
280                    // treat it as raw stream format
281                    true
282                }
283            } else {
284                // doesn't start with the magic number, so it can't be the raw stream format
285                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                // `reserve` may over-allocate, so a frame longer than its prefix can
306                // still fit; drop whatever it appended so an error leaves the
307                // buffer exactly as the caller passed it.
308                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            // Reports a length overflow as well as an allocation failure.
377            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            // Remember the starting position
420            let start_pos = output_buf.len();
421
422            // LZ4 needs space for the compressed data
423            let max_size = ::lz4::block::compress_bound(input_buf.len())?;
424            // Resize to ensure we have enough space (including 4 bytes for size header)
425            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            // Truncate to actual size
436            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            // When prepend_size is true, LZ4 stores the uncompressed size in the first 4 bytes
442            // We can read this to know exactly how much space we need
443            if input_buf.len() < 4 {
444                return Err(Error::invalid_input(
445                    "LZ4 buffer is missing its length prefix",
446                ));
447            }
448
449            // Read the uncompressed size from the first 4 bytes (little-endian)
450            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            // Remember the starting position
455            let start_pos = output_buf.len();
456
457            // Resize to ensure we have the exact space needed
458            output_buf.resize(start_pos + uncompressed_size, 0);
459
460            // Now decompress directly into the buffer slice
461            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            // Truncate to actual decompressed size (should be same as uncompressed_size)
466            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            // FSST has its own compression path and isn't implemented as a generic buffer compressor
576            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/// A block decompressor that first applies general-purpose compression (LZ4/Zstd)
611/// before delegating to an inner block decompressor.
612#[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// An encoder which uses generic compression, such as zstd/lz4 to encode buffers
639#[derive(Debug)]
640pub struct CompressedBufferEncoder {
641    pub(crate) compressor: Box<dyn BufferCompressor>,
642    // Runtime compressors normalize levels that they default or ignore. Block descriptors must
643    // retain the selected configuration so stable writers preserve those present/absent values.
644    block_compression: CompressionConfig,
645}
646
647impl Default for CompressedBufferEncoder {
648    fn default() -> Self {
649        // Pick zstd if available, otherwise lz4, otherwise none
650        #[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        // TODO: Support setting the level
763        // TODO: Support underlying compression of data (e.g. defer to binary encoding for offset bitpacking)
764        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                // Wrap VariableEncoder to handle the encoding
810                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        // Delegate to BinaryBlockDecompressor which handles the inline metadata
846        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        /// Deterministic pseudo-random bytes (xorshift64), so inputs are reproducible
899        /// without a test-only RNG dependency.
900        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        /// Inputs covering the shapes zstd treats differently: empty, tiny,
913        /// incompressible, highly repetitive, and all-zero, up to 1 MiB.
914        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        /// A length-prefixed frame built with the `zstd` crate directly, independent
925        /// of `ZstdBufferCompressor`: the on-disk layout the decompressor must read.
926        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        /// Compression and decompression append into the buffer's spare capacity
933        /// without zero-filling it first. Round-trip across sizes (including empty
934        /// and incompressible inputs), levels, pre-existing buffer contents, and
935        /// spare capacity left over from earlier writes; the prefix must survive
936        /// and every byte past it must be exactly what zstd produced.
937        #[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        /// The written bytes are exactly what one-shot zstd produces for the same
960        /// level, behind the same 8-byte length prefix, so appending into spare
961        /// capacity cannot change what lands on disk.
962        #[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        /// Frames written by plain zstd decode through `decompress`, and frames
980        /// written by `compress` decode with plain zstd.
981        #[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        /// A length prefix that disagrees with the frame is an error in both
999        /// directions, and the output buffer is left exactly as it was passed in
1000        /// (including when the frame is longer and fits in over-reserved capacity).
1001        #[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        /// Corrupted or truncated frames fail without exposing any bytes.
1023        #[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        /// The per-thread decompression context is reset for every frame: a failed
1039        /// frame does not poison the next one, and frames of very different sizes
1040        /// decode correctly back to back on the same thread.
1041        #[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        /// Many threads decompress concurrently through one shared compressor; each
1061        /// thread uses its own context and every frame decodes to its input.
1062        #[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        /// Output buffers with no spare capacity (every append must grow the
1087        /// allocation) and with far more capacity than needed both produce the
1088        /// same bytes, with the length covering only what zstd wrote.
1089        #[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        /// Decompressing an empty input is a no-op, as before.
1114        #[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        /// Exact decompression appends after existing contents, for length-prefixed
1138        /// and raw frames, regardless of how much spare capacity the buffer has.
1139        #[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        /// A frame that decodes to more or fewer bytes than expected fails and
1160        /// leaves the buffer as it was, even when spare capacity could hold the
1161        /// longer frame.
1162        #[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            // the output should contain both input_data_1 and input_data_2
1218            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            // compress using raw stream format
1241            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            // decompress using length prefixed format
1246            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            // Some bad cardinality estimatation causes us to use dictionary encoding currently
1329            // which causes the expected encoding check to fail.
1330            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            // Also disable size-based dictionary encoding
1333            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                // Need to use large pages as small pages might be too small to compress
1340                .with_page_sizes(vec![1024 * 1024])
1341                .with_expected_encoding("zstd")
1342                .with_encoding(encoding)
1343                .with_slicing_modes([use_slicing]);
1344
1345            // Can't use the default random provider because random data isn't compressible
1346            // and we will fallback to uncompressed encoding
1347            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}