use crate::error::{Result, TdbError};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompressionStrategy {
Speed,
Balanced,
Ratio,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum CompressionAlgorithm {
None = 0,
Lz4 = 1,
Zstd = 2,
Brotli = 3,
Snappy = 4,
Prefix = 5,
Delta = 6,
RunLength = 7,
Bitmap = 8,
}
#[derive(Debug, Clone, Copy)]
pub struct CompressionLevel(u8);
impl CompressionLevel {
pub const FAST: Self = Self(1);
pub const DEFAULT: Self = Self(5);
pub const BEST: Self = Self(9);
pub fn new(level: u8) -> Self {
Self(level.min(9))
}
pub fn value(&self) -> u8 {
self.0
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompressionStats {
pub algorithm: CompressionAlgorithm,
pub original_size: usize,
pub compressed_size: usize,
pub ratio: f64,
pub compression_time_us: u64,
pub decompression_time_us: Option<u64>,
}
impl CompressionStats {
pub fn new(
algorithm: CompressionAlgorithm,
original_size: usize,
compressed_size: usize,
compression_time_us: u64,
) -> Self {
let ratio = if original_size > 0 {
compressed_size as f64 / original_size as f64
} else {
1.0
};
Self {
algorithm,
original_size,
compressed_size,
ratio,
compression_time_us,
decompression_time_us: None,
}
}
pub fn savings_percent(&self) -> f64 {
(1.0 - self.ratio) * 100.0
}
}
pub struct UnifiedCompression {
default_algorithm: CompressionAlgorithm,
default_level: CompressionLevel,
strategy: CompressionStrategy,
}
impl UnifiedCompression {
pub fn new() -> Self {
Self {
default_algorithm: CompressionAlgorithm::Zstd,
default_level: CompressionLevel::DEFAULT,
strategy: CompressionStrategy::Balanced,
}
}
pub fn with_algorithm(mut self, algorithm: CompressionAlgorithm) -> Self {
self.default_algorithm = algorithm;
self
}
pub fn with_level(mut self, level: CompressionLevel) -> Self {
self.default_level = level;
self
}
pub fn with_strategy(mut self, strategy: CompressionStrategy) -> Self {
self.strategy = strategy;
self
}
pub fn compress(&self, data: &[u8]) -> Result<Vec<u8>> {
self.compress_with(data, self.default_algorithm, self.default_level)
}
pub fn compress_with(
&self,
data: &[u8],
algorithm: CompressionAlgorithm,
level: CompressionLevel,
) -> Result<Vec<u8>> {
use std::time::Instant;
let start = Instant::now();
let compressed = match algorithm {
CompressionAlgorithm::None => data.to_vec(),
CompressionAlgorithm::Lz4 => self.compress_lz4(data)?,
CompressionAlgorithm::Zstd => self.compress_zstd(data, level.value() as i32)?,
CompressionAlgorithm::Brotli => self.compress_brotli(data, level.value() as u32)?,
CompressionAlgorithm::Snappy => self.compress_snappy(data)?,
_ => {
return Err(TdbError::Other(format!(
"Algorithm {:?} not yet implemented",
algorithm
)))
}
};
Ok(compressed)
}
pub fn decompress(&self, data: &[u8], algorithm: CompressionAlgorithm) -> Result<Vec<u8>> {
match algorithm {
CompressionAlgorithm::None => Ok(data.to_vec()),
CompressionAlgorithm::Lz4 => self.decompress_lz4(data),
CompressionAlgorithm::Zstd => self.decompress_zstd(data),
CompressionAlgorithm::Brotli => self.decompress_brotli(data),
CompressionAlgorithm::Snappy => self.decompress_snappy(data),
_ => Err(TdbError::Other(format!(
"Algorithm {:?} not yet implemented",
algorithm
))),
}
}
pub fn select_algorithm(&self, data: &[u8]) -> CompressionAlgorithm {
match self.strategy {
CompressionStrategy::Speed => CompressionAlgorithm::Snappy,
CompressionStrategy::Balanced => {
if data.len() < 1024 {
CompressionAlgorithm::Lz4
} else {
CompressionAlgorithm::Zstd
}
}
CompressionStrategy::Ratio => CompressionAlgorithm::Brotli,
}
}
fn compress_lz4(&self, data: &[u8]) -> Result<Vec<u8>> {
oxiarc_lz4::compress(data)
.map_err(|e| TdbError::Other(format!("LZ4 compression failed: {:?}", e)))
}
fn decompress_lz4(&self, data: &[u8]) -> Result<Vec<u8>> {
oxiarc_lz4::decompress(data, 100 * 1024 * 1024)
.map_err(|e| TdbError::Other(format!("LZ4 decompression failed: {:?}", e)))
}
fn compress_zstd(&self, data: &[u8], level: i32) -> Result<Vec<u8>> {
oxiarc_zstd::encode_all(data, level)
.map_err(|e| TdbError::Other(format!("Zstd compression failed: {}", e)))
}
fn decompress_zstd(&self, data: &[u8]) -> Result<Vec<u8>> {
oxiarc_zstd::decode_all(data)
.map_err(|e| TdbError::Other(format!("Zstd decompression failed: {}", e)))
}
fn compress_brotli(&self, data: &[u8], level: u32) -> Result<Vec<u8>> {
oxiarc_brotli::compress(data, level)
.map_err(|e| TdbError::Other(format!("Brotli compression failed: {}", e)))
}
fn decompress_brotli(&self, data: &[u8]) -> Result<Vec<u8>> {
oxiarc_brotli::decompress(data)
.map_err(|e| TdbError::Other(format!("Brotli decompression failed: {}", e)))
}
fn compress_snappy(&self, data: &[u8]) -> Result<Vec<u8>> {
Ok(oxiarc_snappy::compress(data))
}
fn decompress_snappy(&self, data: &[u8]) -> Result<Vec<u8>> {
oxiarc_snappy::decompress(data)
.map_err(|e| TdbError::Other(format!("Snappy decompression failed: {}", e)))
}
pub fn benchmark(&self, data: &[u8]) -> Vec<CompressionStats> {
let mut results = Vec::new();
let algorithms = vec![
CompressionAlgorithm::Lz4,
CompressionAlgorithm::Zstd,
CompressionAlgorithm::Brotli,
CompressionAlgorithm::Snappy,
];
for algo in algorithms {
if let Ok(compressed) = self.compress_with(data, algo, CompressionLevel::DEFAULT) {
let stats = CompressionStats::new(
algo,
data.len(),
compressed.len(),
0, );
results.push(stats);
}
}
results
}
}
impl Default for UnifiedCompression {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unified_compression_creation() {
let _compression = UnifiedCompression::new();
}
#[test]
fn test_compression_level() {
assert_eq!(CompressionLevel::FAST.value(), 1);
assert_eq!(CompressionLevel::DEFAULT.value(), 5);
assert_eq!(CompressionLevel::BEST.value(), 9);
let custom = CompressionLevel::new(7);
assert_eq!(custom.value(), 7);
let clamped = CompressionLevel::new(15);
assert_eq!(clamped.value(), 9);
}
#[test]
fn test_lz4_roundtrip() {
let compression = UnifiedCompression::new();
let data = b"Hello, World! This is a test of LZ4 compression.";
let compressed = compression
.compress_with(data, CompressionAlgorithm::Lz4, CompressionLevel::DEFAULT)
.unwrap();
let decompressed = compression
.decompress(&compressed, CompressionAlgorithm::Lz4)
.unwrap();
assert_eq!(decompressed, data);
}
#[test]
fn test_zstd_roundtrip() {
let compression = UnifiedCompression::new();
let data = b"Zstandard compression test data with some repetition repetition repetition";
let compressed = compression
.compress_with(data, CompressionAlgorithm::Zstd, CompressionLevel::DEFAULT)
.unwrap();
let decompressed = compression
.decompress(&compressed, CompressionAlgorithm::Zstd)
.unwrap();
assert_eq!(decompressed, data);
}
#[test]
fn test_snappy_roundtrip() {
let compression = UnifiedCompression::new();
let data = b"Snappy is designed for speed rather than maximum compression";
let compressed = compression
.compress_with(
data,
CompressionAlgorithm::Snappy,
CompressionLevel::DEFAULT,
)
.unwrap();
let decompressed = compression
.decompress(&compressed, CompressionAlgorithm::Snappy)
.unwrap();
assert_eq!(decompressed, data);
}
#[test]
fn test_brotli_roundtrip() {
let compression = UnifiedCompression::new();
let data = b"Brotli excels at compressing text and structured web content. ".repeat(20);
let compressed = compression
.compress_with(
&data,
CompressionAlgorithm::Brotli,
CompressionLevel::DEFAULT,
)
.unwrap();
let decompressed = compression
.decompress(&compressed, CompressionAlgorithm::Brotli)
.unwrap();
assert_eq!(decompressed, data);
}
#[test]
fn test_brotli_roundtrip_incompressible() {
use scirs2_core::random::rng;
use scirs2_core::RngExt;
let mut r = rng();
let data: Vec<u8> = (0..1500).map(|_| r.random_range(0..256) as u8).collect();
let compression = UnifiedCompression::new();
let compressed = compression
.compress_with(
&data,
CompressionAlgorithm::Brotli,
CompressionLevel::DEFAULT,
)
.unwrap();
let decompressed = compression
.decompress(&compressed, CompressionAlgorithm::Brotli)
.unwrap();
assert_eq!(decompressed, data);
}
#[test]
fn test_algorithm_selection() {
let compression = UnifiedCompression::new().with_strategy(CompressionStrategy::Speed);
assert_eq!(
compression.select_algorithm(b"test"),
CompressionAlgorithm::Snappy
);
let compression = UnifiedCompression::new().with_strategy(CompressionStrategy::Ratio);
assert_eq!(
compression.select_algorithm(b"test"),
CompressionAlgorithm::Brotli
);
}
#[test]
fn test_compression_stats() {
let stats = CompressionStats::new(CompressionAlgorithm::Zstd, 1000, 500, 100);
assert_eq!(stats.ratio, 0.5);
assert_eq!(stats.savings_percent(), 50.0);
}
#[test]
fn test_benchmark() {
let compression = UnifiedCompression::new();
let data = b"Sample data for benchmarking compression algorithms. ".repeat(10);
let results = compression.benchmark(&data);
assert!(results.len() >= 4); for result in &results {
assert!(result.compressed_size > 0);
assert!(result.compressed_size <= result.original_size);
}
}
#[test]
fn test_none_algorithm() {
let compression = UnifiedCompression::new();
let data = b"Test data";
let compressed = compression
.compress_with(data, CompressionAlgorithm::None, CompressionLevel::DEFAULT)
.unwrap();
assert_eq!(compressed, data);
let decompressed = compression
.decompress(&compressed, CompressionAlgorithm::None)
.unwrap();
assert_eq!(decompressed, data);
}
}