1use std::{
22 borrow::Cow,
23 io::{Read, Write},
24};
25
26use bytes::{Bytes, BytesMut};
27use fallible_streaming_iterator::FallibleStreamingIterator;
28use snafu::ResultExt;
29
30use crate::error::{self, OrcError, Result};
31use crate::proto::{self, CompressionKind};
32
33const DEFAULT_COMPRESSION_BLOCK_SIZE: u64 = 256 * 1024;
35
36const MAX_COMPRESSION_BLOCK_SIZE: u64 = 1 << 23;
38
39#[derive(Clone, Copy, Debug)]
40pub struct Compression {
41 compression_type: CompressionType,
42 max_decompressed_block_size: usize,
45}
46impl std::fmt::Display for Compression {
47 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 write!(
49 f,
50 "{} ({} byte max block size)",
51 self.compression_type, self.max_decompressed_block_size
52 )
53 }
54}
55
56impl Compression {
57 pub fn compression_type(&self) -> CompressionType {
58 self.compression_type
59 }
60
61 pub(crate) fn from_proto(
62 kind: proto::CompressionKind,
63 compression_block_size: Option<u64>,
64 ) -> Option<Self> {
65 let max_decompressed_block_size =
66 compression_block_size.unwrap_or(DEFAULT_COMPRESSION_BLOCK_SIZE) as usize;
67 match kind {
68 CompressionKind::None => None,
69 CompressionKind::Zlib => Some(Self {
70 compression_type: CompressionType::Zlib,
71 max_decompressed_block_size,
72 }),
73 CompressionKind::Snappy => Some(Self {
74 compression_type: CompressionType::Snappy,
75 max_decompressed_block_size,
76 }),
77 CompressionKind::Lzo => Some(Self {
78 compression_type: CompressionType::Lzo,
79 max_decompressed_block_size,
80 }),
81 CompressionKind::Lz4 => Some(Self {
82 compression_type: CompressionType::Lz4,
83 max_decompressed_block_size,
84 }),
85 CompressionKind::Zstd => Some(Self {
86 compression_type: CompressionType::Zstd,
87 max_decompressed_block_size,
88 }),
89 }
90 }
91}
92
93#[derive(Clone, Copy, Debug)]
94pub enum CompressionType {
95 Zlib,
96 Snappy,
97 Lzo,
98 Lz4,
99 Zstd,
100}
101
102impl std::fmt::Display for CompressionType {
103 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104 write!(f, "{self:?}")
105 }
106}
107
108impl CompressionType {
109 pub(crate) fn to_proto(self) -> proto::CompressionKind {
110 match self {
111 Self::Zlib => proto::CompressionKind::Zlib,
112 Self::Snappy => proto::CompressionKind::Snappy,
113 Self::Lzo => proto::CompressionKind::Lzo,
114 Self::Lz4 => proto::CompressionKind::Lz4,
115 Self::Zstd => proto::CompressionKind::Zstd,
116 }
117 }
118}
119
120#[derive(Debug, PartialEq, Eq)]
122enum CompressionHeader {
123 Original(u32),
124 Compressed(u32),
125}
126
127impl CompressionHeader {
131 fn decode(bytes: [u8; 3]) -> Self {
132 let bytes = [bytes[0], bytes[1], bytes[2], 0];
133 let length_and_flag = u32::from_le_bytes(bytes);
134 let is_original = length_and_flag & 1 == 1;
135 let length = length_and_flag >> 1;
136 if is_original {
137 Self::Original(length)
138 } else {
139 Self::Compressed(length)
140 }
141 }
142
143 fn encode(self) -> [u8; 3] {
144 let (length, is_original) = match self {
145 Self::Original(length) => (length, 1),
146 Self::Compressed(length) => (length, 0),
147 };
148 debug_assert!((length as u64) < MAX_COMPRESSION_BLOCK_SIZE);
149 let encoded = (length << 1) | is_original;
150 let bytes = encoded.to_le_bytes();
151 [bytes[0], bytes[1], bytes[2]]
152 }
153}
154
155trait DecompressorVariant: Send {
156 fn decompress_block(&self, compressed_bytes: &[u8], scratch: &mut Vec<u8>) -> Result<()>;
157}
158
159#[derive(Debug, Clone, Copy)]
160struct Zlib;
161#[derive(Debug, Clone, Copy)]
162struct Zstd;
163#[derive(Debug, Clone, Copy)]
164struct Snappy;
165#[derive(Debug, Clone, Copy)]
166struct Lzo;
167#[derive(Debug, Clone, Copy)]
168struct Lz4 {
169 max_decompressed_block_size: usize,
170}
171
172impl DecompressorVariant for Zlib {
173 fn decompress_block(&self, compressed_bytes: &[u8], scratch: &mut Vec<u8>) -> Result<()> {
174 let mut gz = flate2::read::DeflateDecoder::new(compressed_bytes);
175 scratch.clear();
176 gz.read_to_end(scratch).context(error::IoSnafu)?;
177 Ok(())
178 }
179}
180
181impl DecompressorVariant for Zstd {
182 fn decompress_block(&self, compressed_bytes: &[u8], scratch: &mut Vec<u8>) -> Result<()> {
183 let mut reader =
184 zstd::Decoder::new(compressed_bytes).context(error::BuildZstdDecoderSnafu)?;
185 scratch.clear();
186 reader.read_to_end(scratch).context(error::IoSnafu)?;
187 Ok(())
188 }
189}
190
191impl DecompressorVariant for Snappy {
192 fn decompress_block(&self, compressed_bytes: &[u8], scratch: &mut Vec<u8>) -> Result<()> {
193 let len =
194 snap::raw::decompress_len(compressed_bytes).context(error::BuildSnappyDecoderSnafu)?;
195 scratch.resize(len, 0);
196 let mut decoder = snap::raw::Decoder::new();
197 decoder
198 .decompress(compressed_bytes, scratch)
199 .context(error::BuildSnappyDecoderSnafu)?;
200 Ok(())
201 }
202}
203
204impl DecompressorVariant for Lzo {
205 fn decompress_block(&self, compressed_bytes: &[u8], scratch: &mut Vec<u8>) -> Result<()> {
206 let decompressed = lzokay_native::decompress_all(compressed_bytes, None)
207 .context(error::BuildLzoDecoderSnafu)?;
208 scratch.clear();
210 scratch.extend(decompressed);
211 Ok(())
212 }
213}
214
215impl DecompressorVariant for Lz4 {
216 fn decompress_block(&self, compressed_bytes: &[u8], scratch: &mut Vec<u8>) -> Result<()> {
217 let decompressed =
218 lz4_flex::block::decompress(compressed_bytes, self.max_decompressed_block_size)
219 .context(error::BuildLz4DecoderSnafu)?;
220 scratch.clear();
222 scratch.extend(decompressed);
223 Ok(())
224 }
225}
226
227fn get_decompressor_variant(
229 Compression {
230 compression_type,
231 max_decompressed_block_size,
232 }: Compression,
233) -> Box<dyn DecompressorVariant> {
234 match compression_type {
235 CompressionType::Zlib => Box::new(Zlib),
236 CompressionType::Snappy => Box::new(Snappy),
237 CompressionType::Lzo => Box::new(Lzo),
238 CompressionType::Lz4 => Box::new(Lz4 {
239 max_decompressed_block_size,
240 }),
241 CompressionType::Zstd => Box::new(Zstd),
242 }
243}
244
245enum State {
246 Original(Bytes),
247 Compressed(Vec<u8>),
248}
249
250struct DecompressorIter {
251 stream: BytesMut,
252 current: Option<State>, compression: Option<Box<dyn DecompressorVariant>>,
254 scratch: Vec<u8>,
255}
256
257impl DecompressorIter {
258 fn new(stream: Bytes, compression: Option<Compression>, scratch: Vec<u8>) -> Self {
259 Self {
260 stream: BytesMut::from(stream.as_ref()),
261 current: None,
262 compression: compression.map(get_decompressor_variant),
263 scratch,
264 }
265 }
266}
267
268impl FallibleStreamingIterator for DecompressorIter {
269 type Item = [u8];
270
271 type Error = OrcError;
272
273 #[inline]
274 fn advance(&mut self) -> Result<(), Self::Error> {
275 if self.stream.is_empty() {
276 self.current = None;
277 return Ok(());
278 }
279
280 match &self.compression {
281 Some(compression) => {
282 let header = self.stream.split_to(3);
284 let header = [header[0], header[1], header[2]];
285 match CompressionHeader::decode(header) {
286 CompressionHeader::Original(length) => {
287 let original = self.stream.split_to(length as usize);
288 self.current = Some(State::Original(original.into()));
289 }
290 CompressionHeader::Compressed(length) => {
291 let compressed = self.stream.split_to(length as usize);
292 compression.decompress_block(&compressed, &mut self.scratch)?;
293 self.current = Some(State::Compressed(std::mem::take(&mut self.scratch)));
294 }
295 };
296 Ok(())
297 }
298 None => {
299 self.current = Some(State::Original(self.stream.clone().into()));
301 self.stream.clear();
302 Ok(())
303 }
304 }
305 }
306
307 #[inline]
308 fn get(&self) -> Option<&Self::Item> {
309 self.current.as_ref().map(|x| match x {
310 State::Original(x) => x.as_ref(),
311 State::Compressed(x) => x.as_ref(),
312 })
313 }
314}
315
316pub struct Decompressor {
318 decompressor: DecompressorIter,
319 offset: usize,
320 is_first: bool,
321}
322
323impl Decompressor {
324 pub fn new(stream: Bytes, compression: Option<Compression>, scratch: Vec<u8>) -> Self {
326 Self {
327 decompressor: DecompressorIter::new(stream, compression, scratch),
328 offset: 0,
329 is_first: true,
330 }
331 }
332
333 pub fn empty() -> Self {
335 Self {
336 decompressor: DecompressorIter::new(Bytes::new(), None, vec![]),
337 offset: 0,
338 is_first: true,
339 }
340 }
341}
342
343impl std::io::Read for Decompressor {
344 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
345 if self.is_first {
346 self.is_first = false;
347 self.decompressor.advance().unwrap();
348 }
349 let current = self.decompressor.get();
350 let current = if let Some(current) = current {
351 if current.len() == self.offset {
352 self.decompressor.advance().unwrap();
353 self.offset = 0;
354 let current = self.decompressor.get();
355 if let Some(current) = current {
356 current
357 } else {
358 return Ok(0);
359 }
360 } else {
361 ¤t[self.offset..]
362 }
363 } else {
364 return Ok(0);
365 };
366
367 if current.len() >= buf.len() {
368 buf.copy_from_slice(¤t[..buf.len()]);
369 self.offset += buf.len();
370 Ok(buf.len())
371 } else {
372 buf[..current.len()].copy_from_slice(current);
373 self.offset += current.len();
374 Ok(current.len())
375 }
376 }
377}
378
379trait CompressorVariant: Send {
380 fn compress_block(&mut self, input: &[u8], output: &mut Vec<u8>) -> Result<()>;
381}
382
383struct ZlibCompressor;
384
385impl CompressorVariant for ZlibCompressor {
386 fn compress_block(&mut self, input: &[u8], output: &mut Vec<u8>) -> Result<()> {
387 let mut encoder =
388 flate2::write::DeflateEncoder::new(output, flate2::Compression::default());
389 encoder.write_all(input).context(error::IoSnafu)?;
390 encoder.finish().context(error::IoSnafu)?;
391 Ok(())
392 }
393}
394
395struct SnappyCompressor(snap::raw::Encoder);
396
397impl CompressorVariant for SnappyCompressor {
398 fn compress_block(&mut self, input: &[u8], output: &mut Vec<u8>) -> Result<()> {
399 output.resize(snap::raw::max_compress_len(input.len()), 0);
400 let written = self
401 .0
402 .compress(input, output)
403 .context(error::CompressSnappySnafu)?;
404 output.truncate(written);
405 Ok(())
406 }
407}
408
409struct Lz4Compressor;
410
411impl CompressorVariant for Lz4Compressor {
412 fn compress_block(&mut self, input: &[u8], output: &mut Vec<u8>) -> Result<()> {
413 output.resize(lz4_flex::block::get_maximum_output_size(input.len()), 0);
414 let written =
415 lz4_flex::block::compress_into(input, output).context(error::CompressLz4Snafu)?;
416 output.truncate(written);
417 Ok(())
418 }
419}
420
421struct ZstdCompressor(zstd::bulk::Compressor<'static>);
422
423impl CompressorVariant for ZstdCompressor {
424 fn compress_block(&mut self, input: &[u8], output: &mut Vec<u8>) -> Result<()> {
425 output.reserve(zstd::zstd_safe::compress_bound(input.len()));
426 self.0
427 .compress_to_buffer(input, output)
428 .context(error::IoSnafu)?;
429 Ok(())
430 }
431}
432
433fn get_compressor_variant(compression: CompressionType) -> Result<Box<dyn CompressorVariant>> {
434 match compression {
435 CompressionType::Zlib => Ok(Box::new(ZlibCompressor)),
436 CompressionType::Snappy => Ok(Box::new(SnappyCompressor(snap::raw::Encoder::new()))),
437 CompressionType::Lz4 => Ok(Box::new(Lz4Compressor)),
438 CompressionType::Zstd => Ok(Box::new(ZstdCompressor(
439 zstd::bulk::Compressor::new(0).context(error::IoSnafu)?,
440 ))),
441 CompressionType::Lzo => error::UnexpectedSnafu {
442 msg: "LZO compression is not supported by the ORC writer",
443 }
444 .fail(),
445 }
446}
447
448pub(crate) struct Compressor {
449 compression: Option<CompressionType>,
450 block_size: Option<usize>,
451 compressor: Option<Box<dyn CompressorVariant>>,
452 scratch: Vec<u8>,
453}
454
455impl Compressor {
456 pub(crate) fn new(
457 compression: Option<CompressionType>,
458 block_size: Option<usize>,
459 ) -> Result<Self> {
460 if let Some(block_size) = block_size {
461 if block_size == 0 || (block_size as u64) >= MAX_COMPRESSION_BLOCK_SIZE {
462 return error::UnexpectedSnafu {
463 msg: format!(
464 "compression block size must be in 1..{}, got {}",
465 MAX_COMPRESSION_BLOCK_SIZE, block_size
466 ),
467 }
468 .fail();
469 }
470 }
471
472 let (block_size, compressor) = match compression {
473 Some(compression) => {
474 let block_size = block_size.unwrap_or(DEFAULT_COMPRESSION_BLOCK_SIZE as usize);
475 (Some(block_size), Some(get_compressor_variant(compression)?))
476 }
477 None => (None, None),
478 };
479 Ok(Self {
480 compression,
481 block_size,
482 compressor,
483 scratch: Vec::new(),
484 })
485 }
486
487 pub(crate) fn compress<'a>(&mut self, input: &'a [u8]) -> Result<Cow<'a, [u8]>> {
488 let Some(compressor) = self.compressor.as_mut() else {
489 return Ok(Cow::Borrowed(input));
490 };
491 let block_size = self.block_size.expect("enabled compressor has block size");
492 let mut output = Vec::with_capacity(input.len());
493 for block in input.chunks(block_size) {
494 self.scratch.clear();
495 compressor.compress_block(block, &mut self.scratch)?;
496
497 let (payload, header) = if self.scratch.len() < block.len() {
498 (
499 self.scratch.as_slice(),
500 CompressionHeader::Compressed(self.scratch.len() as u32),
501 )
502 } else {
503 (block, CompressionHeader::Original(block.len() as u32))
504 };
505 output.extend_from_slice(&header.encode());
506 output.extend_from_slice(payload);
507 }
508 Ok(Cow::Owned(output))
509 }
510
511 pub(crate) fn compression(&self) -> Option<CompressionType> {
512 self.compression
513 }
514
515 pub(crate) fn block_size(&self) -> Option<usize> {
516 self.block_size
517 }
518}
519
520#[cfg(test)]
521mod tests {
522 use super::*;
523
524 #[test]
525 fn compression_header_roundtrip() {
526 for (expected, bytes) in [
527 (CompressionHeader::Original(5), [0x0b, 0x00, 0x00]),
528 (CompressionHeader::Compressed(100_000), [0x40, 0x0d, 0x03]),
529 (
530 CompressionHeader::Original((MAX_COMPRESSION_BLOCK_SIZE - 1) as u32),
531 [0xff, 0xff, 0xff],
532 ),
533 ] {
534 let decoded = CompressionHeader::decode(bytes);
535 assert_eq!(decoded, expected);
536 assert_eq!(decoded.encode(), bytes);
537 }
538 }
539
540 #[test]
541 fn rejects_invalid_writer_compression() {
542 assert!(Compressor::new(Some(CompressionType::Zstd), Some(0)).is_err());
543 assert!(Compressor::new(
544 Some(CompressionType::Zstd),
545 Some(MAX_COMPRESSION_BLOCK_SIZE as usize),
546 )
547 .is_err());
548 assert!(Compressor::new(Some(CompressionType::Lzo), None).is_err());
549 }
550
551 #[test]
552 fn handles_empty_original_and_chunked_blocks() {
553 let mut disabled = Compressor::new(None, None).unwrap();
554 let encoded = disabled.compress(b"uncompressed").unwrap();
555 assert!(matches!(encoded, Cow::Borrowed(b"uncompressed")));
556 assert!(disabled.compression().is_none());
557 assert!(disabled.block_size().is_none());
558
559 let mut compressor = Compressor::new(Some(CompressionType::Zstd), Some(4)).unwrap();
560 assert!(compressor.compress(&[]).unwrap().is_empty());
561
562 for compression_type in [
563 CompressionType::Zlib,
564 CompressionType::Snappy,
565 CompressionType::Lz4,
566 CompressionType::Zstd,
567 ] {
568 let mut compressor = Compressor::new(Some(compression_type), Some(64)).unwrap();
569 let encoded = compressor.compress(b"x").unwrap();
570 assert_eq!(encoded.as_ref(), [3, 0, 0, b'x']);
571 }
572
573 let encoded = compressor.compress(b"abcdefghi").unwrap();
574 assert_eq!(
575 encoded.as_ref(),
576 [9, 0, 0, b'a', b'b', b'c', b'd', 9, 0, 0, b'e', b'f', b'g', b'h', 3, 0, 0, b'i',]
577 );
578 }
579
580 #[test]
581 fn roundtrips_all_writer_codecs() {
582 let input = vec![0; 1024];
583 for compression_type in [
584 CompressionType::Zlib,
585 CompressionType::Snappy,
586 CompressionType::Lz4,
587 CompressionType::Zstd,
588 ] {
589 let mut compressor = Compressor::new(Some(compression_type), Some(64)).unwrap();
590 let encoded = compressor.compress(&input).unwrap();
591 let header = CompressionHeader::decode([encoded[0], encoded[1], encoded[2]]);
592 assert!(
593 matches!(header, CompressionHeader::Compressed(length) if length < 64),
594 "{compression_type} unexpectedly stored a compressible block as original"
595 );
596
597 let compression = Compression::from_proto(compression_type.to_proto(), Some(64));
598 let mut decoded = Vec::new();
599 Decompressor::new(Bytes::from(encoded.into_owned()), compression, Vec::new())
600 .read_to_end(&mut decoded)
601 .unwrap();
602 assert_eq!(decoded, input);
603 }
604 }
605}