1use crate::compression::{
4 bitmap::{BitmapRoaringEncoder, BitmapWAHEncoder},
5 column_store::ColumnStoreCompressor,
6 delta::DeltaEncoder,
7 dictionary::AdaptiveDictionary,
8 frame_of_reference::FrameOfReferenceEncoder,
9 run_length::RunLengthEncoder,
10 AdvancedCompressionType, CompressedData, CompressionAlgorithm,
11};
12use anyhow::{anyhow, Result};
13use std::time::Instant;
14
15pub struct AdaptiveCompressor {
17 sample_size: usize,
19 threshold: f64,
21}
22
23impl AdaptiveCompressor {
24 pub fn new(sample_size: usize, threshold: f64) -> Self {
26 Self {
27 sample_size,
28 threshold,
29 }
30 }
31
32 pub fn select_best_algorithm(&self, data: &[u8]) -> AdvancedCompressionType {
34 if data.len() < 10 {
35 return AdvancedCompressionType::RunLength; }
37
38 let sample_data = if data.len() > self.sample_size {
39 &data[..self.sample_size]
40 } else {
41 data
42 };
43
44 let stats = self.analyze_data(sample_data);
46
47 if stats.repetition_ratio < self.threshold && stats.sparsity < self.threshold {
49 return AdvancedCompressionType::RunLength;
51 }
52
53 if stats.sparsity > 0.9 {
55 if stats.bit_runs > stats.byte_runs {
57 AdvancedCompressionType::BitmapRoaring
58 } else {
59 AdvancedCompressionType::BitmapWAH
60 }
61 } else if stats.repetition_ratio > 0.5 {
62 AdvancedCompressionType::RunLength
64 } else if stats.is_sorted && stats.delta_efficiency > 0.7 {
65 if stats.range_ratio < 0.1 {
67 AdvancedCompressionType::FrameOfReference
68 } else {
69 AdvancedCompressionType::Delta
70 }
71 } else if stats.dictionary_efficiency > 0.6 {
72 AdvancedCompressionType::AdaptiveDictionary
74 } else if data.len() % 8 == 0 && stats.structured_score > 0.5 {
75 AdvancedCompressionType::ColumnStore
77 } else {
78 AdvancedCompressionType::RunLength
80 }
81 }
82
83 fn analyze_data(&self, data: &[u8]) -> DataStats {
85 let mut stats = DataStats::default();
86
87 if data.is_empty() {
88 return stats;
89 }
90
91 stats.length = data.len();
93 stats.unique_bytes = data.iter().collect::<std::collections::HashSet<_>>().len();
94
95 let zero_count = data.iter().filter(|&&b| b == 0).count();
97 stats.sparsity = zero_count as f64 / data.len() as f64;
98
99 let mut repetitions = 0;
101 let mut current_byte = data[0];
102 let mut run_length = 1;
103
104 for &byte in &data[1..] {
105 if byte == current_byte {
106 run_length += 1;
107 } else {
108 if run_length > 1 {
109 repetitions += run_length;
110 }
111 stats.byte_runs += 1;
112 current_byte = byte;
113 run_length = 1;
114 }
115 }
116
117 if run_length > 1 {
118 repetitions += run_length;
119 }
120 stats.byte_runs += 1;
121
122 stats.repetition_ratio = repetitions as f64 / data.len() as f64;
123
124 let mut bit_runs = 0;
126 let mut current_bit = data[0] & 1;
127
128 for &byte in data {
129 for i in 0..8 {
130 let bit = (byte >> i) & 1;
131 if bit != current_bit {
132 bit_runs += 1;
133 current_bit = bit;
134 }
135 }
136 }
137 stats.bit_runs = bit_runs;
138
139 if data.len() >= 8 {
141 let u64_values: Vec<u64> = data
142 .chunks_exact(8)
143 .map(|chunk| {
144 u64::from_le_bytes([
145 chunk[0], chunk[1], chunk[2], chunk[3], chunk[4], chunk[5], chunk[6],
146 chunk[7],
147 ])
148 })
149 .collect();
150
151 if u64_values.len() > 1 {
152 let mut sorted_count = 0;
153 for i in 1..u64_values.len() {
154 if u64_values[i] >= u64_values[i - 1] {
155 sorted_count += 1;
156 }
157 }
158 stats.is_sorted = sorted_count as f64 / (u64_values.len() - 1) as f64 > 0.8;
159
160 if let (Some(&min_val), Some(&max_val)) =
162 (u64_values.iter().min(), u64_values.iter().max())
163 {
164 stats.range_ratio = if max_val > 0 {
165 (max_val - min_val) as f64 / max_val as f64
166 } else {
167 0.0
168 };
169 }
170
171 let mut small_deltas = 0;
173 for i in 1..u64_values.len() {
174 if u64_values[i].abs_diff(u64_values[i - 1]) < 65536 {
175 small_deltas += 1;
176 }
177 }
178 stats.delta_efficiency = small_deltas as f64 / (u64_values.len() - 1) as f64;
179 }
180 }
181
182 let mut word_count = 0;
184 let mut word_bytes = 0;
185 let mut in_word = false;
186
187 for &byte in data {
188 if byte.is_ascii_alphanumeric() || byte == b'_' || byte == b'-' {
189 if !in_word {
190 word_count += 1;
191 in_word = true;
192 }
193 word_bytes += 1;
194 } else {
195 in_word = false;
196 }
197 }
198
199 if word_count > 0 {
200 stats.dictionary_efficiency = word_bytes as f64 / data.len() as f64;
201 }
202
203 if data.len() >= 16 {
205 let mut pattern_score = 0.0;
206 let chunk_size = 8;
207 let chunks: Vec<_> = data.chunks_exact(chunk_size).collect();
208
209 if chunks.len() > 1 {
210 let mut similar_chunks = 0;
211 for i in 1..chunks.len() {
212 let mut differences = 0;
213 #[allow(clippy::needless_range_loop)]
215 for j in 0..chunk_size {
216 if chunks[i][j] != chunks[0][j] {
217 differences += 1;
218 }
219 }
220 if differences <= chunk_size / 2 {
221 similar_chunks += 1;
222 }
223 }
224 pattern_score = similar_chunks as f64 / (chunks.len() - 1) as f64;
225 }
226 stats.structured_score = pattern_score;
227 }
228
229 stats
230 }
231
232 pub fn compress_adaptive(&self, data: &[u8]) -> Result<CompressedData> {
234 let algorithm = self.select_best_algorithm(data);
235
236 let start = Instant::now();
237 let result = match algorithm {
238 AdvancedCompressionType::RunLength => {
239 let encoder = RunLengthEncoder;
240 encoder.compress(data)?
241 }
242 AdvancedCompressionType::Delta => {
243 let encoder = DeltaEncoder;
244 encoder.compress(data)?
245 }
246 AdvancedCompressionType::FrameOfReference => {
247 let encoder = FrameOfReferenceEncoder::default();
248 encoder.compress(data)?
249 }
250 AdvancedCompressionType::AdaptiveDictionary => {
251 let encoder = AdaptiveDictionary::new();
252 encoder.compress(data)?
253 }
254 AdvancedCompressionType::BitmapWAH => {
255 let encoder = BitmapWAHEncoder;
256 encoder.compress(data)?
257 }
258 AdvancedCompressionType::BitmapRoaring => {
259 let encoder = BitmapRoaringEncoder;
260 encoder.compress(data)?
261 }
262 AdvancedCompressionType::ColumnStore => {
263 let encoder = ColumnStoreCompressor::new();
264 encoder.compress(data)?
265 }
266 AdvancedCompressionType::Lz4 => {
267 use crate::compression::Lz4Compressor;
268 let encoder = Lz4Compressor::balanced();
269 encoder.compress(data)?
270 }
271 AdvancedCompressionType::Zstd => {
272 use crate::compression::ZstdCompressor;
273 let encoder = ZstdCompressor::balanced();
274 encoder.compress(data)?
275 }
276 AdvancedCompressionType::Snappy => {
277 use crate::compression::SnappyCompressor;
278 let encoder = SnappyCompressor::new();
279 encoder.compress(data)?
280 }
281 AdvancedCompressionType::Brotli => {
282 use crate::compression::BrotliCompressor;
283 let encoder = BrotliCompressor::balanced();
284 encoder.compress(data)?
285 }
286 AdvancedCompressionType::Adaptive => {
287 return Err(anyhow!("Recursive adaptive compression not allowed"));
288 }
289 };
290 let selection_time = start.elapsed();
291
292 let mut metadata = result.metadata;
294 metadata.algorithm = AdvancedCompressionType::Adaptive;
295 metadata.compression_time_us += selection_time.as_micros() as u64;
296 metadata
297 .metadata
298 .insert("selected_algorithm".to_string(), algorithm.to_string());
299
300 Ok(CompressedData {
301 data: result.data,
302 metadata,
303 })
304 }
305
306 pub fn decompress_adaptive(&self, compressed: &CompressedData) -> Result<Vec<u8>> {
308 if compressed.metadata.algorithm != AdvancedCompressionType::Adaptive {
309 return Err(anyhow!(
310 "Invalid compression algorithm: expected Adaptive, got {}",
311 compressed.metadata.algorithm
312 ));
313 }
314
315 let selected_algorithm = compressed
317 .metadata
318 .metadata
319 .get("selected_algorithm")
320 .ok_or_else(|| {
321 anyhow!("Missing selected algorithm in adaptive compression metadata")
322 })?;
323
324 let algorithm = match selected_algorithm.as_str() {
325 "RunLength" => AdvancedCompressionType::RunLength,
326 "Delta" => AdvancedCompressionType::Delta,
327 "FrameOfReference" => AdvancedCompressionType::FrameOfReference,
328 "AdaptiveDictionary" => AdvancedCompressionType::AdaptiveDictionary,
329 "BitmapWAH" => AdvancedCompressionType::BitmapWAH,
330 "BitmapRoaring" => AdvancedCompressionType::BitmapRoaring,
331 "ColumnStore" => AdvancedCompressionType::ColumnStore,
332 "LZ4" => AdvancedCompressionType::Lz4,
333 "Zstd" => AdvancedCompressionType::Zstd,
334 "Snappy" => AdvancedCompressionType::Snappy,
335 "Brotli" => AdvancedCompressionType::Brotli,
336 _ => {
337 return Err(anyhow!(
338 "Unknown selected algorithm: {}",
339 selected_algorithm
340 ))
341 }
342 };
343
344 let mut temp_compressed = compressed.clone();
346 temp_compressed.metadata.algorithm = algorithm;
347
348 match algorithm {
350 AdvancedCompressionType::RunLength => {
351 let encoder = RunLengthEncoder;
352 encoder.decompress(&temp_compressed)
353 }
354 AdvancedCompressionType::Delta => {
355 let encoder = DeltaEncoder;
356 encoder.decompress(&temp_compressed)
357 }
358 AdvancedCompressionType::FrameOfReference => {
359 let encoder = FrameOfReferenceEncoder::default();
360 encoder.decompress(&temp_compressed)
361 }
362 AdvancedCompressionType::AdaptiveDictionary => {
363 let encoder = AdaptiveDictionary::new();
364 encoder.decompress(&temp_compressed)
365 }
366 AdvancedCompressionType::BitmapWAH => {
367 let encoder = BitmapWAHEncoder;
368 encoder.decompress(&temp_compressed)
369 }
370 AdvancedCompressionType::BitmapRoaring => {
371 let encoder = BitmapRoaringEncoder;
372 encoder.decompress(&temp_compressed)
373 }
374 AdvancedCompressionType::ColumnStore => {
375 let encoder = ColumnStoreCompressor::new();
376 encoder.decompress(&temp_compressed)
377 }
378 AdvancedCompressionType::Lz4 => {
379 use crate::compression::Lz4Compressor;
380 let encoder = Lz4Compressor::balanced();
381 encoder.decompress(&temp_compressed)
382 }
383 AdvancedCompressionType::Zstd => {
384 use crate::compression::ZstdCompressor;
385 let encoder = ZstdCompressor::balanced();
386 encoder.decompress(&temp_compressed)
387 }
388 AdvancedCompressionType::Snappy => {
389 use crate::compression::SnappyCompressor;
390 let encoder = SnappyCompressor::new();
391 encoder.decompress(&temp_compressed)
392 }
393 AdvancedCompressionType::Brotli => {
394 use crate::compression::BrotliCompressor;
395 let encoder = BrotliCompressor::balanced();
396 encoder.decompress(&temp_compressed)
397 }
398 AdvancedCompressionType::Adaptive => {
399 Err(anyhow!("Recursive adaptive compression not allowed"))
400 }
401 }
402 }
403}
404
405impl Default for AdaptiveCompressor {
406 fn default() -> Self {
407 Self::new(1024, 0.8) }
409}
410
411impl CompressionAlgorithm for AdaptiveCompressor {
412 fn compress(&self, data: &[u8]) -> Result<CompressedData> {
413 self.compress_adaptive(data)
414 }
415
416 fn decompress(&self, compressed: &CompressedData) -> Result<Vec<u8>> {
417 self.decompress_adaptive(compressed)
418 }
419
420 fn algorithm_type(&self) -> AdvancedCompressionType {
421 AdvancedCompressionType::Adaptive
422 }
423}
424
425#[derive(Debug, Default)]
427struct DataStats {
428 length: usize,
429 unique_bytes: usize,
430 sparsity: f64,
431 repetition_ratio: f64,
432 byte_runs: usize,
433 bit_runs: usize,
434 is_sorted: bool,
435 range_ratio: f64,
436 delta_efficiency: f64,
437 dictionary_efficiency: f64,
438 structured_score: f64,
439}
440
441#[cfg(test)]
442mod tests {
443 use super::*;
444
445 #[test]
446 fn test_adaptive_compressor_basic() {
447 let compressor = AdaptiveCompressor::default();
448
449 let repetitive_data = vec![1u8; 100];
451 let algorithm = compressor.select_best_algorithm(&repetitive_data);
452 assert_eq!(algorithm, AdvancedCompressionType::RunLength);
453
454 let compressed = compressor.compress(&repetitive_data).unwrap();
455 assert_eq!(
456 compressed.metadata.algorithm,
457 AdvancedCompressionType::Adaptive
458 );
459 assert!(compressed
460 .metadata
461 .metadata
462 .contains_key("selected_algorithm"));
463
464 let decompressed = compressor.decompress(&compressed).unwrap();
465 assert_eq!(repetitive_data, decompressed);
466 }
467
468 #[test]
469 fn test_sparse_data_selection() {
470 let compressor = AdaptiveCompressor::default();
471
472 let mut sparse_data = vec![0u8; 1000];
474 sparse_data[10] = 1;
475 sparse_data[100] = 1;
476 sparse_data[500] = 1;
477
478 let algorithm = compressor.select_best_algorithm(&sparse_data);
479 assert!(matches!(
481 algorithm,
482 AdvancedCompressionType::BitmapWAH | AdvancedCompressionType::BitmapRoaring
483 ));
484 }
485
486 #[test]
487 fn test_empty_data() {
488 let compressor = AdaptiveCompressor::default();
489 let data = vec![];
490
491 let compressed = compressor.compress(&data).unwrap();
492 let decompressed = compressor.decompress(&compressed).unwrap();
493 assert_eq!(data, decompressed);
494 }
495
496 #[test]
497 fn test_small_data() {
498 let compressor = AdaptiveCompressor::default();
499 let data = vec![1, 2, 3];
500
501 let algorithm = compressor.select_best_algorithm(&data);
502 assert_eq!(algorithm, AdvancedCompressionType::RunLength); }
504
505 #[test]
506 fn test_data_analysis() {
507 let compressor = AdaptiveCompressor::default();
508
509 let sorted_data: Vec<u8> = (0..64u64).flat_map(|i| i.to_le_bytes()).collect();
511
512 let stats = compressor.analyze_data(&sorted_data);
513 assert!(stats.is_sorted);
514 assert!(stats.delta_efficiency > 0.5);
515 }
516}
517
518use crate::error::{Result as TdbResult, TdbError};
523
524#[derive(Debug, Clone, Copy, PartialEq, Eq)]
529pub enum CodecType {
530 ZstdBasic,
532 ZstdDict,
534 Delta,
536 None,
538}
539
540impl std::fmt::Display for CodecType {
541 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
542 match self {
543 CodecType::ZstdBasic => write!(f, "ZstdBasic"),
544 CodecType::ZstdDict => write!(f, "ZstdDict"),
545 CodecType::Delta => write!(f, "Delta"),
546 CodecType::None => write!(f, "None"),
547 }
548 }
549}
550
551const CODEC_MAGIC_ZSTD_BASIC: u8 = 0x01;
553const CODEC_MAGIC_ZSTD_DICT: u8 = 0x02;
554const CODEC_MAGIC_DELTA: u8 = 0x03;
555const CODEC_MAGIC_NONE: u8 = 0x00;
556
557pub struct AdaptiveCodec {
587 pub threshold_bytes: usize,
590 pub zstd_dict: Option<Vec<u8>>,
592 pub zstd_level: i32,
594}
595
596impl AdaptiveCodec {
597 pub fn new(threshold_bytes: usize) -> Self {
603 Self {
604 threshold_bytes,
605 zstd_dict: None,
606 zstd_level: 3,
607 }
608 }
609
610 pub fn with_dict(threshold_bytes: usize, dict: Vec<u8>, zstd_level: i32) -> Self {
612 Self {
613 threshold_bytes,
614 zstd_dict: Some(dict),
615 zstd_level: zstd_level.clamp(1, 22),
616 }
617 }
618
619 pub fn select_codec(&self, data: &[u8]) -> CodecType {
626 if data.len() < self.threshold_bytes {
627 return CodecType::None;
628 }
629
630 if self.zstd_dict.is_some() {
633 let printable_ratio = Self::compute_printable_ratio(data);
634 if printable_ratio > 0.7 {
636 return CodecType::ZstdDict;
637 }
638 }
639
640 if Self::is_mostly_sorted(data) {
642 return CodecType::Delta;
643 }
644
645 CodecType::ZstdBasic
647 }
648
649 pub fn compress(&self, data: &[u8]) -> TdbResult<(Vec<u8>, CodecType)> {
655 let codec = self.select_codec(data);
656 let compressed = match codec {
657 CodecType::None => {
658 let mut out = Vec::with_capacity(1 + data.len());
659 out.push(CODEC_MAGIC_NONE);
660 out.extend_from_slice(data);
661 out
662 }
663 CodecType::ZstdBasic => {
664 let payload = Self::zstd_compress_raw(data, self.zstd_level, None)?;
665 let mut out = Vec::with_capacity(1 + payload.len());
666 out.push(CODEC_MAGIC_ZSTD_BASIC);
667 out.extend_from_slice(&payload);
668 out
669 }
670 CodecType::ZstdDict => {
671 let dict = self.zstd_dict.as_deref().ok_or_else(|| {
672 TdbError::Other("ZstdDict selected but no dictionary loaded".to_string())
673 })?;
674 let payload = Self::zstd_compress_raw(data, self.zstd_level, Some(dict))?;
675 let mut out = Vec::with_capacity(1 + payload.len());
676 out.push(CODEC_MAGIC_ZSTD_DICT);
677 out.extend_from_slice(&payload);
678 out
679 }
680 CodecType::Delta => {
681 let payload = Self::delta_compress(data)?;
682 let mut out = Vec::with_capacity(1 + payload.len());
683 out.push(CODEC_MAGIC_DELTA);
684 out.extend_from_slice(&payload);
685 out
686 }
687 };
688 Ok((compressed, codec))
689 }
690
691 pub fn decompress(&self, data: &[u8], codec: CodecType) -> TdbResult<Vec<u8>> {
697 if data.is_empty() {
698 return Ok(Vec::new());
699 }
700
701 let header = data[0];
703 let payload = &data[1..];
704
705 let expected_header = match codec {
707 CodecType::None => CODEC_MAGIC_NONE,
708 CodecType::ZstdBasic => CODEC_MAGIC_ZSTD_BASIC,
709 CodecType::ZstdDict => CODEC_MAGIC_ZSTD_DICT,
710 CodecType::Delta => CODEC_MAGIC_DELTA,
711 };
712 if header != expected_header {
713 return Err(TdbError::Other(format!(
714 "Codec mismatch: header byte 0x{:02x} does not match expected codec {}",
715 header, codec
716 )));
717 }
718
719 self.decompress_payload(payload, codec)
720 }
721
722 pub fn decompress_auto(&self, data: &[u8]) -> TdbResult<Vec<u8>> {
727 if data.is_empty() {
728 return Ok(Vec::new());
729 }
730 let header = data[0];
732 let payload = &data[1..];
733
734 let codec = match header {
735 CODEC_MAGIC_NONE => CodecType::None,
736 CODEC_MAGIC_ZSTD_BASIC => CodecType::ZstdBasic,
737 CODEC_MAGIC_ZSTD_DICT => CodecType::ZstdDict,
738 CODEC_MAGIC_DELTA => CodecType::Delta,
739 _ => {
740 return Err(TdbError::Other(format!(
741 "Unknown codec header byte: 0x{:02x}",
742 header
743 )))
744 }
745 };
746
747 self.decompress_payload(payload, codec)
748 }
749
750 fn decompress_payload(&self, payload: &[u8], codec: CodecType) -> TdbResult<Vec<u8>> {
753 match codec {
754 CodecType::None => Ok(payload.to_vec()),
755 CodecType::ZstdBasic => Self::zstd_decompress_raw(payload, None),
756 CodecType::ZstdDict => {
757 let dict = self.zstd_dict.as_deref().ok_or_else(|| {
758 TdbError::Other(
759 "ZstdDict decompression requested but no dictionary loaded".to_string(),
760 )
761 })?;
762 Self::zstd_decompress_raw(payload, Some(dict))
763 }
764 CodecType::Delta => Self::delta_decompress(payload),
765 }
766 }
767
768 fn zstd_compress_raw(data: &[u8], level: i32, dict: Option<&[u8]>) -> TdbResult<Vec<u8>> {
769 use std::io::Write;
770 let compressed = match dict {
771 None => {
772 let mut enc = oxiarc_zstd::ZstdStreamEncoder::new(Vec::new(), level);
773 enc.write_all(data)
774 .map_err(|e| TdbError::Other(format!("zstd write: {e}")))?;
775 enc.finish()
776 .map_err(|e| TdbError::Other(format!("zstd finish: {e}")))?
777 }
778 Some(d) => {
779 let mut enc = oxiarc_zstd::ZstdEncoder::new();
780 enc.set_level(level);
781 enc.set_dictionary(d);
782 enc.compress(data)
783 .map_err(|e| TdbError::Other(format!("zstd dict compress: {e}")))?
784 }
785 };
786 Ok(compressed)
787 }
788
789 fn zstd_decompress_raw(data: &[u8], dict: Option<&[u8]>) -> TdbResult<Vec<u8>> {
790 use std::io::Read;
791 match dict {
792 None => {
793 let mut dec = oxiarc_zstd::ZstdStreamDecoder::new(data);
794 let mut out = Vec::new();
795 dec.read_to_end(&mut out)
796 .map_err(|e| TdbError::Other(format!("zstd decompress: {e}")))?;
797 Ok(out)
798 }
799 Some(d) => {
800 let out = oxiarc_zstd::decompress_with_dict(data, d)
802 .map_err(|e| TdbError::Other(format!("zstd dict decompress: {e}")))?;
803 Ok(out)
804 }
805 }
806 }
807
808 fn delta_compress(data: &[u8]) -> TdbResult<Vec<u8>> {
810 if data.is_empty() {
811 return Ok(Vec::new());
812 }
813 let mut out = Vec::with_capacity(1 + (data.len() - 1) * 2);
814 out.push(data[0]);
815 for i in 1..data.len() {
816 let delta = data[i] as i16 - data[i - 1] as i16;
817 out.extend_from_slice(&delta.to_le_bytes());
818 }
819 Ok(out)
820 }
821
822 fn delta_decompress(encoded: &[u8]) -> TdbResult<Vec<u8>> {
823 if encoded.is_empty() {
824 return Ok(Vec::new());
825 }
826 let mut out = Vec::new();
827 out.push(encoded[0]);
828 let mut current = encoded[0] as i16;
829 for chunk in encoded[1..].chunks_exact(2) {
830 let delta = i16::from_le_bytes([chunk[0], chunk[1]]);
831 current = (current + delta).clamp(0, 255);
832 out.push(current as u8);
833 }
834 Ok(out)
835 }
836
837 fn compute_printable_ratio(data: &[u8]) -> f64 {
839 if data.is_empty() {
840 return 0.0;
841 }
842 let printable = data.iter().filter(|&&b| (0x20..=0x7e).contains(&b)).count();
843 printable as f64 / data.len() as f64
844 }
845
846 fn is_mostly_sorted(data: &[u8]) -> bool {
848 if data.len() < 2 {
849 return false;
850 }
851 let non_decreasing = data.windows(2).filter(|w| w[1] >= w[0]).count();
852 non_decreasing as f64 / (data.len() - 1) as f64 >= 0.80
853 }
854}
855
856impl Default for AdaptiveCodec {
857 fn default() -> Self {
858 Self::new(64)
859 }
860}
861
862#[cfg(test)]
863mod adaptive_codec_tests {
864 use super::*;
865
866 #[test]
867 fn test_adaptive_codec_none_below_threshold() {
868 let codec = AdaptiveCodec::new(1000);
869 let data = b"small data".to_vec();
870 let selected = codec.select_codec(&data);
871 assert_eq!(
872 selected,
873 CodecType::None,
874 "small data below threshold should select None"
875 );
876 }
877
878 #[test]
879 fn test_adaptive_codec_zstd_basic_roundtrip() {
880 let codec = AdaptiveCodec::new(10);
881 let data: Vec<u8> = (0..200u8).rev().collect(); let (compressed, chosen) = codec.compress(&data).expect("compress");
884 assert!(
886 chosen == CodecType::ZstdBasic || chosen == CodecType::Delta,
887 "expected ZstdBasic or Delta, got {:?}",
888 chosen
889 );
890 let decompressed = codec.decompress(&compressed, chosen).expect("decompress");
891 assert_eq!(decompressed, data, "ZstdBasic roundtrip must be identical");
892 }
893
894 #[test]
895 fn test_adaptive_codec_zstd_dict_roundtrip() {
896 use crate::compression::zstd_codec::ZstdCodec;
897
898 let samples: Vec<Vec<u8>> = (0..300)
900 .map(|i| {
901 format!(
902 "<http://example.org/r/{}> <http://schema.org/name> \"Name {}\" .",
903 i, i
904 )
905 .into_bytes()
906 })
907 .collect();
908 let dict = ZstdCodec::train_dict(&samples, 4096).expect("train dict");
909
910 let codec = AdaptiveCodec::with_dict(10, dict, 3);
911 let data = b"<http://example.org/r/42> <http://schema.org/name> \"Name 42\" .".repeat(5);
913 let (compressed, chosen) = codec.compress(&data).expect("compress with dict");
914 assert_eq!(
915 chosen,
916 CodecType::ZstdDict,
917 "text-like data with dict should select ZstdDict"
918 );
919
920 let decompressed = codec
921 .decompress(&compressed, chosen)
922 .expect("decompress with dict");
923 assert_eq!(decompressed, data, "ZstdDict roundtrip must be identical");
924 }
925
926 #[test]
927 fn test_adaptive_codec_delta_roundtrip() {
928 let codec = AdaptiveCodec::new(10);
929 let data: Vec<u8> = (0..200u8).collect();
931 let (compressed, chosen) = codec.compress(&data).expect("compress sorted");
932 assert_eq!(
933 chosen,
934 CodecType::Delta,
935 "sorted data should select Delta codec"
936 );
937
938 let decompressed = codec
939 .decompress(&compressed, chosen)
940 .expect("decompress delta");
941 assert_eq!(decompressed, data, "Delta roundtrip must be identical");
942 }
943
944 #[test]
945 fn test_adaptive_codec_none_roundtrip() {
946 let codec = AdaptiveCodec::new(1000);
947 let data = b"small".to_vec();
948 let (compressed, chosen) = codec.compress(&data).expect("compress none");
949 assert_eq!(chosen, CodecType::None);
950
951 let decompressed = codec
952 .decompress(&compressed, chosen)
953 .expect("decompress none");
954 assert_eq!(decompressed, data, "None codec roundtrip must be identical");
955 }
956
957 #[test]
958 fn test_adaptive_codec_decompress_auto() {
959 let codec = AdaptiveCodec::new(10);
960 let data = b"Auto-detect decompression test data for the adaptive codec.".repeat(10);
961 let (compressed, _chosen) = codec.compress(&data).expect("compress");
962 let decompressed = codec.decompress_auto(&compressed).expect("decompress auto");
963 assert_eq!(
964 decompressed, data,
965 "decompress_auto roundtrip must be identical"
966 );
967 }
968
969 #[test]
970 fn test_adaptive_codec_empty_data() {
971 let codec = AdaptiveCodec::new(64);
972 let (compressed, chosen) = codec.compress(&[]).expect("compress empty");
973 assert_eq!(chosen, CodecType::None);
975 let decompressed = codec
976 .decompress(&compressed, chosen)
977 .expect("decompress empty");
978 assert_eq!(decompressed, b"");
979 }
980
981 #[test]
982 fn test_adaptive_codec_codec_type_display() {
983 assert_eq!(CodecType::ZstdBasic.to_string(), "ZstdBasic");
984 assert_eq!(CodecType::ZstdDict.to_string(), "ZstdDict");
985 assert_eq!(CodecType::Delta.to_string(), "Delta");
986 assert_eq!(CodecType::None.to_string(), "None");
987 }
988
989 #[test]
990 fn test_adaptive_codec_wrong_codec_type_error() {
991 let codec = AdaptiveCodec::new(10);
992 let data: Vec<u8> = (0..200u8).collect(); let (compressed, _chosen) = codec.compress(&data).expect("compress");
994 let result = codec.decompress(&compressed, CodecType::ZstdBasic);
996 assert!(result.is_err(), "wrong CodecType should return error");
997 }
998
999 #[test]
1000 fn test_adaptive_codec_large_rdf_data() {
1001 let codec = AdaptiveCodec::new(64);
1002 let rdf_triple = b"<http://dbpedia.org/resource/Thing> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://dbpedia.org/ontology/Thing> .\n";
1003 let mut data = Vec::new();
1004 for _ in 0..500 {
1005 data.extend_from_slice(rdf_triple);
1006 }
1007
1008 let (compressed, chosen) = codec.compress(&data).expect("compress large RDF");
1009 println!(
1010 "Large RDF: {} -> {} bytes ({:?})",
1011 data.len(),
1012 compressed.len(),
1013 chosen
1014 );
1015
1016 let decompressed = codec
1017 .decompress(&compressed, chosen)
1018 .expect("decompress large RDF");
1019 assert_eq!(decompressed, data, "large RDF roundtrip must be identical");
1020 assert!(
1022 compressed.len() < data.len(),
1023 "large repetitive RDF should compress, ratio: {:.2}",
1024 compressed.len() as f64 / data.len() as f64
1025 );
1026 }
1027}