Skip to main content

parquet/
compression.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! Contains codec interface and supported codec implementations.
19//!
20//! See [`Compression`](crate::basic::Compression) enum for all available compression
21//! algorithms.
22//!
23// NOTE: this must be an inner attribute so that the example is attached to the module (and
24// therefore actually run as a doc test) rather than to the `use` statement below.
25#![cfg_attr(
26    feature = "experimental",
27    doc = r"
28# Example
29
30```no_run
31use parquet::{basic::Compression, compression::{create_codec, CodecOptionsBuilder}};
32
33let codec_options = CodecOptionsBuilder::default()
34    .set_backward_compatible_lz4(false)
35    .build();
36let mut codec = match create_codec(Compression::SNAPPY, &codec_options) {
37 Ok(Some(codec)) => codec,
38 _ => panic!(),
39};
40
41let data = vec![b'p', b'a', b'r', b'q', b'u', b'e', b't'];
42let mut compressed = vec![];
43codec.compress(&data[..], &mut compressed).unwrap();
44
45let mut output = vec![];
46codec.decompress(&compressed[..], &mut output, None).unwrap();
47
48assert_eq!(output, data);
49```
50"
51)]
52use crate::basic::Compression as CodecType;
53use crate::errors::{ParquetError, Result};
54
55/// Parquet compression codec interface.
56pub trait Codec: Send {
57    /// Compresses data stored in slice `input_buf` and appends the compressed result
58    /// to `output_buf`.
59    ///
60    /// Note that you'll need to call `clear()` before reusing the same `output_buf`
61    /// across different `compress` calls.
62    fn compress(&mut self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()>;
63
64    /// Decompresses data stored in slice `input_buf` and appends output to `output_buf`.
65    ///
66    /// If the uncompress_size is provided it will allocate the exact amount of memory.
67    /// Otherwise, it will estimate the uncompressed size, allocating an amount of memory
68    /// greater or equal to the real uncompress_size.
69    ///
70    /// Returns the total number of bytes written.
71    fn decompress(
72        &mut self,
73        input_buf: &[u8],
74        output_buf: &mut Vec<u8>,
75        uncompress_size: Option<usize>,
76    ) -> Result<usize>;
77}
78
79/// Struct to hold `Codec` creation options.
80#[derive(Debug, PartialEq, Eq)]
81pub struct CodecOptions {
82    /// Whether or not to fallback to other LZ4 older implementations on error in LZ4_HADOOP.
83    backward_compatible_lz4: bool,
84}
85
86impl Default for CodecOptions {
87    fn default() -> Self {
88        CodecOptionsBuilder::default().build()
89    }
90}
91
92pub struct CodecOptionsBuilder {
93    /// Whether or not to fallback to other LZ4 older implementations on error in LZ4_HADOOP.
94    backward_compatible_lz4: bool,
95}
96
97impl Default for CodecOptionsBuilder {
98    fn default() -> Self {
99        Self {
100            backward_compatible_lz4: true,
101        }
102    }
103}
104
105impl CodecOptionsBuilder {
106    /// Enable/disable backward compatible LZ4.
107    ///
108    /// If backward compatible LZ4 is enable, on LZ4_HADOOP error it will fallback
109    /// to the older versions LZ4 algorithms. That is LZ4_FRAME, for backward compatibility
110    /// with files generated by older versions of this library, and LZ4_RAW, for backward
111    /// compatibility with files generated by older versions of parquet-cpp.
112    ///
113    /// If backward compatible LZ4 is disabled, on LZ4_HADOOP error it will return the error.
114    pub fn set_backward_compatible_lz4(mut self, value: bool) -> CodecOptionsBuilder {
115        self.backward_compatible_lz4 = value;
116        self
117    }
118
119    pub fn build(self) -> CodecOptions {
120        CodecOptions {
121            backward_compatible_lz4: self.backward_compatible_lz4,
122        }
123    }
124}
125
126/// Defines valid compression levels.
127pub(crate) trait CompressionLevel<T: std::fmt::Display + std::cmp::PartialOrd> {
128    const MINIMUM_LEVEL: T;
129    const MAXIMUM_LEVEL: T;
130
131    /// Tests if the provided compression level is valid.
132    fn is_valid_level(level: T) -> Result<()> {
133        let compression_range = Self::MINIMUM_LEVEL..=Self::MAXIMUM_LEVEL;
134        if compression_range.contains(&level) {
135            Ok(())
136        } else {
137            Err(ParquetError::General(format!(
138                "valid compression range {}..={} exceeded.",
139                compression_range.start(),
140                compression_range.end()
141            )))
142        }
143    }
144}
145
146/// Given the compression type `codec`, returns a codec used to compress and decompress
147/// bytes for the compression type.
148/// This returns `None` if the codec type is `UNCOMPRESSED`.
149pub fn create_codec(codec: CodecType, _options: &CodecOptions) -> Result<Option<Box<dyn Codec>>> {
150    #[allow(unreachable_code, unused_variables)]
151    match codec {
152        CodecType::BROTLI(level) => {
153            #[cfg(any(feature = "brotli", test))]
154            return Ok(Some(Box::new(BrotliCodec::new(level))));
155            Err(ParquetError::General(
156                "Disabled feature at compile time: brotli".into(),
157            ))
158        }
159        CodecType::GZIP(level) => {
160            #[cfg(any(feature = "flate2", test))]
161            return Ok(Some(Box::new(GZipCodec::new(level))));
162            Err(ParquetError::General(
163                "Disabled feature at compile time: flate2".into(),
164            ))
165        }
166        CodecType::SNAPPY => {
167            #[cfg(any(feature = "snap", test))]
168            return Ok(Some(Box::new(SnappyCodec::new())));
169            Err(ParquetError::General(
170                "Disabled feature at compile time: snap".into(),
171            ))
172        }
173        CodecType::LZ4 => {
174            #[cfg(any(feature = "lz4", test))]
175            return Ok(Some(Box::new(LZ4HadoopCodec::new(
176                _options.backward_compatible_lz4,
177            ))));
178            Err(ParquetError::General(
179                "Disabled feature at compile time: lz4".into(),
180            ))
181        }
182        CodecType::ZSTD(level) => {
183            #[cfg(any(feature = "zstd", test))]
184            return Ok(Some(Box::new(ZSTDCodec::new(level))));
185            Err(ParquetError::General(
186                "Disabled feature at compile time: zstd".into(),
187            ))
188        }
189        CodecType::LZ4_RAW => {
190            #[cfg(any(feature = "lz4", test))]
191            return Ok(Some(Box::new(LZ4RawCodec::new())));
192            Err(ParquetError::General(
193                "Disabled feature at compile time: lz4".into(),
194            ))
195        }
196        CodecType::UNCOMPRESSED => Ok(None),
197        _ => Err(nyi_err!("The codec type {} is not supported yet", codec)),
198    }
199}
200
201#[cfg(any(feature = "snap", test))]
202mod snappy_codec {
203    use snap::raw::{Decoder, Encoder, decompress_len, max_compress_len};
204
205    use crate::compression::Codec;
206    use crate::errors::Result;
207
208    /// Codec for Snappy compression format.
209    pub struct SnappyCodec {
210        decoder: Decoder,
211        encoder: Encoder,
212    }
213
214    impl SnappyCodec {
215        /// Creates new Snappy compression codec.
216        pub(crate) fn new() -> Self {
217            Self {
218                decoder: Decoder::new(),
219                encoder: Encoder::new(),
220            }
221        }
222    }
223
224    impl Codec for SnappyCodec {
225        fn decompress(
226            &mut self,
227            input_buf: &[u8],
228            output_buf: &mut Vec<u8>,
229            uncompress_size: Option<usize>,
230        ) -> Result<usize> {
231            let len = match uncompress_size {
232                Some(size) => size,
233                None => decompress_len(input_buf)?,
234            };
235            let offset = output_buf.len();
236            output_buf.resize(offset + len, 0);
237            self.decoder
238                .decompress(input_buf, &mut output_buf[offset..])
239                .map_err(|e| e.into())
240        }
241
242        fn compress(&mut self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()> {
243            let output_buf_len = output_buf.len();
244            let required_len = max_compress_len(input_buf.len());
245            output_buf.resize(output_buf_len + required_len, 0);
246            let n = self
247                .encoder
248                .compress(input_buf, &mut output_buf[output_buf_len..])?;
249            output_buf.truncate(output_buf_len + n);
250            Ok(())
251        }
252    }
253}
254#[cfg(any(feature = "snap", test))]
255pub use snappy_codec::*;
256
257#[cfg(any(feature = "flate2", test))]
258mod gzip_codec {
259
260    use std::io::{Read, Write};
261
262    use flate2::{Compression, read, write};
263
264    use crate::compression::Codec;
265    use crate::errors::Result;
266
267    use super::GzipLevel;
268
269    /// Codec for GZIP compression algorithm.
270    pub struct GZipCodec {
271        level: GzipLevel,
272    }
273
274    impl GZipCodec {
275        /// Creates new GZIP compression codec.
276        pub(crate) fn new(level: GzipLevel) -> Self {
277            Self { level }
278        }
279    }
280
281    impl Codec for GZipCodec {
282        fn decompress(
283            &mut self,
284            input_buf: &[u8],
285            output_buf: &mut Vec<u8>,
286            _uncompress_size: Option<usize>,
287        ) -> Result<usize> {
288            let mut decoder = read::MultiGzDecoder::new(input_buf);
289            decoder.read_to_end(output_buf).map_err(|e| e.into())
290        }
291
292        fn compress(&mut self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()> {
293            let mut encoder = write::GzEncoder::new(output_buf, Compression::new(self.level.0));
294            encoder.write_all(input_buf)?;
295            encoder.try_finish().map_err(|e| e.into())
296        }
297    }
298}
299#[cfg(any(feature = "flate2", test))]
300pub use gzip_codec::*;
301
302/// Represents a valid gzip compression level.
303///
304/// Defaults to 6.
305///
306/// * 0: least compression
307/// * 9: most compression (that other software can read)
308/// * 10: most compression (incompatible with other software, see below)
309/// #### WARNING:
310/// Level 10 compression can offer smallest file size,
311/// but Parquet files created with it will not be readable
312/// by other "standard" paquet readers.
313///
314/// Do **NOT** use level 10 if you need other software to
315/// be able to read the files. Read below for details.
316///
317/// ### IMPORTANT:
318/// There's often confusion about the compression levels in `flate2` vs `arrow`
319/// as highlighted in issue [#1011](https://github.com/apache/arrow-rs/issues/6282).
320///
321/// `flate2` supports two compression backends: `miniz_oxide` and `zlib`.
322///
323/// - `zlib` supports levels from 0 to 9.
324/// - `miniz_oxide` supports levels from 0 to 10.
325///
326/// `arrow` uses `flate` with `rust_backend` feature,
327/// which provides `miniz_oxide` as the backend.
328/// Therefore 0-10 levels are supported.
329///
330/// `flate2` documents this behavior properly with
331/// [this commit](https://github.com/rust-lang/flate2-rs/pull/430).
332#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
333pub struct GzipLevel(u32);
334
335impl Default for GzipLevel {
336    fn default() -> Self {
337        // The default as of miniz_oxide 0.5.1 is 6 for compression level
338        // (miniz_oxide::deflate::CompressionLevel::DefaultLevel)
339        Self(6)
340    }
341}
342
343impl CompressionLevel<u32> for GzipLevel {
344    const MINIMUM_LEVEL: u32 = 0;
345    const MAXIMUM_LEVEL: u32 = 9;
346}
347
348impl GzipLevel {
349    /// Attempts to create a gzip compression level.
350    ///
351    /// Compression levels must be valid (i.e. be acceptable for [`flate2::Compression`]).
352    pub fn try_new(level: u32) -> Result<Self> {
353        Self::is_valid_level(level).map(|()| Self(level))
354    }
355
356    /// Returns the compression level.
357    pub fn compression_level(&self) -> u32 {
358        self.0
359    }
360}
361
362#[cfg(any(feature = "brotli", test))]
363mod brotli_codec {
364
365    use std::io::{Read, Write};
366
367    use crate::compression::Codec;
368    use crate::errors::Result;
369
370    use super::BrotliLevel;
371
372    const BROTLI_DEFAULT_BUFFER_SIZE: usize = 4096;
373    const BROTLI_DEFAULT_LG_WINDOW_SIZE: u32 = 22; // recommended between 20-22
374
375    /// Codec for Brotli compression algorithm.
376    pub struct BrotliCodec {
377        level: BrotliLevel,
378    }
379
380    impl BrotliCodec {
381        /// Creates new Brotli compression codec.
382        pub(crate) fn new(level: BrotliLevel) -> Self {
383            Self { level }
384        }
385    }
386
387    impl Codec for BrotliCodec {
388        fn decompress(
389            &mut self,
390            input_buf: &[u8],
391            output_buf: &mut Vec<u8>,
392            uncompress_size: Option<usize>,
393        ) -> Result<usize> {
394            let buffer_size = uncompress_size.unwrap_or(BROTLI_DEFAULT_BUFFER_SIZE);
395            brotli::Decompressor::new(input_buf, buffer_size)
396                .read_to_end(output_buf)
397                .map_err(|e| e.into())
398        }
399
400        fn compress(&mut self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()> {
401            let mut encoder = brotli::CompressorWriter::new(
402                output_buf,
403                BROTLI_DEFAULT_BUFFER_SIZE,
404                self.level.0,
405                BROTLI_DEFAULT_LG_WINDOW_SIZE,
406            );
407            encoder.write_all(input_buf)?;
408            encoder.flush().map_err(|e| e.into())
409        }
410    }
411}
412#[cfg(any(feature = "brotli", test))]
413pub use brotli_codec::*;
414
415/// Represents a valid brotli compression level.
416#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
417pub struct BrotliLevel(u32);
418
419impl Default for BrotliLevel {
420    fn default() -> Self {
421        Self(1)
422    }
423}
424
425impl CompressionLevel<u32> for BrotliLevel {
426    const MINIMUM_LEVEL: u32 = 0;
427    const MAXIMUM_LEVEL: u32 = 11;
428}
429
430impl BrotliLevel {
431    /// Attempts to create a brotli compression level.
432    ///
433    /// Compression levels must be valid.
434    pub fn try_new(level: u32) -> Result<Self> {
435        Self::is_valid_level(level).map(|()| Self(level))
436    }
437
438    /// Returns the compression level.
439    pub fn compression_level(&self) -> u32 {
440        self.0
441    }
442}
443
444#[cfg(any(feature = "lz4", test))]
445mod lz4_codec {
446    use std::io::{Read, Write};
447
448    use crate::compression::Codec;
449    use crate::errors::{ParquetError, Result};
450
451    const LZ4_BUFFER_SIZE: usize = 4096;
452
453    /// Codec for LZ4 compression algorithm.
454    pub struct LZ4Codec {}
455
456    impl LZ4Codec {
457        /// Creates new LZ4 compression codec.
458        pub(crate) fn new() -> Self {
459            Self {}
460        }
461    }
462
463    impl Codec for LZ4Codec {
464        fn decompress(
465            &mut self,
466            input_buf: &[u8],
467            output_buf: &mut Vec<u8>,
468            _uncompress_size: Option<usize>,
469        ) -> Result<usize> {
470            let mut decoder = lz4_flex::frame::FrameDecoder::new(input_buf);
471            let mut buffer: [u8; LZ4_BUFFER_SIZE] = [0; LZ4_BUFFER_SIZE];
472            let mut total_len = 0;
473            loop {
474                let len = decoder.read(&mut buffer)?;
475                if len == 0 {
476                    break;
477                }
478                total_len += len;
479                output_buf.write_all(&buffer[0..len])?;
480            }
481            Ok(total_len)
482        }
483
484        fn compress(&mut self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()> {
485            let mut encoder = lz4_flex::frame::FrameEncoder::new(output_buf);
486            let mut from = 0;
487            loop {
488                let to = std::cmp::min(from + LZ4_BUFFER_SIZE, input_buf.len());
489                encoder.write_all(&input_buf[from..to])?;
490                from += LZ4_BUFFER_SIZE;
491                if from >= input_buf.len() {
492                    break;
493                }
494            }
495            match encoder.finish() {
496                Ok(_) => Ok(()),
497                Err(e) => Err(ParquetError::External(Box::new(e))),
498            }
499        }
500    }
501}
502
503#[cfg(all(feature = "experimental", any(feature = "lz4", test)))]
504pub use lz4_codec::*;
505
506#[cfg(any(feature = "zstd", test))]
507mod zstd_codec {
508    use crate::compression::{Codec, ZstdLevel};
509    use crate::errors::Result;
510    use std::io::Cursor;
511
512    /// Codec for Zstandard compression algorithm.
513    ///
514    /// Uses `zstd::bulk` API with reusable compressor/decompressor contexts
515    /// to avoid the overhead of reinitializing contexts for each operation.
516    pub struct ZSTDCodec {
517        compressor: zstd::bulk::Compressor<'static>,
518        decompressor: zstd::bulk::Decompressor<'static>,
519    }
520
521    impl ZSTDCodec {
522        /// Creates new Zstandard compression codec.
523        pub(crate) fn new(level: ZstdLevel) -> Self {
524            Self {
525                compressor: zstd::bulk::Compressor::new(level.compression_level())
526                    .expect("valid zstd compression level"),
527                decompressor: zstd::bulk::Decompressor::new()
528                    .expect("can create zstd decompressor"),
529            }
530        }
531    }
532
533    impl Codec for ZSTDCodec {
534        fn decompress(
535            &mut self,
536            input_buf: &[u8],
537            output_buf: &mut Vec<u8>,
538            uncompress_size: Option<usize>,
539        ) -> Result<usize> {
540            let offset = output_buf.len();
541            let len = uncompress_size
542                .or_else(|| {
543                    // Get the decompressed size from the zstd frame header
544                    zstd::zstd_safe::get_frame_content_size(input_buf)
545                        .ok()
546                        .flatten()
547                        .map(|size| size as usize)
548                })
549                .unwrap_or(input_buf.len().saturating_mul(4));
550            output_buf.reserve(len);
551
552            let mut cursor = Cursor::new(output_buf);
553            cursor.set_position(offset as u64);
554            let len = self
555                .decompressor
556                .decompress_to_buffer(input_buf, &mut cursor)?;
557            Ok(len)
558        }
559
560        fn compress(&mut self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()> {
561            let offset = output_buf.len();
562            let len = zstd::zstd_safe::compress_bound(input_buf.len());
563            output_buf.reserve(len);
564
565            let mut cursor = Cursor::new(output_buf);
566            cursor.set_position(offset as u64);
567            let _written = self.compressor.compress_to_buffer(input_buf, &mut cursor)?;
568            Ok(())
569        }
570    }
571}
572#[cfg(any(feature = "zstd", test))]
573pub use zstd_codec::*;
574
575/// Represents a valid zstd compression level.
576#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
577pub struct ZstdLevel(i32);
578
579impl CompressionLevel<i32> for ZstdLevel {
580    // zstd binds to C, and hence zstd::compression_level_range() is not const as this calls the
581    // underlying C library.
582    const MINIMUM_LEVEL: i32 = -131072;
583    const MAXIMUM_LEVEL: i32 = 22;
584}
585
586impl ZstdLevel {
587    /// Attempts to create a zstd compression level from a given compression level.
588    ///
589    /// Compression levels must be valid (i.e. be acceptable for [`zstd::compression_level_range`]).
590    pub fn try_new(level: i32) -> Result<Self> {
591        Self::is_valid_level(level).map(|()| Self(level))
592    }
593
594    /// Returns the compression level.
595    pub fn compression_level(&self) -> i32 {
596        self.0
597    }
598}
599
600impl Default for ZstdLevel {
601    fn default() -> Self {
602        Self(1)
603    }
604}
605
606#[cfg(any(feature = "lz4", test))]
607mod lz4_raw_codec {
608    use crate::compression::Codec;
609    use crate::errors::ParquetError;
610    use crate::errors::Result;
611
612    /// Codec for LZ4 Raw compression algorithm.
613    pub struct LZ4RawCodec {}
614
615    impl LZ4RawCodec {
616        /// Creates new LZ4 Raw compression codec.
617        pub(crate) fn new() -> Self {
618            Self {}
619        }
620    }
621
622    impl Codec for LZ4RawCodec {
623        fn decompress(
624            &mut self,
625            input_buf: &[u8],
626            output_buf: &mut Vec<u8>,
627            uncompress_size: Option<usize>,
628        ) -> Result<usize> {
629            let offset = output_buf.len();
630            let required_len = match uncompress_size {
631                Some(uncompress_size) => uncompress_size,
632                None => {
633                    return Err(ParquetError::General(
634                        "LZ4RawCodec unsupported without uncompress_size".into(),
635                    ));
636                }
637            };
638            output_buf.resize(offset + required_len, 0);
639            match lz4_flex::block::decompress_into(input_buf, &mut output_buf[offset..]) {
640                Ok(n) => {
641                    if n != required_len {
642                        return Err(ParquetError::General(
643                            "LZ4RawCodec uncompress_size is not the expected one".into(),
644                        ));
645                    }
646                    Ok(n)
647                }
648                Err(e) => Err(ParquetError::External(Box::new(e))),
649            }
650        }
651
652        fn compress(&mut self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()> {
653            let offset = output_buf.len();
654            let required_len = lz4_flex::block::get_maximum_output_size(input_buf.len());
655            output_buf.resize(offset + required_len, 0);
656            match lz4_flex::block::compress_into(input_buf, &mut output_buf[offset..]) {
657                Ok(n) => {
658                    output_buf.truncate(offset + n);
659                    Ok(())
660                }
661                Err(e) => Err(ParquetError::External(Box::new(e))),
662            }
663        }
664    }
665}
666#[cfg(any(feature = "lz4", test))]
667pub use lz4_raw_codec::*;
668
669#[cfg(any(feature = "lz4", test))]
670mod lz4_hadoop_codec {
671    use crate::compression::Codec;
672    use crate::compression::lz4_codec::LZ4Codec;
673    use crate::compression::lz4_raw_codec::LZ4RawCodec;
674    use crate::errors::{ParquetError, Result};
675    use std::io;
676
677    /// Size of u32 type.
678    const SIZE_U32: usize = std::mem::size_of::<u32>();
679
680    /// Length of the LZ4_HADOOP prefix.
681    const PREFIX_LEN: usize = SIZE_U32 * 2;
682
683    /// Codec for LZ4 Hadoop compression algorithm.
684    pub struct LZ4HadoopCodec {
685        /// Whether or not to fallback to other LZ4 implementations on error.
686        /// Fallback is done to be backward compatible with older versions of this
687        /// library and older versions parquet-cpp.
688        backward_compatible_lz4: bool,
689    }
690
691    impl LZ4HadoopCodec {
692        /// Creates new LZ4 Hadoop compression codec.
693        pub(crate) fn new(backward_compatible_lz4: bool) -> Self {
694            Self {
695                backward_compatible_lz4,
696            }
697        }
698    }
699
700    /// Try to decompress the buffer as if it was compressed with the Hadoop Lz4Codec.
701    /// Adapted from pola-rs [compression.rs:try_decompress_hadoop](https://pola-rs.github.io/polars/src/parquet2/compression.rs.html#225)
702    /// Translated from the apache arrow c++ function [TryDecompressHadoop](https://github.com/apache/arrow/blob/bf18e6e4b5bb6180706b1ba0d597a65a4ce5ca48/cpp/src/arrow/util/compression_lz4.cc#L474).
703    /// Returns error if decompression failed.
704    fn try_decompress_hadoop(input_buf: &[u8], output_buf: &mut [u8]) -> io::Result<usize> {
705        // Parquet files written with the Hadoop Lz4Codec use their own framing.
706        // The input buffer can contain an arbitrary number of "frames", each
707        // with the following structure:
708        // - bytes 0..3: big-endian uint32_t representing the frame decompressed size
709        // - bytes 4..7: big-endian uint32_t representing the frame compressed size
710        // - bytes 8...: frame compressed data
711        //
712        // The Hadoop Lz4Codec source code can be found here:
713        // https://github.com/apache/hadoop/blob/trunk/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-nativetask/src/main/native/src/codec/Lz4Codec.cc
714        let mut input_len = input_buf.len();
715        let mut input = input_buf;
716        let mut read_bytes = 0;
717        let mut output_len = output_buf.len();
718        let mut output: &mut [u8] = output_buf;
719        while input_len >= PREFIX_LEN {
720            let mut bytes = [0; SIZE_U32];
721            bytes.copy_from_slice(&input[0..4]);
722            let expected_decompressed_size = u32::from_be_bytes(bytes);
723            let mut bytes = [0; SIZE_U32];
724            bytes.copy_from_slice(&input[4..8]);
725            let expected_compressed_size = u32::from_be_bytes(bytes);
726            input = &input[PREFIX_LEN..];
727            input_len -= PREFIX_LEN;
728
729            if input_len < expected_compressed_size as usize {
730                return Err(io::Error::other("Not enough bytes for Hadoop frame"));
731            }
732
733            if output_len < expected_decompressed_size as usize {
734                return Err(io::Error::other(
735                    "Not enough bytes to hold advertised output",
736                ));
737            }
738            let decompressed_size =
739                lz4_flex::decompress_into(&input[..expected_compressed_size as usize], output)
740                    .map_err(|e| ParquetError::External(Box::new(e)))?;
741            if decompressed_size != expected_decompressed_size as usize {
742                return Err(io::Error::other("Unexpected decompressed size"));
743            }
744            input_len -= expected_compressed_size as usize;
745            output_len -= expected_decompressed_size as usize;
746            read_bytes += expected_decompressed_size as usize;
747            if input_len > expected_compressed_size as usize {
748                input = &input[expected_compressed_size as usize..];
749                output = &mut output[expected_decompressed_size as usize..];
750            } else {
751                break;
752            }
753        }
754        if input_len == 0 {
755            Ok(read_bytes)
756        } else {
757            Err(io::Error::other("Not all input are consumed"))
758        }
759    }
760
761    impl Codec for LZ4HadoopCodec {
762        fn decompress(
763            &mut self,
764            input_buf: &[u8],
765            output_buf: &mut Vec<u8>,
766            uncompress_size: Option<usize>,
767        ) -> Result<usize> {
768            let output_len = output_buf.len();
769            let required_len = match uncompress_size {
770                Some(n) => n,
771                None => {
772                    return Err(ParquetError::General(
773                        "LZ4HadoopCodec unsupported without uncompress_size".into(),
774                    ));
775                }
776            };
777            output_buf.resize(output_len + required_len, 0);
778            match try_decompress_hadoop(input_buf, &mut output_buf[output_len..]) {
779                Ok(n) => {
780                    if n != required_len {
781                        return Err(ParquetError::General(
782                            "LZ4HadoopCodec uncompress_size is not the expected one".into(),
783                        ));
784                    }
785                    Ok(n)
786                }
787                Err(e) if !self.backward_compatible_lz4 => Err(e.into()),
788                // Fallback done to be backward compatible with older versions of this
789                // library and older versions of parquet-cpp.
790                Err(_) => {
791                    // Truncate any inserted element before tryingg next algorithm.
792                    output_buf.truncate(output_len);
793                    match LZ4Codec::new().decompress(input_buf, output_buf, uncompress_size) {
794                        Ok(n) => Ok(n),
795                        Err(_) => {
796                            // Truncate any inserted element before tryingg next algorithm.
797                            output_buf.truncate(output_len);
798                            LZ4RawCodec::new().decompress(input_buf, output_buf, uncompress_size)
799                        }
800                    }
801                }
802            }
803        }
804
805        fn compress(&mut self, input_buf: &[u8], output_buf: &mut Vec<u8>) -> Result<()> {
806            // Allocate memory to store the LZ4_HADOOP prefix.
807            let offset = output_buf.len();
808            output_buf.resize(offset + PREFIX_LEN, 0);
809
810            // Append LZ4_RAW compressed bytes after prefix.
811            LZ4RawCodec::new().compress(input_buf, output_buf)?;
812
813            // Prepend decompressed size and compressed size in big endian to be compatible
814            // with LZ4_HADOOP.
815            let output_buf = &mut output_buf[offset..];
816            let compressed_size = output_buf.len() - PREFIX_LEN;
817            let compressed_size = compressed_size as u32;
818            let uncompressed_size = input_buf.len() as u32;
819            output_buf[..SIZE_U32].copy_from_slice(&uncompressed_size.to_be_bytes());
820            output_buf[SIZE_U32..PREFIX_LEN].copy_from_slice(&compressed_size.to_be_bytes());
821
822            Ok(())
823        }
824    }
825}
826#[cfg(any(feature = "lz4", test))]
827pub use lz4_hadoop_codec::*;
828
829#[cfg(test)]
830mod tests {
831    use super::*;
832
833    use crate::util::test_common::rand_gen::random_bytes;
834
835    fn test_roundtrip(c: CodecType, data: &[u8], uncompress_size: Option<usize>) {
836        let codec_options = CodecOptionsBuilder::default()
837            .set_backward_compatible_lz4(false)
838            .build();
839        let mut c1 = create_codec(c, &codec_options).unwrap().unwrap();
840        let mut c2 = create_codec(c, &codec_options).unwrap().unwrap();
841
842        // Compress with c1
843        let mut compressed = Vec::new();
844        let mut decompressed = Vec::new();
845        c1.compress(data, &mut compressed)
846            .expect("Error when compressing");
847
848        // Decompress with c2
849        let decompressed_size = c2
850            .decompress(compressed.as_slice(), &mut decompressed, uncompress_size)
851            .expect("Error when decompressing");
852        assert_eq!(data.len(), decompressed_size);
853        assert_eq!(data, decompressed.as_slice());
854
855        decompressed.clear();
856        compressed.clear();
857
858        // Compress with c2
859        c2.compress(data, &mut compressed)
860            .expect("Error when compressing");
861
862        // Decompress with c1
863        let decompressed_size = c1
864            .decompress(compressed.as_slice(), &mut decompressed, uncompress_size)
865            .expect("Error when decompressing");
866        assert_eq!(data.len(), decompressed_size);
867        assert_eq!(data, decompressed.as_slice());
868
869        decompressed.clear();
870        compressed.clear();
871
872        // Test does not trample existing data in output buffers
873        let prefix = &[0xDE, 0xAD, 0xBE, 0xEF];
874        decompressed.extend_from_slice(prefix);
875        compressed.extend_from_slice(prefix);
876
877        c2.compress(data, &mut compressed)
878            .expect("Error when compressing");
879
880        assert_eq!(&compressed[..4], prefix);
881
882        let decompressed_size = c2
883            .decompress(&compressed[4..], &mut decompressed, uncompress_size)
884            .expect("Error when decompressing");
885
886        assert_eq!(data.len(), decompressed_size);
887        assert_eq!(data, &decompressed[4..]);
888        assert_eq!(&decompressed[..4], prefix);
889    }
890
891    fn test_codec_with_size(c: CodecType) {
892        let sizes = vec![100, 10000, 100000];
893        for size in sizes {
894            let data = random_bytes(size);
895            test_roundtrip(c, &data, Some(data.len()));
896        }
897    }
898
899    fn test_codec_without_size(c: CodecType) {
900        let sizes = vec![100, 10000, 100000];
901        for size in sizes {
902            let data = random_bytes(size);
903            test_roundtrip(c, &data, None);
904        }
905    }
906
907    #[test]
908    fn test_codec_snappy() {
909        test_codec_with_size(CodecType::SNAPPY);
910        test_codec_without_size(CodecType::SNAPPY);
911    }
912
913    #[test]
914    fn test_codec_gzip() {
915        for level in GzipLevel::MINIMUM_LEVEL..=GzipLevel::MAXIMUM_LEVEL {
916            let level = GzipLevel::try_new(level).unwrap();
917            test_codec_with_size(CodecType::GZIP(level));
918            test_codec_without_size(CodecType::GZIP(level));
919        }
920    }
921
922    #[test]
923    fn test_codec_brotli() {
924        for level in BrotliLevel::MINIMUM_LEVEL..=BrotliLevel::MAXIMUM_LEVEL {
925            let level = BrotliLevel::try_new(level).unwrap();
926            test_codec_with_size(CodecType::BROTLI(level));
927            test_codec_without_size(CodecType::BROTLI(level));
928        }
929    }
930
931    #[test]
932    fn test_codec_lz4() {
933        test_codec_with_size(CodecType::LZ4);
934    }
935
936    #[test]
937    fn test_codec_zstd() {
938        // since ZstdLevel::MINIMUM_LEVEL is a large negative number, we test a smaller range
939        for level in [ZstdLevel::MINIMUM_LEVEL]
940            .into_iter()
941            .chain(-100..=ZstdLevel::MAXIMUM_LEVEL)
942        {
943            let level = ZstdLevel::try_new(level).unwrap();
944            test_codec_with_size(CodecType::ZSTD(level));
945            test_codec_without_size(CodecType::ZSTD(level));
946        }
947    }
948
949    #[test]
950    fn test_codec_lz4_raw() {
951        test_codec_with_size(CodecType::LZ4_RAW);
952    }
953}