oxirs_tdb/compression/
snappy.rs1use super::{
21 AdvancedCompressionType, CompressedData, CompressionAlgorithm, CompressionMetadata,
22 MAX_COMPRESSION_INPUT_SIZE,
23};
24use anyhow::{bail, Result};
25use std::collections::HashMap;
26use std::time::Instant;
27
28#[derive(Debug, Clone)]
33pub struct SnappyCompressor {
34 use_framing: bool,
36}
37
38impl SnappyCompressor {
39 pub fn new() -> Self {
41 Self {
42 use_framing: false, }
44 }
45
46 pub fn with_framing() -> Self {
51 Self { use_framing: true }
52 }
53
54 pub fn raw() -> Self {
56 Self { use_framing: false }
57 }
58}
59
60impl Default for SnappyCompressor {
61 fn default() -> Self {
62 Self::new()
63 }
64}
65
66impl CompressionAlgorithm for SnappyCompressor {
67 fn compress(&self, data: &[u8]) -> Result<CompressedData> {
68 if data.is_empty() {
69 return Ok(CompressedData {
70 data: Vec::new(),
71 metadata: CompressionMetadata {
72 algorithm: AdvancedCompressionType::Adaptive, original_size: 0,
74 compressed_size: 0,
75 compression_time_us: 0,
76 metadata: HashMap::new(),
77 },
78 });
79 }
80
81 if data.len() > MAX_COMPRESSION_INPUT_SIZE {
82 bail!(
83 "Input data too large: {} bytes (max: {})",
84 data.len(),
85 MAX_COMPRESSION_INPUT_SIZE
86 );
87 }
88
89 let start = Instant::now();
90
91 let compressed = if self.use_framing {
94 use std::io::Write;
96 let mut encoder = oxiarc_snappy::FrameEncoder::new(Vec::new());
97 encoder.write_all(data)?;
98 encoder
99 .finish()
100 .map_err(|e| anyhow::anyhow!("Snappy frame finish failed: {}", e))?
101 } else {
102 oxiarc_snappy::compress(data)
105 };
106
107 let compression_time = start.elapsed();
108
109 let mut metadata_map = HashMap::new();
110 metadata_map.insert("framing".to_string(), self.use_framing.to_string());
111 metadata_map.insert(
112 "compression_ratio".to_string(),
113 format!("{:.2}", compressed.len() as f64 / data.len() as f64),
114 );
115
116 let compressed_size = compressed.len() as u64;
117
118 Ok(CompressedData {
119 data: compressed,
120 metadata: CompressionMetadata {
121 algorithm: AdvancedCompressionType::Adaptive, original_size: data.len() as u64,
123 compressed_size,
124 compression_time_us: compression_time.as_micros() as u64,
125 metadata: metadata_map,
126 },
127 })
128 }
129
130 fn decompress(&self, compressed: &CompressedData) -> Result<Vec<u8>> {
131 if compressed.data.is_empty() {
132 return Ok(Vec::new());
133 }
134
135 let start = Instant::now();
136
137 let use_framing = compressed
139 .metadata
140 .metadata
141 .get("framing")
142 .and_then(|v| v.parse().ok())
143 .unwrap_or(self.use_framing);
144
145 let decompressed = if use_framing {
147 use std::io::Read;
149 let mut decoder = oxiarc_snappy::FrameDecoder::new(&compressed.data[..]);
150 let mut result = Vec::new();
151 decoder.read_to_end(&mut result)?;
152 result
153 } else {
154 oxiarc_snappy::decompress(&compressed.data)
156 .map_err(|e| anyhow::anyhow!("Snappy decompression failed: {}", e))?
157 };
158
159 let decompression_time = start.elapsed();
160
161 if decompressed.len() != compressed.metadata.original_size as usize {
163 bail!(
164 "Decompressed size mismatch: expected {}, got {}",
165 compressed.metadata.original_size,
166 decompressed.len()
167 );
168 }
169
170 log::trace!(
171 "Snappy decompression: {} bytes -> {} bytes in {:?}",
172 compressed.data.len(),
173 decompressed.len(),
174 decompression_time
175 );
176
177 Ok(decompressed)
178 }
179
180 fn algorithm_type(&self) -> AdvancedCompressionType {
181 AdvancedCompressionType::Adaptive }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn test_snappy_compress_empty() {
191 let compressor = SnappyCompressor::new();
192 let result = compressor.compress(&[]).unwrap();
193 assert_eq!(result.data.len(), 0);
194 assert_eq!(result.metadata.original_size, 0);
195 assert_eq!(result.metadata.compressed_size, 0);
196 }
197
198 #[test]
199 fn test_snappy_compress_decompress_raw() {
200 let compressor = SnappyCompressor::raw();
201 let original = b"Hello, World! This is a test of Snappy compression. ".repeat(100);
202
203 let compressed = compressor.compress(&original).unwrap();
204 assert!(compressed.data.len() < original.len());
205 assert_eq!(compressed.metadata.original_size, original.len() as u64);
206
207 let decompressed = compressor.decompress(&compressed).unwrap();
208 assert_eq!(decompressed, original);
209 }
210
211 #[test]
212 fn test_snappy_compress_decompress_framed() {
213 let compressor = SnappyCompressor::with_framing();
214 let original = b"Hello, World! This is a test of Snappy framed compression. ".repeat(100);
215
216 let compressed = compressor.compress(&original).unwrap();
217 assert!(compressed.data.len() < original.len());
218 assert_eq!(compressed.metadata.original_size, original.len() as u64);
219
220 let decompressed = compressor.decompress(&compressed).unwrap();
221 assert_eq!(decompressed, original);
222 }
223
224 #[test]
225 fn test_snappy_raw_format_fidelity() {
226 let compressor = SnappyCompressor::raw();
229 let original = b"oxiarc raw snappy fidelity check ".repeat(40);
230
231 let compressed = compressor.compress(&original).unwrap();
232 let decoded = oxiarc_snappy::decompress(&compressed.data).unwrap();
233 assert_eq!(decoded, original);
234
235 let direct = oxiarc_snappy::compress(&original);
237 let cd = CompressedData {
238 data: direct,
239 metadata: CompressionMetadata {
240 algorithm: AdvancedCompressionType::Adaptive,
241 original_size: original.len() as u64,
242 compressed_size: 0,
243 compression_time_us: 0,
244 metadata: {
245 let mut m = std::collections::HashMap::new();
246 m.insert("framing".to_string(), "false".to_string());
247 m
248 },
249 },
250 };
251 assert_eq!(compressor.decompress(&cd).unwrap(), original);
252 }
253
254 #[test]
255 fn test_snappy_framed_format_fidelity() {
256 let compressor = SnappyCompressor::with_framing();
259 let original = b"oxiarc framed snappy fidelity check ".repeat(40);
260
261 let compressed = compressor.compress(&original).unwrap();
262 assert!(compressed.data.len() >= 10);
263 assert_eq!(compressed.data[0], 0xFF);
264 assert_eq!(&compressed.data[4..10], b"sNaPpY");
265
266 let decompressed = compressor.decompress(&compressed).unwrap();
267 assert_eq!(decompressed, original);
268 }
269
270 #[test]
271 fn test_snappy_format_comparison() {
272 let data = b"The quick brown fox jumps over the lazy dog. ".repeat(50);
273
274 let raw = SnappyCompressor::raw();
275 let framed = SnappyCompressor::with_framing();
276
277 let raw_result = raw.compress(&data).unwrap();
278 let framed_result = framed.compress(&data).unwrap();
279
280 assert_eq!(raw.decompress(&raw_result).unwrap(), data);
282 assert_eq!(framed.decompress(&framed_result).unwrap(), data);
283
284 println!("Raw: {} bytes", raw_result.data.len());
286 println!("Framed: {} bytes", framed_result.data.len());
287 assert!(raw_result.data.len() <= framed_result.data.len());
288 }
289
290 #[test]
291 fn test_snappy_highly_compressible_data() {
292 let compressor = SnappyCompressor::new();
293 let data = vec![b'A'; 10000]; let compressed = compressor.compress(&data).unwrap();
296 let decompressed = compressor.decompress(&compressed).unwrap();
297
298 assert_eq!(decompressed, data);
299 assert!(compressed.data.len() < data.len() / 5);
301 println!(
302 "Snappy highly compressible: {} -> {} bytes ({:.2}x)",
303 data.len(),
304 compressed.data.len(),
305 data.len() as f64 / compressed.data.len() as f64
306 );
307 }
308
309 #[test]
310 fn test_snappy_incompressible_data() {
311 let compressor = SnappyCompressor::new();
312 use scirs2_core::random::rng;
314 use scirs2_core::RngExt;
315
316 let mut rng = rng();
317 let data: Vec<u8> = (0..1000).map(|_| rng.random_range(0..256) as u8).collect();
318
319 let compressed = compressor.compress(&data).unwrap();
320 let decompressed = compressor.decompress(&compressed).unwrap();
321
322 assert_eq!(decompressed, data);
323 println!(
325 "Snappy incompressible: {} -> {} bytes",
326 data.len(),
327 compressed.data.len()
328 );
329 }
330
331 #[test]
332 fn test_snappy_too_large_input() {
333 let compressor = SnappyCompressor::new();
334 let data = vec![0u8; MAX_COMPRESSION_INPUT_SIZE + 1];
335
336 let result = compressor.compress(&data);
337 assert!(result.is_err());
338 }
339
340 #[test]
341 fn test_snappy_metadata() {
342 let compressor = SnappyCompressor::with_framing();
343 let data = b"Testing metadata collection with Snappy".repeat(10);
344
345 let compressed = compressor.compress(&data).unwrap();
346
347 assert_eq!(compressed.metadata.metadata.get("framing").unwrap(), "true");
348 assert!(compressed
349 .metadata
350 .metadata
351 .contains_key("compression_ratio"));
352 assert!(compressed.metadata.compression_time_us > 0);
353 }
354
355 #[test]
356 fn test_snappy_small_data() {
357 let compressor = SnappyCompressor::new();
358 let data = b"Small";
359
360 let compressed = compressor.compress(data).unwrap();
361 let decompressed = compressor.decompress(&compressed).unwrap();
362
363 assert_eq!(decompressed, data);
364 println!(
366 "Snappy small data: {} -> {} bytes",
367 data.len(),
368 compressed.data.len()
369 );
370 }
371
372 #[test]
373 fn test_snappy_rdf_data_compression() {
374 let compressor = SnappyCompressor::new();
375 let rdf_data = b"<http://example.org/resource/1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/Class> .\n"
377 .repeat(100);
378
379 let compressed = compressor.compress(&rdf_data).unwrap();
380 let decompressed = compressor.decompress(&compressed).unwrap();
381
382 assert_eq!(decompressed, rdf_data);
383
384 let ratio = rdf_data.len() as f64 / compressed.data.len() as f64;
386 println!("Snappy RDF compression ratio: {:.2}x", ratio);
387 assert!(ratio > 1.5); }
389
390 #[test]
391 fn test_snappy_roundtrip_consistency() {
392 let compressor = SnappyCompressor::new();
393 let data = b"Consistency test data".repeat(50);
394
395 let mut current = data.to_vec();
397 for _ in 0..5 {
398 let compressed = compressor.compress(¤t).unwrap();
399 current = compressor.decompress(&compressed).unwrap();
400 }
401
402 assert_eq!(current, data);
403 }
404}